[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: cargo\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: \"13:00\"\n  open-pull-requests-limit: 10\n  ignore:\n  - dependency-name: env_logger\n    versions:\n    - \">= 0.7.a, < 0.8\"\n  - dependency-name: mio\n    versions:\n    - \">= 0.7.a, < 0.8\"\n  - dependency-name: ring\n    versions:\n    - \">= 0.15.a, < 0.16\"\n  - dependency-name: ring\n    versions:\n    - \">= 0.16.a, < 0.17\"\n  - dependency-name: url\n    versions:\n    - \">= 2.a, < 3\"\n- package-ecosystem: cargo\n  directory: \"/tools/apps\"\n  schedule:\n    interval: daily\n    time: \"13:00\"\n  open-pull-requests-limit: 10\n  ignore:\n  - dependency-name: env_logger\n    versions:\n    - \">= 0.7.a, < 0.8\"\n  - dependency-name: env_logger\n    versions:\n    - \">= 0.8.a, < 0.9\"\n  - dependency-name: mio\n    versions:\n    - \">= 0.7.a, < 0.8\"\n  - dependency-name: url\n    versions:\n    - \">= 2.a, < 3\"\n- package-ecosystem: cargo\n  directory: \"/tools/qlog\"\n  schedule:\n    interval: daily\n    time: \"13:00\"\n  open-pull-requests-limit: 10\n- package-ecosystem: cargo\n  directory: \"/fuzz\"\n  schedule:\n    interval: daily\n    time: \"13:00\"\n  open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "on:\n  push:\n    branches:\n      - master\n\nname: Deploy\n\nenv:\n  RUSTDOCFLAGS: \"--cfg docsrs\"\n\njobs:\n  docs:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install stable toolchain\n        uses: dtolnay/rust-toolchain@nightly\n\n      - name: Install qlog-dancer dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install libexpat1-dev libfreetype6-dev libfontconfig1-dev\n\n      - name: Run cargo doc\n        run: cargo doc --no-deps --all-features\n\n      - name: Deploy to GitHub Pages\n        uses: crazy-max/ghaction-github-pages@v3\n        with:\n          target_branch: gh-pages\n          build_dir: target/doc\n          fqdn: docs.quic.tech\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  docker:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Build Docker images\n        run: make docker-build\n\n      - name: Login to DockerHub\n        uses: docker/login-action@v2\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_TOKEN }}\n\n      - name: Publish Docker images\n        run: make docker-publish\n"
  },
  {
    "path": ".github/workflows/nightly.yml",
    "content": "on: [push, pull_request]\n\nname: Nightly\n\npermissions:\n  contents: read\n  pull-requests: write\n\nenv:\n  FEATURES: \"async,ffi,qlog\"\n  RUSTFLAGS: \"-D warnings\"\n  RUSTDOCFLAGS: \"--cfg docsrs\"\n  RUSTTOOLCHAIN: \"nightly\"\n\nconcurrency:\n  group: ${{ github.ref }}-nightly\n  cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}\n\njobs:\n  quiche_nightly:\n    runs-on: ubuntu-latest\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install nightly toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n\n      - name: Install qlog-dancer dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install libexpat1-dev libfreetype6-dev libfontconfig1-dev\n\n      - name: Run cargo test\n        run: cargo test --verbose --all-targets --features=boringssl-boring-crate,${{ env.FEATURES }}\n\n      # Need to run doc tests separately.\n      # (https://github.com/rust-lang/cargo/issues/6669)\n      - name: Run cargo doc test\n        run: cargo test --verbose --doc --features=boringssl-boring-crate,${{ env.FEATURES }}\n\n      # NOTE: this is disabled as it fails when building changes that bump\n      # version of local crates (e.g. when doing a `qlog` release) that have not\n      # been published yet, and we couldn't find a workaround.\n      #\n      # - name: Run cargo package\n      #   run: cargo package --verbose --workspace --exclude=quiche_apps --allow-dirty\n\n      - name: Run cargo doc\n        run: cargo doc --no-deps --all-features --document-private-items\n\n      - name: Build C examples\n        run: |\n          sudo apt-get install libev-dev uthash-dev\n          make -C quiche/examples\n\n  fuzz:\n    runs-on: ubuntu-latest\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install nightly toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n\n      - name: Install cargo-fuzz\n        run: cargo install cargo-fuzz\n\n      - name: Run cargo fuzz for packet_recv_client\n        run: cargo fuzz run packet_recv_client -- -runs=1\n\n      - name: Run cargo fuzz for packet_recv_server\n        run: cargo fuzz run packet_recv_server -- -runs=1\n\n      - name: Run cargo fuzz for qpack_decode\n        run: cargo fuzz run qpack_decode -- -runs=1\n\n  http3_test_nightly:\n    runs-on: ubuntu-latest\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install nightly toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n\n      - name: Run cargo test\n        run: cargo test --no-run --verbose --manifest-path=tools/http3_test/Cargo.toml\n\n  fmt:\n    runs-on: ubuntu-latest\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install nightly toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n          components: rustfmt\n\n      - name: Run cargo fmt - quiche\n        run: cargo fmt -- --check\n\n      - name: Run cargo fmt - http3_test\n        run: cargo fmt --manifest-path=tools/http3_test/Cargo.toml -- --check\n\n      - name: Run cargo fmt - fuzz\n        run: cargo fmt --manifest-path=fuzz/Cargo.toml -- --check\n"
  },
  {
    "path": ".github/workflows/semgrep.yml",
    "content": "on:\n  pull_request: {}\n  workflow_dispatch: {}\n  push: \n    branches:\n      - main\n      - master\n  schedule:\n    - cron: '0 0 * * *'\n\nname: Semgrep config\n\npermissions:\n  contents: read\n  pull-requests: write\n\nconcurrency:\n  group: ${{ github.ref }}-semgrep\n  cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}\n\njobs:\n  semgrep:\n    name: semgrep/ci\n    runs-on: ubuntu-latest\n    env:\n      SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}\n      SEMGREP_URL: https://cloudflare.semgrep.dev\n      SEMGREP_APP_URL: https://cloudflare.semgrep.dev\n      SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version\n    container:\n      image: semgrep/semgrep\n    steps:\n      - uses: actions/checkout@v4\n      - run: semgrep ci\n"
  },
  {
    "path": ".github/workflows/stable.yml",
    "content": "on: [push, pull_request]\n\nname: Stable\n\npermissions:\n  contents: read\n  pull-requests: write\n\nenv:\n  DEFAULT_OPTIONS: \"--features=async,ffi,qlog --workspace\"\n  NO_BORING_OPTIONS: \"--features=ffi,qlog --workspace --exclude h3i --exclude tokio-quiche\"\n  RUSTFLAGS: \"-D warnings\"\n  RUSTTOOLCHAIN: \"stable\"\n\nconcurrency:\n  group: ${{ github.ref }}-stable\n  cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}\n\njobs:\n  quiche:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        tls-feature:\n          - \"\" # default, boringssl-vendored\n          - \"boringssl-boring-crate\"\n          - \"openssl\"\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install stable toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n          components: clippy\n\n      - name: Install qlog-dancer dependencies\n        run: |\n          sudo apt-get update\n          sudo apt-get install libexpat1-dev libfreetype6-dev libfontconfig1-dev\n\n      - name: Unused dependency check\n        if: ${{ matrix.tls-feature == '' }}\n        uses: bnjbvr/cargo-machete@main\n\n      - name: Build OpenSSL\n        if: ${{ matrix.tls-feature == 'openssl' }}\n        run: |\n          git clone https://github.com/quictls/openssl\n          cd openssl\n          ./Configure --prefix=\"$PWD/install\"\n          make -j\"$(nproc)\"\n          make install -j\"$(nproc)\"\n          echo \"PKG_CONFIG_PATH=$PWD\" >> \"$GITHUB_ENV\"\n          echo \"LD_LIBRARY_PATH=$PWD\" >> \"$GITHUB_ENV\"\n\n      - name: Run cargo test\n        if: ${{ matrix.tls-feature == 'boringssl-boring-crate' }}\n        run: cargo test --verbose --all-targets --features=${{ matrix.tls-feature }} ${{ env.DEFAULT_OPTIONS }}\n\n      # tokio-quiche requires the `boring` crate, so don't run its tests when\n      # building without it.\n      - name: Run cargo test\n        if: ${{ matrix.tls-feature != 'boringssl-boring-crate' }}\n        run: cargo test --verbose --all-targets --features=${{ matrix.tls-feature }} ${{ env.NO_BORING_OPTIONS }}\n\n      # Need to run doc tests separately.\n      # (https://github.com/rust-lang/cargo/issues/6669)\n      - name: Run cargo doc test\n        if: ${{ matrix.tls-feature == 'boringssl-boring-crate' }}\n        run: cargo test --verbose --doc --features=${{ matrix.tls-feature }} ${{ env.DEFAULT_OPTIONS }}\n\n      # Need to run doc tests separately.\n      # (https://github.com/rust-lang/cargo/issues/6669)\n      #\n      # tokio-quiche requires the `boring` crate, so don't run its tests when\n      # building without it.\n      - name: Run cargo doc test\n        if: ${{ matrix.tls-feature != 'boringssl-boring-crate' }}\n        run: cargo test --verbose --doc --features=${{ matrix.tls-feature }} ${{ env.NO_BORING_OPTIONS }}\n\n      # NOTE: this is disabled as it fails when building changes that bump\n      # version of local crates (e.g. when doing a `qlog` release) that have not\n      # been published yet, and we couldn't find a workaround.\n      #\n      # - name: Run cargo package\n      #   run: cargo package --verbose --workspace --exclude=quiche_apps --allow-dirty\n\n      - name: Run cargo clippy\n        run: cargo clippy --features=${{ matrix.tls-feature }} ${{ env.DEFAULT_OPTIONS }} -- -D warnings\n\n      - name: Run cargo clippy on examples\n        run: cargo clippy --examples --features=${{ matrix.tls-feature }} ${{ env.DEFAULT_OPTIONS }} -- -D warnings\n\n      - name: Run cargo doc\n        run: cargo doc --no-deps --all-features --document-private-items\n\n      - name: Build C examples\n        run: |\n          sudo apt-get install libev-dev uthash-dev\n          make -C quiche/examples\n\n  quiche_macos:\n    strategy:\n      matrix:\n        target:\n          - \"macos-15-intel\"     # Intel (x86_64)\n          - \"macos-latest\" # Apple Silicon (M1)\n    runs-on: ${{ matrix.target }}\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install stable toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n\n      - name: Run cargo test\n        run: cargo test --verbose --all-targets ${{ env.DEFAULT_OPTIONS }}\n\n      - name: Build C examples\n        run: |\n          brew install libev uthash\n          make -C quiche/examples\n\n  quiche_ios:\n    runs-on: macos-latest\n    strategy:\n      matrix:\n        target: [\"x86_64-apple-ios\", \"aarch64-apple-ios\"]\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    env:\n      IPHONEOS_DEPLOYMENT_TARGET: \"10.0\"\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install stable toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n          targets: ${{ matrix.target }}\n\n      - name: Remove cdylib from iOS build\n        run: |\n          sed -i -e 's/, \"cdylib\"//g' quiche/Cargo.toml\n\n      - name: Run cargo build\n        run: cargo build --target=${{ matrix.target }} --verbose\n\n  quiche_windows:\n    runs-on: windows-2022\n    strategy:\n      matrix:\n        target: [\"x86_64-pc-windows-msvc\", \"i686-pc-windows-msvc\", \"x86_64-pc-windows-gnu\", \"i686-pc-windows-gnu\"]\n        include:\n          - target: \"i686-pc-windows-gnu\"\n            cflags: \"-mlong-double-64\" # bindgen detects that Rust doesn't support 80-bit long double\n    env:\n      CFLAGS: ${{ matrix.cflags }}\n      CXXFLAGS: ${{ matrix.cflags }}\n      BINDGEN_EXTRA_CLANG_ARGS: ${{ matrix.cflags }}\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install stable toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n          targets: ${{ matrix.target }}\n\n      - name: Set up MinGW for 64 bit\n        if: matrix.target == 'x86_64-pc-windows-gnu'\n        uses: bwoodsend/setup-winlibs-action@v1.10\n        with:\n          tag: 12.2.0-16.0.0-10.0.0-msvcrt-r5\n\n      - name: Set up MinGW for 32 bit\n        if: matrix.target == 'i686-pc-windows-gnu'\n        uses: bwoodsend/setup-winlibs-action@v1.10\n        with:\n          architecture: i686\n          tag: 12.2.0-16.0.0-10.0.0-msvcrt-r5\n\n      - name: Install dependencies\n        uses: crazy-max/ghaction-chocolatey@v3\n        with:\n          args: install nasm\n\n      - name: Run cargo build\n        if: endsWith(matrix.target, '-gnu')\n        run: cargo build --target=${{ matrix.target }} --verbose --all-targets ${{ env.DEFAULT_OPTIONS }} --exclude qlog-dancer --features=boringssl-boring-crate\n\n      - name: Run cargo test\n        if: endsWith(matrix.target, '-msvc')\n        run: cargo test --target=${{ matrix.target }} --verbose --all-targets ${{ env.DEFAULT_OPTIONS }} --exclude qlog-dancer --features=boringssl-boring-crate\n\n  quiche_multiarch:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        target: [\"aarch64-unknown-linux-gnu\",\"armv7-unknown-linux-gnueabihf\",\"i686-unknown-linux-gnu\"]\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install stable toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n\n      - name: Install cargo-binstall\n        uses: cargo-bins/cargo-binstall@main\n\n      - name: Install cross\n        run: cargo-binstall -y cross\n\n      - name: Run cargo test using cross\n        run: cross test --target=${{ matrix.target }} --verbose --all-targets ${{ env.NO_BORING_OPTIONS }} --exclude qlog-dancer\n\n  http3_test:\n    runs-on: ubuntu-latest\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install stable toolchain\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n          components: clippy\n\n      - name: Run cargo test\n        run: cargo test --no-run --verbose --manifest-path=tools/http3_test/Cargo.toml\n\n      - name: Run cargo clippy\n        run: cargo clippy --manifest-path=tools/http3_test/Cargo.toml -- -D warnings\n\n  docker:\n    runs-on: ubuntu-latest\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Build Docker images\n        run: make docker-build\n\n  android_ndk_lts:\n    runs-on: ubuntu-latest\n    env:\n      API_LEVEL: \"21\"\n    strategy:\n      matrix:\n        target: [\"aarch64-linux-android\",\"armv7-linux-androideabi\",\"x86_64-linux-android\",\"i686-linux-android\"]\n        include:\n          - target: \"aarch64-linux-android\"\n            arch: \"arm64-v8a\"\n          - target: \"armv7-linux-androideabi\"\n            arch: \"armeabi-v7a\"\n          - target: \"x86_64-linux-android\"\n            arch: \"x86_64\"\n          - target: \"i686-linux-android\"\n            arch: \"x86\"\n    # Only run on \"pull_request\" event for external PRs. This is to avoid\n    # duplicate builds for PRs created from internal branches.\n    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v4\n        with:\n          submodules: 'recursive'\n\n      - name: Install stable toolchain for the target\n        uses: dtolnay/rust-toolchain@master\n        with:\n          toolchain: ${{ env.RUSTTOOLCHAIN }}\n          targets: ${{ matrix.target }}\n\n      - name: Install cargo-ndk\n        run: cargo install cargo-ndk\n\n      - name: Run cargo ndk\n        run: cargo ndk --manifest-path quiche/Cargo.toml --target ${{ matrix.arch }} --platform ${{ env.API_LEVEL }} -- build --verbose --features ffi\n"
  },
  {
    "path": ".gitignore",
    "content": "**/target\n**/*.rs.bk\n**/Cargo.lock\n**/*.*qlog\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "test-job1:\n  stage: test\n  script:\n    - echo \"This job tests nothing\"\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"boringssl\"]\n\tpath = quiche/deps/boringssl\n\turl = https://github.com/google/boringssl.git\n\tignore = dirty\n"
  },
  {
    "path": ".semgrepignore",
    "content": "/apps/\n/fuzz/\n/quiche/examples/\n/tokio-quiche/examples/\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# PROJECT KNOWLEDGE BASE\n\n**Generated:** 2026-02-20\n**Commit:** 89d1850f\n**Branch:** master\n\n## OVERVIEW\n\nCloudflare's QUIC and HTTP/3 implementation in Rust. Workspace of 11 crates: core `quiche` protocol library, `tokio-quiche` async integration, CLI tools (`apps`, `h3i`), logging/analysis (`qlog`, `qlog-dancer`, `netlog`), and supporting primitives (`octets`, `buffer-pool`, `datagram-socket`, `task-killswitch`).\n\n## STRUCTURE\n\n```\nquiche/                     # Core QUIC+H3 library (C FFI, BoringSSL submodule)\ntokio-quiche/               # Async tokio wrapper (server/client drivers)\napps/                       # CLI binaries: quiche-client, quiche-server\nh3i/                        # HTTP/3 interactive testing/debugging tool\nqlog/                       # qlog event schema (RFC draft)\nqlog-dancer/                # qlog/netlog visualization (native + wasm)\nnetlog/                     # Chrome netlog parser\noctets/                     # Zero-copy byte buffer primitives\nbuffer-pool/                # Sharded lock-free buffer pool\ndatagram-socket/            # UDP socket abstraction (sendmmsg/recvmmsg)\ntask-killswitch/            # Async task cancellation primitive\nfuzz/                       # Fuzz targets (excluded from workspace)\ntools/                      # Android build tooling, http3_test harness\n```\n\n## DEPENDENCY GRAPH\n\n```\noctets  buffer-pool  task-killswitch  qlog  netlog    (Layer 0: no workspace deps)\n  |         |              |            |      |\n  v         v              |            v      v\nquiche  datagram-socket    |        qlog-dancer        (Layer 1)\n  |   \\     |              |\n  v    \\    v              v\n  tokio-quiche  <----------+                           (Layer 2: depends on most)\n  |     |\n  v     v\n h3i   apps                                           (Layer 3: end-user tools)\n```\n\n## WHERE TO LOOK\n\n| Task | Location | Notes |\n|------|----------|-------|\n| QUIC connection logic | `quiche/src/lib.rs` | 9k lines, core `Connection` struct |\n| HTTP/3 protocol | `quiche/src/h3/mod.rs` | Own `Error`/`Result` types |\n| Congestion control | `quiche/src/recovery/` | Two impls: `congestion/` (legacy) + `gcongestion/` (BBR2) |\n| TLS/crypto backends | `quiche/src/tls/`, `quiche/src/crypto/` | BoringSSL + OpenSSL, cfg-gated |\n| C FFI | `quiche/src/ffi.rs` + `quiche/include/quiche.h` | Behind `ffi` feature |\n| Async server/client | `tokio-quiche/src/` | `ApplicationOverQuic` trait is the extension point |\n| H3 async driver | `tokio-quiche/src/http3/driver/` | `DriverHooks` sealed trait, channels |\n| QUIC IO worker | `tokio-quiche/src/quic/io/worker.rs` | Connection FSM, GSO/GRO |\n| Packet routing | `tokio-quiche/src/quic/router/` | Demux by DCID |\n| Test infra | `quiche/src/test_utils.rs` | `Pipe` struct for in-memory QUIC pairs |\n| Config cascade | `tokio-quiche/src/settings/` → `quiche::Config` | `ConnectionParams` → `quiche::Config` → `h3::Config` |\n\n## CODE MAP\n\n| Symbol | Type | Location | Role |\n|--------|------|----------|------|\n| `Connection` | struct | `quiche/src/lib.rs` | Core QUIC connection |\n| `Config` | struct | `quiche/src/lib.rs` | Transport configuration |\n| `h3::Connection` | struct | `quiche/src/h3/mod.rs` | HTTP/3 over QUIC |\n| `ApplicationOverQuic` | trait | `tokio-quiche/src/quic/` | Async app lifecycle hook |\n| `H3Driver<H>` | struct | `tokio-quiche/src/http3/driver/` | Generic H3 driver |\n| `IoWorker<Tx,M,S>` | struct | `tokio-quiche/src/quic/io/worker.rs` | Per-connection IO loop |\n| `Pipe` | struct | `quiche/src/test_utils.rs` | In-memory test connection pair |\n| `BufFactory` | trait | `quiche/src/range_buf.rs` | Zero-copy buffer creation |\n| `Recovery` | enum | `quiche/src/recovery/mod.rs` | CC dispatch via enum_dispatch |\n| `RecoveryOps` | trait | `quiche/src/recovery/mod.rs` | 40+ method CC interface |\n\n## CONVENTIONS\n\n- **Line width 82** (`rustfmt.toml`), comments 80. Nightly rustfmt required.\n- **One `use` per item** (`imports_granularity = \"Item\"`, vertical layout).\n- **`pub(crate)`** for cross-module internals; `pub` only for true public API.\n- **BSD-2-Clause copyright header** on every `.rs` file.\n- **`#[macro_use] extern crate log`** (legacy style, no `use log::*`).\n- **Domain abbreviations**: `cid`, `scid`/`dcid`, `pkt`, `dgram`, `bidi`/`uni`, `rtt`.\n- **`mod.rs` pattern** for submodules (not inline `foo/` + `foo.rs`).\n- **Debug symbols in release** (`profile.release.debug = true`).\n- **`#![warn(missing_docs)]`** -- public items must be documented.\n\n## ANTI-PATTERNS (THIS PROJECT)\n\n- **Do not use `any` types or type assertions** -- this is Rust; no equivalent concern, but `unsafe` is restricted to FFI boundaries (`tls/`, `crypto/`, `ffi.rs`, `gso.rs`).\n- **Do not add clippy `#[allow]` without justification** -- 33 existing overrides all have documented reasons.\n- **Cognitive complexity lint disabled** (`clippy.toml: 100`) -- complex functions accepted for protocol code, but don't add new ones casually.\n- **Two `Acked` types exist** in `recovery/congestion` and `recovery/gcongestion` -- not unified, don't create a third.\n- **`connection_not_present()` returns `TlsFail`** in tokio-quiche driver -- misleading sentinel, don't propagate this pattern.\n- **`Error::Done` used as success signal** in H3 driver write path -- non-obvious, don't replicate.\n- **`transmute` of `Instant`** in `gso.rs` -- fragile, platform-dependent, don't extend.\n\n## FEATURE FLAGS\n\n```\nquiche:        default=boringssl-vendored | boringssl-boring-crate | openssl\n               qlog, gcongestion, internal, ffi, fuzzing, sfv, custom-client-dcid\ntokio-quiche:  fuzzing, quiche_internal, gcongestion, zero-copy, rpk\n               (hardcodes: quiche/boringssl-boring-crate + quiche/qlog)\nh3i:           async (enables tokio-quiche dependency)\n```\n\n## COMMANDS\n\n```bash\n# Dev\ncargo build                                           # build workspace (vendored BoringSSL)\ncargo test --all-targets --features=async,ffi,qlog --workspace  # full test suite\ncargo test --doc --features=async,ffi,qlog --workspace          # doc tests (separate!)\n\n# Lint\ncargo clippy --features=boringssl-vendored --workspace -- -D warnings\ncargo +nightly fmt -- --check                                  \n\n# Fuzz\ncargo fuzz run packet_recv_client -- -runs=1\n\n# Docker\nmake docker-build                                     # quiche-base + quiche-qns images\n```\n\n## NOTES\n\n- **Git submodules required**: `git submodule update --init --recursive` for BoringSSL.\n- **MSRV 1.85**: `rust-version` field in Cargo.toml.\n- **Doc tests are separate**: `cargo test --all-targets` does NOT run doc tests (cargo#6669).\n- **`QUICHE_BSSL_PATH`**: env var to skip vendored BoringSSL build (use pre-built).\n- **`RUSTFLAGS=\"-D warnings\"`**: CI enforces; all warnings are errors.\n- **Cargo.lock is gitignored** (library project).\n- **Dual CI**: GitHub Actions (real) + GitLab CI (no-op stub).\n- **`cargo package` disabled**: commented out due to unpublished local crate version issues.\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "* @cloudflare/protocols\n"
  },
  {
    "path": "COPYING",
    "content": "Copyright (C) 2018-2019, Cloudflare, Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n\n    * 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\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 <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, 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\nON ANY 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": "Cargo.toml",
    "content": "[workspace]\nmembers = [\n  \"apps\",\n  \"buffer-pool\",\n  \"datagram-socket\",\n  \"h3i\",\n  \"netlog\",\n  \"octets\",\n  \"qlog\",\n  \"qlog-dancer\",\n  \"quiche\",\n  \"task-killswitch\",\n  \"tokio-quiche\",\n]\nexclude = [\"fuzz\", \"tools/http3_test\"]\nresolver = \"2\"\n\n[workspace.package]\nedition = \"2021\"\nrepository = \"https://github.com/cloudflare/quiche\"\nlicense = \"BSD-2-Clause\"\nreadme = \"README.md\"\nkeywords = [\"quic\", \"http3\"]\ncategories = [\"network-programming\"]\n\n[workspace.metadata.release]\npre-release-commit-message = \"{{crate_name}}: release {{version}}\"\nconsolidate-commits = false\ntag-prefix = \"{{crate_name}}-\"\ntag-name = \"{{prefix}}{{version}}\"\ntag-message = \"{{crate_name}} {{version}}\"\npublish = false\n\n[workspace.dependencies]\nanyhow = { version = \"1\" }\nassert_matches = { version = \"1\" }\nboring = { version = \"4.3\" }\nbuffer-pool = { version = \"0.2.1\", path = \"./buffer-pool\" }\ncrossbeam = { version = \"0.8.1\", default-features = false }\ndashmap = { version = \"6\" }\ndatagram-socket = { version = \"0.7.0\", path = \"./datagram-socket\" }\nenv_logger = \"0.11\"\nfoundations = { version = \">=4,<6\", default-features = false }\nfutures = { version = \"0.3\" }\nfutures-util = { version = \"0.3\", default-features = false }\nh3i = { version = \"0.6\", path = \"./h3i\" }\nipnetwork = { version = \"0.20\" }\nlibc = { version = \"0.2.76\", default-features = false }\nlog = { version = \"0.4.20\" }\nmio = { version = \"1\" }\nnix = { version = \"0.30.1\", features = [\"net\", \"uio\", \"socket\"] }\nnetlog = { version = \"0.1\", path=\"./netlog\" }\noctets = { version = \"0.3.5\", path = \"./octets\" }\nparking_lot = { version = \"0.12.1\", default-features = false }\npin-project = { version = \"1.0.12\" }\nqlog = { version = \"0.16.0\", path = \"./qlog\" }\nquiche = { version = \"0.26.1\", path = \"./quiche\" }\nregex = { version = \"1.4.2\" }\nring = { version = \"0.17.8\" }\nrstest = { version = \"0.26.1\" }\nserde = { version = \"1\" }\nserde_json = { version = \"1\" }\nserde_with = { version = \"~3.17\", default-features = false }\nslog-scope = { version = \"4.0\" }\nslog-stdlog = { version = \"4.1.1\" }\nsmallvec = { version = \"1.10\", default-features = false }\ntask-killswitch = { version = \"0.2.1\", path = \"./task-killswitch\" }\nthiserror = { version = \"2\" }\ntokio = { version = \"1.44\", default-features = false }\ntokio-quiche = { version = \"0.16.1\", path = \"./tokio-quiche\" }\ntokio-stream = { version = \"0.1\" }\ntokio-util = { version = \"0.7.13\" }\ntriomphe = { version = \"0.1\" }\nurl = { version = \"2\" }\n\n[profile.bench]\ndebug = true\n\n[profile.release]\ndebug = true\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM rust:1.85 AS build\n\nWORKDIR /build\n\nCOPY Cargo.toml ./\nCOPY apps/ ./apps/\nCOPY buffer-pool ./buffer-pool/\nCOPY datagram-socket/ ./datagram-socket/\nCOPY h3i/ ./h3i/\nCOPY netlog/ ./netlog/\nCOPY octets/ ./octets/\nCOPY qlog/ ./qlog/\nCOPY qlog-dancer/ ./qlog-dancer/\nCOPY quiche/ ./quiche/\nCOPY task-killswitch ./task-killswitch/\nCOPY tokio-quiche ./tokio-quiche/\n\nRUN apt-get update && apt-get install -y cmake && rm -rf /var/lib/apt/lists/*\n\nRUN cargo build --release --manifest-path apps/Cargo.toml\n\n##\n## quiche-base: quiche image for apps\n##\nFROM debian:latest AS quiche-base\n\nRUN apt-get update && apt-get install -y ca-certificates && \\\n    rm -rf /var/lib/apt/lists/*\n\nCOPY --from=build \\\n     /build/target/release/quiche-client \\\n     /build/target/release/quiche-server \\\n     /usr/local/bin/\n\nENV PATH=\"/usr/local/bin/:${PATH}\"\nENV RUST_LOG=info\n\n##\n## quiche-qns: quiche image for quic-interop-runner\n## https://github.com/marten-seemann/quic-network-simulator\n## https://github.com/marten-seemann/quic-interop-runner\n##\nFROM martenseemann/quic-network-simulator-endpoint:latest AS quiche-qns\n\nWORKDIR /quiche\n\nRUN apt-get update && apt-get install -y wait-for-it && rm -rf /var/lib/apt/lists/*\n\nCOPY --from=build \\\n     /build/target/release/quiche-client \\\n     /build/target/release/quiche-server \\\n     /build/apps/run_endpoint.sh \\\n     ./\n\nENV RUST_LOG=trace\n\nENTRYPOINT [ \"./run_endpoint.sh\" ]\n"
  },
  {
    "path": "Makefile",
    "content": "DOCKER    = docker\n\nBASE_REPO = cloudflare/quiche\nBASE_TAG  = latest\n\nQNS_REPO  = cloudflare/quiche-qns\nQNS_TAG   = latest\n\nFUZZ_REPO = cloudflare.mayhem.security:5000/protocols/quiche-libfuzzer\nFUZZ_TAG  = latest\n\ndocker-build: docker-base docker-qns\n\n# build quiche-apps only\n.PHONY: build-apps\nbuild-apps:\n\tcargo build --package=quiche_apps\n\n# build base image\n.PHONY: docker-base\ndocker-base: Dockerfile\n\t$(DOCKER) build --target quiche-base -t $(BASE_REPO):$(BASE_TAG) .\n\n# build qns image\n.PHONY: docker-qns\ndocker-qns: Dockerfile apps/run_endpoint.sh\n\t$(DOCKER) build --target quiche-qns -t $(QNS_REPO):$(QNS_TAG) .\n\n.PHONY: docker-publish\ndocker-publish:\n\t$(DOCKER) push $(BASE_REPO):$(BASE_TAG)\n\t$(DOCKER) push $(QNS_REPO):$(QNS_TAG)\n\n# build fuzzers\n.PHONY: build-fuzz\nbuild-fuzz:\n\tcargo +nightly fuzz build --release --debug-assertions packet_recv_client\n\tcargo +nightly fuzz build --release --debug-assertions packet_recv_server\n\tcargo +nightly fuzz build --release --debug-assertions packets_recv_server\n\tcargo +nightly fuzz build --release --debug-assertions packets_posths_server\n\tcargo +nightly fuzz build --release --debug-assertions qpack_decode\n\n# build fuzzing image\n.PHONY: docker-fuzz\ndocker-fuzz:\n\t$(DOCKER) build -f fuzz/Dockerfile --target quiche-libfuzzer --tag $(FUZZ_REPO):$(FUZZ_TAG) .\n\n.PHONY: docker-fuzz-publish\ndocker-fuzz-publish:\n\t$(DOCKER) push $(FUZZ_REPO):$(FUZZ_TAG)\n\n.PHONY: clean\nclean:\n\t@for id in `$(DOCKER) images -q $(BASE_REPO)` `$(DOCKER) images -q $(QNS_REPO)` `$(DOCKER) images -q $(FUZZ_REPO)`; do \\\n\t\techo \">> Removing $$id\"; \\\n\t\t$(DOCKER) rmi -f $$id; \\\n\tdone\n"
  },
  {
    "path": "README.md",
    "content": "![quiche](quiche.svg)\n\n[![crates.io](https://img.shields.io/crates/v/quiche.svg)](https://crates.io/crates/quiche)\n[![docs.rs](https://docs.rs/quiche/badge.svg)](https://docs.rs/quiche)\n[![license](https://img.shields.io/github/license/cloudflare/quiche.svg)](https://opensource.org/licenses/BSD-2-Clause)\n![build](https://img.shields.io/github/actions/workflow/status/cloudflare/quiche/stable.yml?branch=master)\n\n[quiche] is an implementation of the QUIC transport protocol and HTTP/3 as\nspecified by the [IETF]. It provides a low level API for processing QUIC packets\nand handling connection state. The application is responsible for providing I/O\n(e.g. sockets handling) as well as an event loop with support for timers.\n\nFor more information on how quiche came about and some insights into its design\nyou can read a [post] on Cloudflare's blog that goes into some more detail.\n\n[quiche]: https://docs.quic.tech/quiche/\n[ietf]: https://quicwg.org/\n[post]: https://blog.cloudflare.com/enjoy-a-slice-of-quic-and-rust/\n\nWho uses quiche?\n----------------\n\n### Cloudflare\n\nquiche powers Cloudflare edge network's [HTTP/3 support][cloudflare-http3]. The\n[cloudflare-quic.com](https://cloudflare-quic.com) website can be used for\ntesting and experimentation.\n\n### Android\n\nAndroid's DNS resolver uses quiche to [implement DNS over HTTP/3][android-http3].\n\n### curl\n\nquiche can be [integrated into curl][curl-http3] to provide support for HTTP/3.\n\n[cloudflare-http3]: https://blog.cloudflare.com/http3-the-past-present-and-future/\n[android-http3]: https://security.googleblog.com/2022/07/dns-over-http3-in-android.html\n[curl-http3]: https://github.com/curl/curl/blob/master/docs/HTTP3.md#quiche-version\n\nGetting Started\n---------------\n\n### Command-line apps\n\nBefore diving into the quiche API, here are a few examples on how to use the\nquiche tools provided as part of the [quiche-apps](apps/) crate. These are not\nsuitable for production environments; see [disclaimers and\nnotes](#disclaimers-and-notes).\n\nAfter cloning the project according to the command mentioned in the [building](#building) section, the client can be run as follows:\n\n```bash\n $ cargo run --bin quiche-client -- https://cloudflare-quic.com/\n```\n\nwhile the server can be run as follows:\n\n```bash\n $ cargo run --bin quiche-server -- --cert apps/src/bin/cert.crt --key apps/src/bin/cert.key\n```\n\n(note that the certificate provided is self-signed and should not be used in\nproduction)\n\nUse the `--help` command-line flag to get a more detailed description of each\ntool's options.\n\n### Configuring connections\n\nThe first step in establishing a QUIC connection using quiche is creating a\n[`Config`] object:\n\n```rust\nlet mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\nconfig.set_application_protos(&[b\"example-proto\"]);\n\n// Additional configuration specific to application and use case...\n```\n\nThe [`Config`] object controls important aspects of the QUIC connection such\nas QUIC version, ALPN IDs, flow control, congestion control, idle timeout\nand other properties or features.\n\nQUIC is a general-purpose transport protocol and there are several\nconfiguration properties where there is no reasonable default value. For\nexample, the permitted number of concurrent streams of any particular type\nis dependent on the application running over QUIC, and other use-case\nspecific concerns.\n\nquiche defaults several properties to zero, applications most likely need\nto set these to something else to satisfy their needs using the following:\n\n- [`set_initial_max_streams_bidi()`]\n- [`set_initial_max_streams_uni()`]\n- [`set_initial_max_data()`]\n- [`set_initial_max_stream_data_bidi_local()`]\n- [`set_initial_max_stream_data_bidi_remote()`]\n- [`set_initial_max_stream_data_uni()`]\n\n[`Config`] also holds TLS configuration. This can be changed by mutators on\nthe an existing object, or by constructing a TLS context manually and\ncreating a configuration using [`with_boring_ssl_ctx_builder()`].\n\nA configuration object can be shared among multiple connections.\n\n### Connection setup\n\nOn the client-side the [`connect()`] utility function can be used to create\na new connection, while [`accept()`] is for servers:\n\n```rust\n// Client connection.\nlet conn = quiche::connect(Some(&server_name), &scid, local, peer, &mut config)?;\n\n// Server connection.\nlet conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n```\n\n### Handling incoming packets\n\nUsing the connection's [`recv()`] method the application can process\nincoming packets that belong to that connection from the network:\n\n```rust\nlet to = socket.local_addr().unwrap();\n\nloop {\n    let (read, from) = socket.recv_from(&mut buf).unwrap();\n\n    let recv_info = quiche::RecvInfo { from, to };\n\n    let read = match conn.recv(&mut buf[..read], recv_info) {\n        Ok(v) => v,\n\n        Err(e) => {\n            // An error occurred, handle it.\n            break;\n        },\n    };\n}\n```\n\n### Generating outgoing packets\n\nOutgoing packet are generated using the connection's [`send()`] method\ninstead:\n\n```rust\nloop {\n    let (write, send_info) = match conn.send(&mut out) {\n        Ok(v) => v,\n\n        Err(quiche::Error::Done) => {\n            // Done writing.\n            break;\n        },\n\n        Err(e) => {\n            // An error occurred, handle it.\n            break;\n        },\n    };\n\n    socket.send_to(&out[..write], &send_info.to).unwrap();\n}\n```\n\nWhen packets are sent, the application is responsible for maintaining a\ntimer to react to time-based connection events. The timer expiration can be\nobtained using the connection's [`timeout()`] method.\n\n```rust\nlet timeout = conn.timeout();\n```\n\nThe application is responsible for providing a timer implementation, which\ncan be specific to the operating system or networking framework used. When\na timer expires, the connection's [`on_timeout()`] method should be called,\nafter which additional packets might need to be sent on the network:\n\n```rust\n// Timeout expired, handle it.\nconn.on_timeout();\n\n// Send more packets as needed after timeout.\nloop {\n    let (write, send_info) = match conn.send(&mut out) {\n        Ok(v) => v,\n\n        Err(quiche::Error::Done) => {\n            // Done writing.\n            break;\n        },\n\n        Err(e) => {\n            // An error occurred, handle it.\n            break;\n        },\n    };\n\n    socket.send_to(&out[..write], &send_info.to).unwrap();\n}\n```\n\n#### Pacing\n\nIt is recommended that applications [pace] sending of outgoing packets to\navoid creating packet bursts that could cause short-term congestion and\nlosses in the network.\n\nquiche exposes pacing hints for outgoing packets through the [`at`] field\nof the [`SendInfo`] structure that is returned by the [`send()`] method.\nThis field represents the time when a specific packet should be sent into\nthe network.\n\nApplications can use these hints by artificially delaying the sending of\npackets through platform-specific mechanisms (such as the [`SO_TXTIME`]\nsocket option on Linux), or custom methods (for example by using user-space\ntimers).\n\n[pace]: https://datatracker.ietf.org/doc/html/rfc9002#section-7.7\n[`SO_TXTIME`]: https://man7.org/linux/man-pages/man8/tc-etf.8.html\n\n### Sending and receiving stream data\n\nAfter some back and forth, the connection will complete its handshake and\nwill be ready for sending or receiving application data.\n\nData can be sent on a stream by using the [`stream_send()`] method:\n\n```rust\nif conn.is_established() {\n    // Handshake completed, send some data on stream 0.\n    conn.stream_send(0, b\"hello\", true)?;\n}\n```\n\nThe application can check whether there are any readable streams by using\nthe connection's [`readable()`] method, which returns an iterator over all\nthe streams that have outstanding data to read.\n\nThe [`stream_recv()`] method can then be used to retrieve the application\ndata from the readable stream:\n\n```rust\nif conn.is_established() {\n    // Iterate over readable streams.\n    for stream_id in conn.readable() {\n        // Stream is readable, read until there's no more data.\n        while let Ok((read, fin)) = conn.stream_recv(stream_id, &mut buf) {\n            println!(\"Got {} bytes on stream {}\", read, stream_id);\n        }\n    }\n}\n```\n\n### HTTP/3\n\nThe quiche [HTTP/3 module] provides a high level API for sending and\nreceiving HTTP requests and responses on top of the QUIC transport protocol.\n\n[`Config`]: https://docs.quic.tech/quiche/struct.Config.html\n[`set_initial_max_streams_bidi()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi\n[`set_initial_max_streams_uni()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_uni\n[`set_initial_max_data()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data\n[`set_initial_max_stream_data_bidi_local()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local\n[`set_initial_max_stream_data_bidi_remote()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote\n[`set_initial_max_stream_data_uni()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_uni\n[`with_boring_ssl_ctx_builder()`]: https://docs.quic.tech/quiche/struct.Config.html#method.with_boring_ssl_ctx_builder\n[`connect()`]: https://docs.quic.tech/quiche/fn.connect.html\n[`accept()`]: https://docs.quic.tech/quiche/fn.accept.html\n[`recv()`]: https://docs.quic.tech/quiche/struct.Connection.html#method.recv\n[`send()`]: https://docs.quic.tech/quiche/struct.Connection.html#method.send\n[`timeout()`]: https://docs.quic.tech/quiche/struct.Connection.html#method.timeout\n[`on_timeout()`]: https://docs.quic.tech/quiche/struct.Connection.html#method.on_timeout\n[`stream_send()`]: https://docs.quic.tech/quiche/struct.Connection.html#method.stream_send\n[`readable()`]: https://docs.quic.tech/quiche/struct.Connection.html#method.readable\n[`stream_recv()`]: https://docs.quic.tech/quiche/struct.Connection.html#method.stream_recv\n[HTTP/3 module]: https://docs.quic.tech/quiche/h3/index.html\n\nHave a look at the [quiche/examples/] directory for more complete examples on\nhow to use the quiche API, including examples on how to use quiche in C/C++\napplications (see below for more information).\n\n[examples/]: quiche/examples/\n\nCalling quiche from C/C++\n-------------------------\n\nquiche exposes a [thin C API] on top of the Rust API that can be used to more\neasily integrate quiche into C/C++ applications (as well as in other languages\nthat allow calling C APIs via some form of FFI). The C API follows the same\ndesign of the Rust one, modulo the constraints imposed by the C language itself.\n\nWhen running ``cargo build``, a static library called ``libquiche.a`` will be\nbuilt automatically alongside the Rust one. This is fully stand-alone and can\nbe linked directly into C/C++ applications.\n\nNote that in order to enable the FFI API, the ``ffi`` feature must be enabled (it\nis disabled by default), by passing ``--features ffi`` to ``cargo``.\n\n[thin C API]: https://github.com/cloudflare/quiche/blob/master/quiche/include/quiche.h\n\nBuilding\n--------\n\nquiche requires Rust 1.85 or later to build. The latest stable Rust release can\nbe installed using [rustup](https://rustup.rs/).\n\nOnce the Rust build environment is setup, the quiche source code can be fetched\nusing git:\n\n```bash\n $ git clone --recursive https://github.com/cloudflare/quiche\n```\n\nand then built using cargo:\n\n```bash\n $ cargo build --examples\n```\n\ncargo can also be used to run the testsuite:\n\n```bash\n $ cargo test\n```\n\nNote that [BoringSSL], which is used to implement QUIC's cryptographic handshake\nbased on TLS, needs to be built and linked to quiche. This is done automatically\nwhen building quiche using cargo, but requires the `cmake` command to be\navailable during the build process. On Windows you also need\n[NASM](https://www.nasm.us/). The [official BoringSSL\ndocumentation](https://github.com/google/boringssl/blob/master/BUILDING.md) has\nmore details.\n\nIn alternative you can use your own custom build of BoringSSL by configuring\nthe BoringSSL directory with the ``QUICHE_BSSL_PATH`` environment variable:\n\n```bash\n $ QUICHE_BSSL_PATH=\"/path/to/boringssl\" cargo build --examples\n```\n\nAlternatively you can use [OpenSSL/quictls]. To enable quiche to use this vendor\nthe ``openssl`` feature can be added to the ``--feature`` list. Be aware that\n``0-RTT`` is not supported if this vendor is used.\n\n[BoringSSL]: https://boringssl.googlesource.com/boringssl/\n\n[OpenSSL/quictls]: https://github.com/quictls/openssl\n\n### Building for Android\n\nBuilding quiche for Android (NDK version 19 or higher, 21 recommended), can be\ndone using [cargo-ndk] (v2.0 or later).\n\nFirst the [Android NDK] needs to be installed, either using Android Studio or\ndirectly, and the `ANDROID_NDK_HOME` environment variable needs to be set to the\nNDK installation path, e.g.:\n\n```bash\n $ export ANDROID_NDK_HOME=/usr/local/share/android-ndk\n```\n\nThen the Rust toolchain for the Android architectures needed can be installed as\nfollows:\n\n```bash\n $ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android\n```\n\nNote that the minimum API level is 21 for all target architectures.\n\n[cargo-ndk] (v2.0 or later) also needs to be installed:\n\n```bash\n $ cargo install cargo-ndk\n```\n\nFinally the quiche library can be built using the following procedure. Note that\nthe `-t <architecture>` and `-p <NDK version>` options are mandatory.\n\n```bash\n $ cargo ndk -t arm64-v8a -p 21 -- build --features ffi\n```\n\nSee [build_android_ndk19.sh] for more information.\n\n[Android NDK]: https://developer.android.com/ndk\n[cargo-ndk]: https://docs.rs/crate/cargo-ndk\n[build_android_ndk19.sh]: https://github.com/cloudflare/quiche/blob/master/tools/android/build_android_ndk19.sh\n\n### Building for iOS\n\nTo build quiche for iOS, you need the following:\n\n- Install Xcode command-line tools. You can install them with Xcode or with the\n  following command:\n\n```bash\n $ xcode-select --install\n```\n\n- Install the Rust toolchain for iOS architectures:\n\n```bash\n $ rustup target add aarch64-apple-ios x86_64-apple-ios\n```\n\n- Install `cargo-lipo`:\n\n```bash\n $ cargo install cargo-lipo\n```\n\nTo build libquiche, run the following command:\n\n```bash\n $ cargo lipo --features ffi\n```\n\nor\n\n```bash\n $ cargo lipo --features ffi --release\n```\n\niOS build is tested in Xcode 10.1 and Xcode 11.2.\n\n### Building Docker images\n\nIn order to build the Docker images, simply run the following command:\n\n```bash\n $ make docker-build\n```\n\nYou can find the quiche Docker images on the following Docker Hub repositories:\n\n- [cloudflare/quiche](https://hub.docker.com/repository/docker/cloudflare/quiche)\n- [cloudflare/quiche-qns](https://hub.docker.com/repository/docker/cloudflare/quiche-qns)\n\nThe `latest` tag will be updated whenever quiche master branch updates.\n\n**cloudflare/quiche**\n\nProvides a server and client installed in /usr/local/bin.\n\n**cloudflare/quiche-qns**\n\nProvides the script to test quiche within the [quic-interop-runner](https://github.com/marten-seemann/quic-interop-runner).\n\nDisclaimers and Notes\n---------\n\n⚠️ This repository includes a number of client and server example\napplications that are provided to demonstrate simple usage of the quiche library\nAPI. They are not intended to be used in production environments; no\nperformance, security or reliability guarantees are provided.\n\n\nCopyright\n---------\n\nCopyright (C) 2018-2019, Cloudflare, Inc.\n\nSee [COPYING] for the license.\n\n[COPYING]: https://github.com/cloudflare/quiche/tree/master/COPYING\n"
  },
  {
    "path": "RELEASING.md",
    "content": "Releasing\n=========\n\nThis document describes the process for cutting releases of the crates in this\nrepository.\n\nMajor, Minor and Patch releases\n-------------------------------\n\nBefore cutting a release it's important to decide whether a major or minor\nrelease needs to be created. This is important because creating major\n(incompatible) releases requires work in the dependent projects, while minor\nreleases don't, so we should avoid creating unnecessary work for users by using\nthe correct release type.\n\nAll crates in this repository currently use pre-1.0 version numbers, meaning\nthat bumping the minor component (the `Y` in `X.Y.Z`) is equivalent to doing a\nmajor release, while bumping the patch component (`Z`) is equivalent to a minor\nrelease.\n\nIn order to determine whether a major or minor release is necessary, the\n[cargo-semver-checks](https://crates.io/crates/cargo-semver-checks) tool can be\nused to detect API-breaking changes:\n\n```\n $ cargo semver-checks -p <crate>\n```\n\nHowever sometimes a major release needs to be cut even if semver-checks doesn't\ndetect an API change, for example in case of major behaviour changes in the\ncrate that might affect applications using it.\n\nChecking dependencies\n---------------------\n\nIt's also important to ensure the crate to be released can be built outside of\nthe repository, meaning that all required dependencies have also been released\nand published already, otherwise there is a risk of cutting a release that can't\nthen be published to crates.io:\n\n```\n $ cargo package -p <crate>\n```\n\nCreating a release\n------------------\n\nA release can now be created using the `cargo release` command:\n\n```\n $ git checkout -b release-x.y.z\n $ cargo release --no-push --no-publish --no-tag -x <patch|minor> -p <crate>\n```\n\nThe `release-x.y.z` branch can then be pushed to GitHub and a pull request\nopened.\n\nNote that because GitHub will rebase the release commit on merging the PR, we\nuse the `--no-tag` option to avoid tagging the release at this point. The tag\nshould only be created once the release PR is merged as follows:\n\n```\n $ git tag <crate>-<version>\n```\n\ne.g. `git tag tokio-quiche-0.6.0`.\n\nFor historical reasons, releases of quiche itself do not have the name of the\ncrate in the tag (so would only be `0.6.0` in the example above).\n\nPublishing the release\n----------------------\n\nFinally, the new release needs to be published to crates.io:\n\n```\n $ cargo publish -p <crate>\n```\n\nIt's good practice to check that the release then gets successfully published on\n[crates.io](https://crates.io/) and that the documentation is correctly built\nand published to [docs.rs](https://docs.rs) (this might take a few minutes), in\ncase bad changes slipped through the process and caused the crate to not be\npublished correctly.\n\nRelease notes\n-------------\n\nWe currently only provide release notes for the quiche crate itself, via GitHub\nreleases https://github.com/cloudflare/quiche/releases\n\nRelease notes should not use the raw list of git commits, as that usually\nincludes a number of commits that don't really need to appear in the notes as\nthey are not useful for users to know (e.g. internal refactoring, test fixes,\nwarnings/clippy/formatting fixes, ...), and commit messages alone aren't\nnecessarily understandble by users of quiche anyway.\n\nOnly the more important *user visible* changes should be listed, with a brief\ndescription of the change and potentially links to docs.rs (e.g. for new APIs).\n\nAdditionally, breaking changes and security fixes should be clearly marked as\nsuch as they are generally the ones that most require users' attention (see e.g.\n[this one](https://github.com/cloudflare/quiche/releases/tag/0.24.0) for breaking\nchanges, and [this one](https://github.com/cloudflare/quiche/releases/tag/0.24.4)\nfor security fixes, or look for \"breaking changes\" and \"security\" in the list\nof releases linked above).\n\nIt's especially important to note what changes an application might need to do\nin case of breaking changes, so that users can more easily take action.\n\nThe emoji in the release name is optional, but it's the only thing close to\n\"fun\" when doing a release. You can pick one randomly or one that is related\nin one way or another to the contents of the release.\n\nDon't forget to pick the appropriate tag for the release in the dropdown menu,\nas well as marking the new release as the latest one.\n"
  },
  {
    "path": "apps/Cargo.toml",
    "content": "[package]\nname = \"quiche_apps\"\nversion = \"0.1.0\"\nauthors = [\"Lucas Pardue <lucaspardue.24.7@gmail.com>\"]\nedition = { workspace = true }\nrepository = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\npublish = false\n\n[features]\n# Enable quiche's fuzzing mode.\nfuzzing = [\"quiche/fuzzing\"]\n\n# Enable qlog support.\nqlog = [\"quiche/qlog\"]\n\n# Use BoringSSL provided by the boring crate.\nboringssl-boring-crate = [\"quiche/boringssl-boring-crate\"]\n\n# Enable sfv support.\nsfv = [\"quiche/sfv\"]\n\ndefault = [\"qlog\", \"sfv\"]\n\n[dependencies]\ndocopt = \"1\"\nenv_logger = { workspace = true }\nlibc = { workspace = true }\nlog = { workspace = true }\nmio = { workspace = true, features = [\"net\", \"os-poll\"] }\nnix = { workspace = true, features = [\"net\", \"socket\", \"uio\"] }\noctets = { workspace = true }\nquiche = { workspace = true }\nring = { workspace = true }\nurl = { workspace = true }\n\n[lib]\ncrate-type = [\"lib\"]\n"
  },
  {
    "path": "apps/run_endpoint.sh",
    "content": "#!/bin/bash\nset -e\n\n# Set up the routing needed for the simulation\n/setup.sh\n\n# The following variables are available for use:\n# - ROLE contains the role of this execution context, client or server\n# - SERVER_PARAMS contains user-supplied command line parameters\n# - CLIENT_PARAMS contains user-supplied command line parameters\n\nQUICHE_DIR=/quiche\nWWW_DIR=/www\nDOWNLOAD_DIR=/downloads\nQUICHE_CLIENT=quiche-client\nQUICHE_SERVER=quiche-server\nQUICHE_CLIENT_OPT=\"--no-verify --dump-responses ${DOWNLOAD_DIR} --wire-version 1 --max-active-cids 8\"\n# interop container has tso off. need to disable gso as well.\nQUICHE_SERVER_OPT_COMMON=\"--listen [::]:443 --root $WWW_DIR --cert /certs/cert.pem --key /certs/priv.key --enable-active-migration --max-active-cids 8 --disable-gso --disable-pacing\"\nQUICHE_SERVER_OPT=\"$QUICHE_SERVER_OPT_COMMON --no-retry \"\nLOG_DIR=/logs\nLOG=$LOG_DIR/log.txt\n\ncheck_testcase () {\n    case $1 in\n        handshake | multiconnect | http3 )\n            echo \"supported\"\n            ;;\n\n        transfer )\n            echo \"supported\"\n            ;;\n\n        chacha20 )\n            if [ \"$ROLE\" == \"client\" ]; then\n                # We don't support selecting a cipher on the client-side.\n                echo \"unsupported\"\n                exit 127\n            elif [ \"$ROLE\" == \"server\" ]; then\n                echo \"supported\"\n            fi\n            ;;\n\n        resumption )\n            echo \"supported\"\n            QUICHE_CLIENT_OPT=\"$QUICHE_CLIENT_OPT --session-file=session.bin\"\n            ;;\n\n        zerortt )\n            if [ \"$ROLE\" == \"client\" ]; then\n                echo \"supported\"\n                QUICHE_CLIENT_OPT=\"$QUICHE_CLIENT_OPT --session-file=session.bin --early-data\"\n            elif [ \"$ROLE\" == \"server\" ]; then\n                echo \"supported\"\n                QUICHE_SERVER_OPT=\"$QUICHE_SERVER_OPT --early-data\"\n            fi\n            ;;\n\n        retry )\n            echo \"supported\"\n            QUICHE_SERVER_OPT=\"$QUICHE_SERVER_OPT_COMMON\"\n            ;;\n\n        *)\n            echo \"unsupported\"\n            exit 127\n            ;;\n\n    esac\n}\n\nrun_quiche_client_tests () {\n    case $1 in\n        multiconnect )\n            for req in $REQUESTS\n            do\n                $QUICHE_DIR/$QUICHE_CLIENT $QUICHE_CLIENT_OPT \\\n                    $CLIENT_PARAMS $req >> $LOG 2>&1\n            done\n            ;;\n\n        resumption | zerortt )\n            REQS=($REQUESTS)\n\n            # Run first request in 1-RTT to establish session.\n            FIRST_REQUEST=${REQS[0]}\n\n            $QUICHE_DIR/$QUICHE_CLIENT $QUICHE_CLIENT_OPT \\\n                $CLIENT_PARAMS $FIRST_REQUEST >> $LOG 2>&1\n\n            # Run remaining requests in resumed connection.\n            REMAINING_REQUESTS=${REQS[@]:1}\n\n            $QUICHE_DIR/$QUICHE_CLIENT $QUICHE_CLIENT_OPT \\\n                $CLIENT_PARAMS $REMAINING_REQUESTS >& $LOG 2>&1\n\n            ;;\n\n        *)\n            $QUICHE_DIR/$QUICHE_CLIENT $QUICHE_CLIENT_OPT \\\n                $CLIENT_PARAMS $REQUESTS >& $LOG\n            ;;\n\n    esac\n}\n\nrun_quiche_server_tests() {\n    $QUICHE_DIR/$QUICHE_SERVER $SERVER_PARAMS $QUICHE_SERVER_OPT >& $LOG\n}\n\n# Update config based on test case\ncheck_testcase $TESTCASE\n\n# Create quiche log directory\nmkdir -p $LOG_DIR\n\nif [ \"$ROLE\" == \"client\" ]; then\n    # Wait for the simulator to start up.\n    wait-for-it sim:57832 -s -t 30\n    echo \"## Starting quiche client...\"\n    echo \"## Client params: $CLIENT_PARAMS\"\n    echo \"## Requests: $REQUESTS\"\n    echo \"## Test case: $TESTCASE\"\n    run_quiche_client_tests $TESTCASE\nelif [ \"$ROLE\" == \"server\" ]; then\n    echo \"## Starting quiche server...\"\n    echo \"## Server params: $SERVER_PARAMS\"\n    echo \"## Test case: $TESTCASE\"\n    run_quiche_server_tests\nfi\n"
  },
  {
    "path": "apps/src/args.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::common::alpns;\nuse std::time::Duration;\n\npub trait Args {\n    fn with_docopt(docopt: &docopt::Docopt) -> Self;\n}\n\n/// Contains commons arguments for creating a quiche QUIC connection.\npub struct CommonArgs {\n    pub alpns: Vec<&'static [u8]>,\n    pub max_data: u64,\n    pub max_window: u64,\n    pub max_stream_data: u64,\n    pub max_stream_window: u64,\n    pub max_streams_bidi: u64,\n    pub max_streams_uni: u64,\n    pub idle_timeout: u64,\n    pub early_data: bool,\n    pub dump_packet_path: Option<String>,\n    pub no_grease: bool,\n    pub cc_algorithm: String,\n    pub disable_hystart: bool,\n    pub dgrams_enabled: bool,\n    pub dgram_count: u64,\n    pub dgram_data: String,\n    pub max_active_cids: u64,\n    pub enable_active_migration: bool,\n    pub max_field_section_size: Option<u64>,\n    pub qpack_max_table_capacity: Option<u64>,\n    pub qpack_blocked_streams: Option<u64>,\n    pub initial_rtt: Duration,\n    pub initial_cwnd_packets: u64,\n}\n\n/// Creates a new `CommonArgs` structure using the provided [`Docopt`].\n///\n/// The `Docopt` usage String needs to include the following:\n///\n/// --http-version VERSION      HTTP version to use.\n/// --max-data BYTES            Connection-wide flow control limit.\n/// --max-window BYTES          Connection-wide max receiver window.\n/// --max-stream-data BYTES     Per-stream flow control limit.\n/// --max-stream-window BYTES   Per-stream max receiver window.\n/// --max-streams-bidi STREAMS  Number of allowed concurrent streams.\n/// --max-streams-uni STREAMS   Number of allowed concurrent streams.\n/// --dump-packets PATH         Dump the incoming packets in PATH.\n/// --no-grease                 Don't send GREASE.\n/// --cc-algorithm NAME         Set a congestion control algorithm.\n/// --disable-hystart           Disable HyStart++.\n/// --dgram-proto PROTO         DATAGRAM application protocol.\n/// --dgram-count COUNT         Number of DATAGRAMs to send.\n/// --dgram-data DATA           DATAGRAM data to send.\n/// --max-active-cids NUM       Maximum number of active Connection IDs.\n/// --enable-active-migration   Enable active connection migration.\n/// --max-field-section-size BYTES  Max size of uncompressed field section.\n/// --qpack-max-table-capacity BYTES  Max capacity of dynamic QPACK decoding.\n/// --qpack-blocked-streams STREAMS  Limit of blocked streams while decoding.\n/// --initial-cwnd-packets      Size of initial congestion window, in packets.\n///\n/// [`Docopt`]: https://docs.rs/docopt/1.1.0/docopt/\nimpl Args for CommonArgs {\n    fn with_docopt(docopt: &docopt::Docopt) -> Self {\n        let args = docopt.parse().unwrap_or_else(|e| e.exit());\n\n        let http_version = args.get_str(\"--http-version\");\n        let dgram_proto = args.get_str(\"--dgram-proto\");\n        let (alpns, dgrams_enabled) = match (http_version, dgram_proto) {\n            (\"HTTP/0.9\", \"none\") => (alpns::HTTP_09.to_vec(), false),\n\n            (\"HTTP/0.9\", _) => {\n                panic!(\"Unsupported HTTP version and DATAGRAM protocol.\")\n            },\n\n            (\"HTTP/3\", \"none\") => (alpns::HTTP_3.to_vec(), false),\n\n            (\"HTTP/3\", \"oneway\") => (alpns::HTTP_3.to_vec(), true),\n\n            (\"all\", \"none\") => (\n                [alpns::HTTP_3.as_slice(), &alpns::HTTP_09]\n                    .concat()\n                    .to_vec(),\n                false,\n            ),\n\n            (..) => panic!(\"Unsupported HTTP version and DATAGRAM protocol.\"),\n        };\n\n        let dgram_count = args.get_str(\"--dgram-count\");\n        let dgram_count = dgram_count.parse::<u64>().unwrap();\n\n        let dgram_data = args.get_str(\"--dgram-data\").to_string();\n\n        let max_data = args.get_str(\"--max-data\");\n        let max_data = max_data.parse::<u64>().unwrap();\n\n        let max_window = args.get_str(\"--max-window\");\n        let max_window = max_window.parse::<u64>().unwrap();\n\n        let max_stream_data = args.get_str(\"--max-stream-data\");\n        let max_stream_data = max_stream_data.parse::<u64>().unwrap();\n\n        let max_stream_window = args.get_str(\"--max-stream-window\");\n        let max_stream_window = max_stream_window.parse::<u64>().unwrap();\n\n        let max_streams_bidi = args.get_str(\"--max-streams-bidi\");\n        let max_streams_bidi = max_streams_bidi.parse::<u64>().unwrap();\n\n        let max_streams_uni = args.get_str(\"--max-streams-uni\");\n        let max_streams_uni = max_streams_uni.parse::<u64>().unwrap();\n\n        let idle_timeout = args.get_str(\"--idle-timeout\");\n        let idle_timeout = idle_timeout.parse::<u64>().unwrap();\n\n        let early_data = args.get_bool(\"--early-data\");\n\n        let dump_packet_path = if !args.get_str(\"--dump-packets\").is_empty() {\n            Some(args.get_str(\"--dump-packets\").to_string())\n        } else {\n            None\n        };\n\n        let no_grease = args.get_bool(\"--no-grease\");\n\n        let cc_algorithm = args.get_str(\"--cc-algorithm\");\n\n        let disable_hystart = args.get_bool(\"--disable-hystart\");\n\n        let max_active_cids = args.get_str(\"--max-active-cids\");\n        let max_active_cids = max_active_cids.parse::<u64>().unwrap();\n\n        let enable_active_migration = args.get_bool(\"--enable-active-migration\");\n\n        let max_field_section_size =\n            if !args.get_str(\"--max-field-section-size\").is_empty() {\n                Some(\n                    args.get_str(\"--max-field-section-size\")\n                        .parse::<u64>()\n                        .unwrap(),\n                )\n            } else {\n                None\n            };\n\n        let qpack_max_table_capacity =\n            if !args.get_str(\"--qpack-max-table-capacity\").is_empty() {\n                Some(\n                    args.get_str(\"--qpack-max-table-capacity\")\n                        .parse::<u64>()\n                        .unwrap(),\n                )\n            } else {\n                None\n            };\n\n        let qpack_blocked_streams =\n            if !args.get_str(\"--qpack-blocked-streams\").is_empty() {\n                Some(\n                    args.get_str(\"--qpack-blocked-streams\")\n                        .parse::<u64>()\n                        .unwrap(),\n                )\n            } else {\n                None\n            };\n\n        let initial_rtt_millis =\n            args.get_str(\"--initial-rtt\").parse::<u64>().unwrap();\n        let initial_rtt = Duration::from_millis(initial_rtt_millis);\n\n        let initial_cwnd_packets = args\n            .get_str(\"--initial-cwnd-packets\")\n            .parse::<u64>()\n            .unwrap();\n\n        CommonArgs {\n            alpns,\n            max_data,\n            max_window,\n            max_stream_data,\n            max_stream_window,\n            max_streams_bidi,\n            max_streams_uni,\n            idle_timeout,\n            early_data,\n            dump_packet_path,\n            no_grease,\n            cc_algorithm: cc_algorithm.to_string(),\n            disable_hystart,\n            dgrams_enabled,\n            dgram_count,\n            dgram_data,\n            max_active_cids,\n            enable_active_migration,\n            max_field_section_size,\n            qpack_max_table_capacity,\n            qpack_blocked_streams,\n            initial_rtt,\n            initial_cwnd_packets,\n        }\n    }\n}\n\nimpl Default for CommonArgs {\n    fn default() -> Self {\n        CommonArgs {\n            alpns: alpns::HTTP_3.to_vec(),\n            max_data: 10000000,\n            max_window: 25165824,\n            max_stream_data: 1000000,\n            max_stream_window: 16777216,\n            max_streams_bidi: 100,\n            max_streams_uni: 100,\n            idle_timeout: 30000,\n            early_data: false,\n            dump_packet_path: None,\n            no_grease: false,\n            cc_algorithm: \"cubic\".to_string(),\n            disable_hystart: false,\n            dgrams_enabled: false,\n            dgram_count: 0,\n            dgram_data: \"quack\".to_string(),\n            max_active_cids: 2,\n            enable_active_migration: false,\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            initial_rtt: Duration::from_millis(333),\n            initial_cwnd_packets: 10,\n        }\n    }\n}\n\npub const CLIENT_USAGE: &str = \"Usage:\n  quiche-client [options] URL...\n  quiche-client -h | --help\n\nOptions:\n  --method METHOD          Use the given HTTP request method [default: GET].\n  --body FILE              Send the given file as request body.\n  --max-data BYTES         Connection-wide flow control limit [default: 10000000].\n  --max-window BYTES       Connection-wide max receiver window [default: 25165824].\n  --max-stream-data BYTES  Per-stream flow control limit [default: 1000000].\n  --max-stream-window BYTES   Per-stream max receiver window [default: 16777216].\n  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].\n  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].\n  --idle-timeout TIMEOUT   Idle timeout in milliseconds [default: 30000].\n  --wire-version VERSION   The version number to send to the server [default: babababa].\n  --http-version VERSION   HTTP version to use [default: all].\n  --early-data             Enable sending early data.\n  --dgram-proto PROTO      DATAGRAM application protocol to use [default: none].\n  --dgram-count COUNT      Number of DATAGRAMs to send [default: 0].\n  --dgram-data DATA        Data to send for certain types of DATAGRAM application protocol [default: quack].\n  --dump-packets PATH      Dump the incoming packets as files in the given directory.\n  --dump-responses PATH    Dump response payload as files in the given directory.\n  --dump-json              Dump response headers and payload to stdout in JSON format.\n  --max-json-payload BYTES  Per-response payload limit when dumping JSON [default: 10000].\n  --connect-to ADDRESS     Override the server's address.\n  --no-verify              Don't verify server's certificate.\n  --trust-origin-ca-pem <file>  Path to the pem file of the origin's CA, if not publicly trusted.\n  --no-grease              Don't send GREASE.\n  --cc-algorithm NAME      Specify which congestion control algorithm to use [default: cubic].\n  --disable-hystart        Disable HyStart++.\n  --max-active-cids NUM    The maximum number of active Connection IDs we can support [default: 2].\n  --enable-active-migration   Enable active connection migration.\n  --perform-migration      Perform connection migration on another source port.\n  -H --header HEADER ...   Add a request header.\n  -n --requests REQUESTS   Send the given number of identical requests [default: 1].\n  --send-priority-update   Send HTTP/3 priority updates if the query string params 'u' or 'i' are present in URLs\n  --max-field-section-size BYTES    Max size of uncompressed field section. Default is unlimited.\n  --qpack-max-table-capacity BYTES  Max capacity of dynamic QPACK decoding.. Any value other that 0 is currently unsupported.\n  --qpack-blocked-streams STREAMS   Limit of blocked streams while decoding. Any value other that 0 is currently unsupported.\n  --session-file PATH      File used to cache a TLS session for resumption.\n  --source-port PORT       Source port to use when connecting to the server [default: 0].\n  --initial-rtt MILLIS     The initial RTT in milliseconds [default: 333].\n  --initial-cwnd-packets PACKETS   The initial congestion window size in terms of packet count [default: 10].\n  -h --help                Show this screen.\n\";\n\n/// Application-specific arguments that compliment the `CommonArgs`.\npub struct ClientArgs {\n    pub version: u32,\n    pub dump_response_path: Option<String>,\n    pub dump_json: Option<usize>,\n    pub urls: Vec<url::Url>,\n    pub reqs_cardinal: u64,\n    pub req_headers: Vec<String>,\n    pub no_verify: bool,\n    pub trust_origin_ca_pem: Option<String>,\n    pub body: Option<Vec<u8>>,\n    pub method: String,\n    pub connect_to: Option<String>,\n    pub session_file: Option<String>,\n    pub source_port: u16,\n    pub perform_migration: bool,\n    pub send_priority_update: bool,\n}\n\nimpl Args for ClientArgs {\n    fn with_docopt(docopt: &docopt::Docopt) -> Self {\n        let args = docopt.parse().unwrap_or_else(|e| e.exit());\n\n        let version = args.get_str(\"--wire-version\");\n        let version = u32::from_str_radix(version, 16).unwrap();\n\n        let dump_response_path = if !args.get_str(\"--dump-responses\").is_empty() {\n            Some(args.get_str(\"--dump-responses\").to_string())\n        } else {\n            None\n        };\n\n        let dump_json = args.get_bool(\"--dump-json\");\n        let dump_json = if dump_json {\n            let max_payload = args.get_str(\"--max-json-payload\");\n            let max_payload = max_payload.parse::<usize>().unwrap();\n            Some(max_payload)\n        } else {\n            None\n        };\n\n        // URLs (can be multiple).\n        let urls: Vec<url::Url> = args\n            .get_vec(\"URL\")\n            .into_iter()\n            .map(|x| url::Url::parse(x).unwrap())\n            .collect();\n\n        // Request headers (can be multiple).\n        let req_headers = args\n            .get_vec(\"--header\")\n            .into_iter()\n            .map(|x| x.to_string())\n            .collect();\n\n        let reqs_cardinal = args.get_str(\"--requests\");\n        let reqs_cardinal = reqs_cardinal.parse::<u64>().unwrap();\n\n        let no_verify = args.get_bool(\"--no-verify\");\n\n        let trust_origin_ca_pem = args.get_str(\"--trust-origin-ca-pem\");\n        let trust_origin_ca_pem = if !trust_origin_ca_pem.is_empty() {\n            Some(trust_origin_ca_pem.to_string())\n        } else {\n            None\n        };\n\n        let body = if args.get_bool(\"--body\") {\n            std::fs::read(args.get_str(\"--body\")).ok()\n        } else {\n            None\n        };\n\n        let method = args.get_str(\"--method\").to_string();\n\n        let connect_to = if args.get_bool(\"--connect-to\") {\n            Some(args.get_str(\"--connect-to\").to_string())\n        } else {\n            None\n        };\n\n        let session_file = if args.get_bool(\"--session-file\") {\n            Some(args.get_str(\"--session-file\").to_string())\n        } else {\n            None\n        };\n\n        let source_port = args.get_str(\"--source-port\");\n        let source_port = source_port.parse::<u16>().unwrap();\n\n        let perform_migration = args.get_bool(\"--perform-migration\");\n\n        let send_priority_update = args.get_bool(\"--send-priority-update\");\n\n        ClientArgs {\n            version,\n            dump_response_path,\n            dump_json,\n            urls,\n            reqs_cardinal,\n            req_headers,\n            no_verify,\n            trust_origin_ca_pem,\n            body,\n            method,\n            connect_to,\n            session_file,\n            source_port,\n            perform_migration,\n            send_priority_update,\n        }\n    }\n}\n\nimpl Default for ClientArgs {\n    fn default() -> Self {\n        ClientArgs {\n            version: 0xbabababa,\n            dump_response_path: None,\n            dump_json: None,\n            urls: vec![],\n            req_headers: vec![],\n            reqs_cardinal: 1,\n            no_verify: false,\n            trust_origin_ca_pem: None,\n            body: None,\n            method: \"GET\".to_string(),\n            connect_to: None,\n            session_file: None,\n            source_port: 0,\n            perform_migration: false,\n            send_priority_update: false,\n        }\n    }\n}\n\npub const SERVER_USAGE: &str = \"Usage:\n  quiche-server [options]\n  quiche-server -h | --help\n\nOptions:\n  --listen <addr>             Listen on the given IP:port [default: 127.0.0.1:4433]\n  --cert <file>               TLS certificate path [default: src/bin/cert.crt]\n  --key <file>                TLS certificate key path [default: src/bin/cert.key]\n  --root <dir>                Root directory [default: src/bin/root/]\n  --index <name>              The file that will be used as index [default: index.html].\n  --name <str>                Name of the server [default: quic.tech]\n  --max-data BYTES            Connection-wide flow control limit [default: 10000000].\n  --max-window BYTES          Connection-wide max receiver window [default: 25165824].\n  --max-stream-data BYTES     Per-stream flow control limit [default: 1000000].\n  --max-stream-window BYTES   Per-stream max receiver window [default: 16777216].\n  --max-streams-bidi STREAMS  Number of allowed concurrent streams [default: 100].\n  --max-streams-uni STREAMS   Number of allowed concurrent streams [default: 100].\n  --idle-timeout TIMEOUT      Idle timeout in milliseconds [default: 30000].\n  --dump-packets PATH         Dump the incoming packets as files in the given directory.\n  --early-data                Enable receiving early data.\n  --no-retry                  Disable stateless retry.\n  --no-grease                 Don't send GREASE.\n  --http-version VERSION      HTTP version to use [default: all].\n  --dgram-proto PROTO         DATAGRAM application protocol to use [default: none].\n  --dgram-count COUNT         Number of DATAGRAMs to send [default: 0].\n  --dgram-data DATA           Data to send for certain types of DATAGRAM application protocol [default: brrr].\n  --cc-algorithm NAME         Specify which congestion control algorithm to use [default: cubic].\n  --disable-hystart           Disable HyStart++.\n  --max-active-cids NUM       The maximum number of active Connection IDs we can support [default: 2].\n  --enable-active-migration   Enable active connection migration.\n  --max-field-section-size BYTES    Max size of uncompressed HTTP/3 field section. Default is unlimited.\n  --qpack-max-table-capacity BYTES  Max capacity of QPACK dynamic table decoding. Any value other that 0 is currently unsupported.\n  --qpack-blocked-streams STREAMS   Limit of streams that can be blocked while decoding. Any value other that 0 is currently unsupported.\n  --disable-gso               Disable GSO (linux only).\n  --disable-pacing            Disable pacing (linux only).\n  --initial-rtt MILLIS     The initial RTT in milliseconds [default: 333].\n  --initial-cwnd-packets PACKETS      The initial congestion window size in terms of packet count [default: 10].\n  -h --help                   Show this screen.\n\";\n\n// Application-specific arguments that compliment the `CommonArgs`.\npub struct ServerArgs {\n    pub listen: String,\n    pub no_retry: bool,\n    pub root: String,\n    pub index: String,\n    pub cert: String,\n    pub key: String,\n    pub disable_gso: bool,\n    pub disable_pacing: bool,\n    pub enable_pmtud: bool,\n}\n\nimpl Args for ServerArgs {\n    fn with_docopt(docopt: &docopt::Docopt) -> Self {\n        let args = docopt.parse().unwrap_or_else(|e| e.exit());\n\n        let listen = args.get_str(\"--listen\").to_string();\n        let no_retry = args.get_bool(\"--no-retry\");\n        let root = args.get_str(\"--root\").to_string();\n        let index = args.get_str(\"--index\").to_string();\n        let cert = args.get_str(\"--cert\").to_string();\n        let key = args.get_str(\"--key\").to_string();\n        let disable_gso = args.get_bool(\"--disable-gso\");\n        let disable_pacing = args.get_bool(\"--disable-pacing\");\n        let enable_pmtud = args.get_bool(\"--enable-pmtud\");\n\n        ServerArgs {\n            listen,\n            no_retry,\n            root,\n            index,\n            cert,\n            key,\n            disable_gso,\n            disable_pacing,\n            enable_pmtud,\n        }\n    }\n}\n"
  },
  {
    "path": "apps/src/bin/cert.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDkzCCAnugAwIBAgIUaj26Dyzr2W9R8juKm2pNyrtati0wDQYJKoZIhvcNAQEL\nBQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJcXVpYy50ZWNoMB4X\nDTE4MDkzMDIyMTE0OFoXDTE5MDkzMDIyMTE0OFowWTELMAkGA1UEBhMCQVUxEzAR\nBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5\nIEx0ZDESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAqrS30fnkI6Q+5SKsBXkIwnhO61x/Wgt0zo5P+0yTAZDYVYtEhRlf\nmJ3esEleO1nq5MtM3d+6aVBJlwtTi8pBOzVfJklnxd07N3rKh3HZbGHybjhJFGT9\nU4sUrcKcCpSKJaEu7IQsQQs1Hh0B67MeqJG3F7OcYCF3OXC11WK3CtDDKcLcsa2x\n+WImzsPfayzEjQ4ELTVDP73oQGR6D3HaWauKES4JjI9CMn8EJRCcxjwet+c4U3kQ\ng2z5KDbooBfCfrzmX3/EpMf/RaASaUtZF3kgfDT648dICWUoiparo1V73pg2vDe5\nRsAp4n1A7VCY48VvGEz9Qgcp8QFztpFJnwIDAQABo1MwUTAdBgNVHQ4EFgQUFOlS\nIeYH/41CN5BP/8w8F3e/fkYwHwYDVR0jBBgwFoAUFOlSIeYH/41CN5BP/8w8F3e/\nfkYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAZa7XK3My4Jpe\nSLz0BAj44QtghGdg98QFR3iZEnn0XC09HrkhbjaR8Ma3dn0QyMDRuPLLNl5j3VWu\nrDqngENbuJJBPGkCTzozFfMU6MZzGLK1ljIiGzkMXVEaamSj7GDJ2eR2i2cBugiM\nYv7N/e8FbSMRBXoYVPjukoA8QwDJhS/oN47vt0+VsTi5wah9d3t0RCruAe/4TETo\njPxjbEGTQ71dmU66xPZMrnqlGCNa4kN2alCDNfSg1yRp4j10zSmK0jHEHOuiHliW\n/Zc+aLEFcVB1QHmIyvcBIhKiuDbfbkWrqSiel6nLScIvhJaJOrGzQYBfjeZ4TO0m\nIHJUojcgZA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "apps/src/bin/cert.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCqtLfR+eQjpD7l\nIqwFeQjCeE7rXH9aC3TOjk/7TJMBkNhVi0SFGV+Ynd6wSV47Werky0zd37ppUEmX\nC1OLykE7NV8mSWfF3Ts3esqHcdlsYfJuOEkUZP1TixStwpwKlIoloS7shCxBCzUe\nHQHrsx6okbcXs5xgIXc5cLXVYrcK0MMpwtyxrbH5YibOw99rLMSNDgQtNUM/vehA\nZHoPcdpZq4oRLgmMj0IyfwQlEJzGPB635zhTeRCDbPkoNuigF8J+vOZff8Skx/9F\noBJpS1kXeSB8NPrjx0gJZSiKlqujVXvemDa8N7lGwCnifUDtUJjjxW8YTP1CBynx\nAXO2kUmfAgMBAAECggEAdWR0KT1NS8luy0qtu9HBWWM8+pSQq87HFClADZRaYDBI\n5YMxqsqJOD4Q33CFEhHC/HZmtQpfen8RLINIgBCmDV6lwYGnkKWUTJHv53c+y08M\nVgn1D8Zng+VYYio7/vapjjkrONGoUU6wx7WxFXMHuWsD25PUDTPWdrTxBv6s3A0X\nLe7UtuCdo/xNY4YS6S64SfiEPsBddj1NhoiwOHkXekpNRoAwnizjngubEkiznScu\ngwKCW4nPV8y4CoIYyncGayrKieg03llgRngFiGJKpKeyL2UkX07Fqb2tXuJ36+RA\n9DrluEkYWZCjOS+aaQu+NwxCkUV5pq+HcXQmF5VX+QKBgQDTrgF4sKwcIjm+k3Fp\nbqhMS5stuSQJVn85fCIeQLq3u5DRq9n+UOvq6GvdEXz0SiupLfkXx/pDwiOux2sn\nCcwMaPqWbFE4mSsCFCBkL/PvXSzH2zYesHOplztvcV+gexAjmoCikMBCcM00QpN1\nGScUmQGTk/7BKJYGnVchJOXbfQKBgQDOcoZryCDxUPsg2ZMwkrnpcM+fSTT1gcgf\nI3gbGohagiXVTDU4+S7I7WmsJv+lBUJCWRG0p8JJZb0NsgnrGyOfCKL59xAV5PyT\nxSXMIi2+OH+fQXblII76GqWCs7A7NxtEU2geSy4ePPzSS4G81FN2oeV1OxZ9a6fk\n6cFIzmqsSwKBgQDIBQlg6NiI8RJNcXdeH/EpvtuQNfzGUhR/1jtLCPEmgjcS2Odx\nNzflzd92knrXP2rIPye7//wMoNsk4UzwI4LLSztWfl21NI5+NVRyNxmyWgHhi9M0\n5pk0bDH+WUv6Ea8rZWgdtNfnMD3HHw3FPZI/FWF2+QZlsRsqfuyA5iPI5QKBgQCu\nD7F2Po5H6FdUIx4O3icRw6PKURbtyDbKykUB1SUR6pmrdU2Kc84WatWl6Fuy7vQm\nrKJZBviwma8EVRA3wfIOrGF9D+noC+FJVffAXTDkKQ6xX6i3FvR1uvHBeW8k/hln\nSkuG/ywrIpCnXjJM21hjtayZYvBbXuF4B/6HPEKEcQKBgQC+DVoOVjsoyd9udTcp\n1v2xvwRVvU/OrPOLXwac1IbTgmb5FJYd8EZI0hdxJhialoTK3OONk04uxdn5tlAB\nQwKBmkXZEr9EIreMp18gbzmDGalx8UcS0j+nIZvmpZXWsIimAKDGEwFc8w+NAN5a\nX5UkSGjM6dnJocH0sLI7hXuVJw==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "apps/src/bin/quiche-client.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse quiche_apps::args::*;\n\nuse quiche_apps::common::*;\n\nuse quiche_apps::client::*;\n\nfn main() {\n    env_logger::builder().format_timestamp_nanos().init();\n\n    // Parse CLI parameters.\n    let docopt = docopt::Docopt::new(CLIENT_USAGE).unwrap();\n    let conn_args = CommonArgs::with_docopt(&docopt);\n    let args = ClientArgs::with_docopt(&docopt);\n\n    match connect(args, conn_args, stdout_sink) {\n        Err(ClientError::HandshakeFail) => std::process::exit(-1),\n\n        Err(ClientError::HttpFail) => std::process::exit(-2),\n\n        Err(ClientError::Other(e)) => panic!(\"{}\", e),\n\n        Ok(_) => (),\n    }\n}\n"
  },
  {
    "path": "apps/src/bin/quiche-server.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[macro_use]\nextern crate log;\n\nuse std::io;\n\nuse std::net;\n\nuse std::io::prelude::*;\n\nuse std::collections::HashMap;\n\nuse std::convert::TryFrom;\n\nuse std::rc::Rc;\n\nuse std::cell::RefCell;\n\nuse ring::rand::*;\n\nuse quiche_apps::args::*;\n\nuse quiche_apps::common::*;\n\nuse quiche_apps::sendto::*;\n\nconst MAX_BUF_SIZE: usize = 65507;\n\nconst MAX_DATAGRAM_SIZE: usize = 1350;\n\nfn main() {\n    let mut buf = [0; MAX_BUF_SIZE];\n    let mut out = [0; MAX_BUF_SIZE];\n    let mut pacing = false;\n\n    env_logger::builder().format_timestamp_nanos().init();\n\n    // Parse CLI parameters.\n    let docopt = docopt::Docopt::new(SERVER_USAGE).unwrap();\n    let conn_args = CommonArgs::with_docopt(&docopt);\n    let args = ServerArgs::with_docopt(&docopt);\n\n    // Setup the event loop.\n    let mut poll = mio::Poll::new().unwrap();\n    let mut events = mio::Events::with_capacity(1024);\n\n    // Create the UDP listening socket, and register it with the event loop.\n    let mut socket =\n        mio::net::UdpSocket::bind(args.listen.parse().unwrap()).unwrap();\n\n    // Set SO_TXTIME socket option on the listening UDP socket for pacing\n    // outgoing packets.\n    if !args.disable_pacing {\n        match set_txtime_sockopt(&socket) {\n            Ok(_) => {\n                pacing = true;\n                debug!(\"successfully set SO_TXTIME socket option\");\n            },\n            Err(e) => debug!(\"setsockopt failed {e:?}\"),\n        };\n    }\n\n    info!(\"listening on {:}\", socket.local_addr().unwrap());\n\n    poll.registry()\n        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)\n        .unwrap();\n\n    let max_datagram_size = MAX_DATAGRAM_SIZE;\n    let enable_gso = if args.disable_gso {\n        false\n    } else {\n        detect_gso(&socket, max_datagram_size)\n    };\n\n    trace!(\"GSO detected: {enable_gso}\");\n\n    // Create the configuration for the QUIC connections.\n    let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n\n    config.load_cert_chain_from_pem_file(&args.cert).unwrap();\n    config.load_priv_key_from_pem_file(&args.key).unwrap();\n\n    config.set_application_protos(&conn_args.alpns).unwrap();\n\n    config.discover_pmtu(args.enable_pmtud);\n    config.set_initial_rtt(conn_args.initial_rtt);\n    config.set_max_idle_timeout(conn_args.idle_timeout);\n    config.set_max_recv_udp_payload_size(max_datagram_size);\n    config.set_max_send_udp_payload_size(max_datagram_size);\n    config.set_initial_max_data(conn_args.max_data);\n    config.set_initial_max_stream_data_bidi_local(conn_args.max_stream_data);\n    config.set_initial_max_stream_data_bidi_remote(conn_args.max_stream_data);\n    config.set_initial_max_stream_data_uni(conn_args.max_stream_data);\n    config.set_initial_max_streams_bidi(conn_args.max_streams_bidi);\n    config.set_initial_max_streams_uni(conn_args.max_streams_uni);\n    config.set_disable_active_migration(!conn_args.enable_active_migration);\n    config.set_active_connection_id_limit(conn_args.max_active_cids);\n    config.set_initial_congestion_window_packets(\n        usize::try_from(conn_args.initial_cwnd_packets).unwrap(),\n    );\n\n    config.set_max_connection_window(conn_args.max_window);\n    config.set_max_stream_window(conn_args.max_stream_window);\n\n    config.enable_pacing(pacing);\n\n    let mut keylog = None;\n\n    if let Some(keylog_path) = std::env::var_os(\"SSLKEYLOGFILE\") {\n        let file = std::fs::OpenOptions::new()\n            .create(true)\n            .append(true)\n            .open(keylog_path)\n            .unwrap();\n\n        keylog = Some(file);\n\n        config.log_keys();\n    }\n\n    if conn_args.early_data {\n        config.enable_early_data();\n    }\n\n    if conn_args.no_grease {\n        config.grease(false);\n    }\n\n    config\n        .set_cc_algorithm_name(&conn_args.cc_algorithm)\n        .unwrap();\n\n    if conn_args.disable_hystart {\n        config.enable_hystart(false);\n    }\n\n    if conn_args.dgrams_enabled {\n        config.enable_dgram(true, 1000, 1000);\n    }\n\n    let rng = SystemRandom::new();\n    let conn_id_seed =\n        ring::hmac::Key::generate(ring::hmac::HMAC_SHA256, &rng).unwrap();\n\n    let mut next_client_id = 0;\n    let mut clients_ids = ClientIdMap::new();\n    let mut clients = ClientMap::new();\n\n    let mut pkt_count = 0;\n\n    let mut continue_write = false;\n\n    let local_addr = socket.local_addr().unwrap();\n\n    loop {\n        // Find the shorter timeout from all the active connections.\n        //\n        // TODO: use event loop that properly supports timers\n        let timeout = match continue_write {\n            true => Some(std::time::Duration::from_secs(0)),\n\n            false => clients.values().filter_map(|c| c.conn.timeout()).min(),\n        };\n\n        let mut poll_res = poll.poll(&mut events, timeout);\n        while let Err(e) = poll_res.as_ref() {\n            if e.kind() == std::io::ErrorKind::Interrupted {\n                trace!(\"mio poll() call failed, retrying: {e:?}\");\n                poll_res = poll.poll(&mut events, timeout);\n            } else {\n                panic!(\"mio poll() call failed fatally: {e:?}\");\n            }\n        }\n\n        // Read incoming UDP packets from the socket and feed them to quiche,\n        // until there are no more packets to read.\n        'read: loop {\n            // If the event loop reported no events, it means that the timeout\n            // has expired, so handle it without attempting to read packets. We\n            // will then proceed with the send loop.\n            if events.is_empty() && !continue_write {\n                trace!(\"timed out\");\n\n                clients.values_mut().for_each(|c| c.conn.on_timeout());\n\n                break 'read;\n            }\n\n            let (len, from) = match socket.recv_from(&mut buf) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    // There are no more UDP packets to read, so end the read\n                    // loop.\n                    if e.kind() == std::io::ErrorKind::WouldBlock {\n                        trace!(\"recv() would block\");\n                        break 'read;\n                    }\n\n                    panic!(\"recv() failed: {e:?}\");\n                },\n            };\n\n            trace!(\"got {len} bytes from {from} to {local_addr}\");\n\n            let pkt_buf = &mut buf[..len];\n\n            if let Some(target_path) = conn_args.dump_packet_path.as_ref() {\n                let path = format!(\"{target_path}/{pkt_count}.pkt\");\n\n                if let Ok(f) = std::fs::File::create(path) {\n                    let mut f = std::io::BufWriter::new(f);\n                    f.write_all(pkt_buf).ok();\n                }\n            }\n\n            pkt_count += 1;\n\n            // Parse the QUIC packet's header.\n            let hdr = match quiche::Header::from_slice(\n                pkt_buf,\n                quiche::MAX_CONN_ID_LEN,\n            ) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    error!(\"Parsing packet header failed: {e:?}\");\n                    continue 'read;\n                },\n            };\n\n            trace!(\"got packet {hdr:?}\");\n\n            let conn_id = if !cfg!(feature = \"fuzzing\") {\n                let conn_id = ring::hmac::sign(&conn_id_seed, &hdr.dcid);\n                let conn_id = &conn_id.as_ref()[..quiche::MAX_CONN_ID_LEN];\n                conn_id.to_vec().into()\n            } else {\n                // When fuzzing use an all zero connection ID.\n                [0; quiche::MAX_CONN_ID_LEN].to_vec().into()\n            };\n\n            // Lookup a connection based on the packet's connection ID. If there\n            // is no connection matching, create a new one.\n            let client = if !clients_ids.contains_key(&hdr.dcid) &&\n                !clients_ids.contains_key(&conn_id)\n            {\n                if hdr.ty != quiche::Type::Initial {\n                    error!(\"Packet is not Initial\");\n                    continue 'read;\n                }\n\n                if !quiche::version_is_supported(hdr.version) {\n                    warn!(\"Doing version negotiation\");\n\n                    let len =\n                        quiche::negotiate_version(&hdr.scid, &hdr.dcid, &mut out)\n                            .unwrap();\n\n                    let out = &out[..len];\n\n                    if let Err(e) = socket.send_to(out, from) {\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            trace!(\"send() would block\");\n                            break;\n                        }\n\n                        panic!(\"send() failed: {e:?}\");\n                    }\n                    continue 'read;\n                }\n\n                let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n                scid.copy_from_slice(&conn_id);\n\n                let mut odcid = None;\n\n                if !args.no_retry {\n                    // Token is always present in Initial packets.\n                    let token = hdr.token.as_ref().unwrap();\n\n                    // Do stateless retry if the client didn't send a token.\n                    if token.is_empty() {\n                        warn!(\"Doing stateless retry\");\n\n                        let scid = quiche::ConnectionId::from_ref(&scid);\n                        let new_token = mint_token(&hdr, &from);\n\n                        let len = quiche::retry(\n                            &hdr.scid,\n                            &hdr.dcid,\n                            &scid,\n                            &new_token,\n                            hdr.version,\n                            &mut out,\n                        )\n                        .unwrap();\n\n                        let out = &out[..len];\n\n                        if let Err(e) = socket.send_to(out, from) {\n                            if e.kind() == std::io::ErrorKind::WouldBlock {\n                                trace!(\"send() would block\");\n                                break;\n                            }\n\n                            panic!(\"send() failed: {e:?}\");\n                        }\n                        continue 'read;\n                    }\n\n                    odcid = validate_token(&from, token);\n\n                    // The token was not valid, meaning the retry failed, so\n                    // drop the packet.\n                    if odcid.is_none() {\n                        error!(\"Invalid address validation token\");\n                        continue;\n                    }\n\n                    if scid.len() != hdr.dcid.len() {\n                        error!(\"Invalid destination connection ID\");\n                        continue 'read;\n                    }\n\n                    // Reuse the source connection ID we sent in the Retry\n                    // packet, instead of changing it again.\n                    scid.copy_from_slice(&hdr.dcid);\n                }\n\n                let scid = quiche::ConnectionId::from_vec(scid.to_vec());\n\n                debug!(\"New connection: dcid={:?} scid={:?}\", hdr.dcid, scid);\n\n                #[allow(unused_mut)]\n                let mut conn = quiche::accept(\n                    &scid,\n                    odcid.as_ref(),\n                    local_addr,\n                    from,\n                    &mut config,\n                )\n                .unwrap();\n\n                if let Some(keylog) = &mut keylog {\n                    if let Ok(keylog) = keylog.try_clone() {\n                        conn.set_keylog(Box::new(keylog));\n                    }\n                }\n\n                // Only bother with qlog if the user specified it.\n                #[cfg(feature = \"qlog\")]\n                {\n                    if let Some(dir) = std::env::var_os(\"QLOGDIR\") {\n                        let id = format!(\"{:?}\", &scid);\n                        let writer = make_qlog_writer(&dir, \"server\", &id);\n\n                        conn.set_qlog(\n                            std::boxed::Box::new(writer),\n                            \"quiche-server qlog\".to_string(),\n                            format!(\"{} id={}\", \"quiche-server qlog\", id),\n                        );\n                    }\n                }\n\n                let client_id = next_client_id;\n\n                let client = Client {\n                    conn,\n                    http_conn: None,\n                    client_id,\n                    partial_requests: HashMap::new(),\n                    partial_responses: HashMap::new(),\n                    app_proto_selected: false,\n                    max_datagram_size,\n                    loss_rate: 0.0,\n                    max_send_burst: MAX_BUF_SIZE,\n                };\n\n                clients.insert(client_id, client);\n                clients_ids.insert(scid.clone(), client_id);\n\n                next_client_id += 1;\n\n                clients.get_mut(&client_id).unwrap()\n            } else {\n                let cid = match clients_ids.get(&hdr.dcid) {\n                    Some(v) => v,\n\n                    None => clients_ids.get(&conn_id).unwrap(),\n                };\n\n                clients.get_mut(cid).unwrap()\n            };\n\n            let recv_info = quiche::RecvInfo {\n                to: local_addr,\n                from,\n            };\n\n            // Process potentially coalesced packets.\n            let read = match client.conn.recv(pkt_buf, recv_info) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    error!(\"{} recv failed: {:?}\", client.conn.trace_id(), e);\n                    continue 'read;\n                },\n            };\n\n            trace!(\"{} processed {} bytes\", client.conn.trace_id(), read);\n\n            // Create a new application protocol session as soon as the QUIC\n            // connection is established.\n            if !client.app_proto_selected &&\n                (client.conn.is_in_early_data() ||\n                    client.conn.is_established())\n            {\n                // At this stage the ALPN negotiation succeeded and selected a\n                // single application protocol name. We'll use this to construct\n                // the correct type of HttpConn but `application_proto()`\n                // returns a slice, so we have to convert it to a str in order\n                // to compare to our lists of protocols. We `unwrap()` because\n                // we need the value and if something fails at this stage, there\n                // is not much anyone can do to recover.\n                let app_proto = client.conn.application_proto();\n\n                #[allow(clippy::box_default)]\n                if alpns::HTTP_09.contains(&app_proto) {\n                    client.http_conn = Some(Box::<Http09Conn>::default());\n\n                    client.app_proto_selected = true;\n                } else if alpns::HTTP_3.contains(&app_proto) {\n                    let dgram_sender = if conn_args.dgrams_enabled {\n                        Some(Http3DgramSender::new(\n                            conn_args.dgram_count,\n                            conn_args.dgram_data.clone(),\n                            1,\n                        ))\n                    } else {\n                        None\n                    };\n\n                    client.http_conn = match Http3Conn::with_conn(\n                        &mut client.conn,\n                        conn_args.max_field_section_size,\n                        conn_args.qpack_max_table_capacity,\n                        conn_args.qpack_blocked_streams,\n                        dgram_sender,\n                        Rc::new(RefCell::new(stdout_sink)),\n                    ) {\n                        Ok(v) => Some(v),\n\n                        Err(e) => {\n                            trace!(\"{} {}\", client.conn.trace_id(), e);\n                            None\n                        },\n                    };\n\n                    client.app_proto_selected = true;\n                }\n\n                // Update max_datagram_size after connection established.\n                client.max_datagram_size =\n                    client.conn.max_send_udp_payload_size();\n            }\n\n            if let Some(http_conn) = client.http_conn.as_mut() {\n                let conn = &mut client.conn;\n                let partial_responses = &mut client.partial_responses;\n\n                // Visit all writable response streams to send any remaining HTTP\n                // content.\n                for stream_id in writable_response_streams(conn) {\n                    http_conn.handle_writable(conn, partial_responses, stream_id);\n                }\n\n                if http_conn\n                    .handle_requests(\n                        conn,\n                        &mut client.partial_requests,\n                        partial_responses,\n                        &args.root,\n                        &args.index,\n                        &mut buf,\n                    )\n                    .is_err()\n                {\n                    continue 'read;\n                }\n            }\n\n            handle_path_events(client);\n\n            // See whether source Connection IDs have been retired.\n            while let Some(retired_scid) = client.conn.retired_scid_next() {\n                info!(\"Retiring source CID {retired_scid:?}\");\n                clients_ids.remove(&retired_scid);\n            }\n\n            // Provides as many CIDs as possible.\n            while client.conn.scids_left() > 0 {\n                let (scid, reset_token) = generate_cid_and_reset_token(&rng);\n                if client.conn.new_scid(&scid, reset_token, false).is_err() {\n                    break;\n                }\n\n                clients_ids.insert(scid, client.client_id);\n            }\n        }\n\n        // Generate outgoing QUIC packets for all active connections and send\n        // them on the UDP socket, until quiche reports that there are no more\n        // packets to be sent.\n        continue_write = false;\n        for client in clients.values_mut() {\n            // Reduce max_send_burst by 25% if loss is increasing more than 0.1%.\n            let loss_rate =\n                client.conn.stats().lost as f64 / client.conn.stats().sent as f64;\n            if loss_rate > client.loss_rate + 0.001 {\n                client.max_send_burst = client.max_send_burst / 4 * 3;\n                // Minimum bound of 10xMSS.\n                client.max_send_burst =\n                    client.max_send_burst.max(client.max_datagram_size * 10);\n                client.loss_rate = loss_rate;\n            }\n\n            let max_send_burst =\n                client.conn.send_quantum().min(client.max_send_burst) /\n                    client.max_datagram_size *\n                    client.max_datagram_size;\n            let mut total_write = 0;\n            let mut dst_info = None;\n\n            while total_write < max_send_burst {\n                let (write, send_info) = match client\n                    .conn\n                    .send(&mut out[total_write..max_send_burst])\n                {\n                    Ok(v) => v,\n\n                    Err(quiche::Error::Done) => {\n                        trace!(\"{} done writing\", client.conn.trace_id());\n                        break;\n                    },\n\n                    Err(e) => {\n                        error!(\"{} send failed: {:?}\", client.conn.trace_id(), e);\n\n                        client.conn.close(false, 0x1, b\"fail\").ok();\n                        break;\n                    },\n                };\n\n                total_write += write;\n\n                // Use the first packet time to send, not the last.\n                let _ = dst_info.get_or_insert(send_info);\n\n                if write < client.max_datagram_size {\n                    continue_write = true;\n                    break;\n                }\n            }\n\n            if total_write == 0 || dst_info.is_none() {\n                continue;\n            }\n\n            if let Err(e) = send_to(\n                &socket,\n                &out[..total_write],\n                &dst_info.unwrap(),\n                client.max_datagram_size,\n                pacing,\n                enable_gso,\n            ) {\n                if e.kind() == std::io::ErrorKind::WouldBlock {\n                    trace!(\"send() would block\");\n                    break;\n                }\n\n                panic!(\"send_to() failed: {e:?}\");\n            }\n\n            trace!(\n                \"{} written {total_write} bytes with {dst_info:?}\",\n                client.conn.trace_id()\n            );\n\n            if total_write >= max_send_burst {\n                trace!(\"{} pause writing\", client.conn.trace_id(),);\n                continue_write = true;\n                break;\n            }\n        }\n\n        // Garbage collect closed connections.\n        clients.retain(|_, ref mut c| {\n            trace!(\"Collecting garbage\");\n\n            if c.conn.is_closed() {\n                info!(\n                    \"{} connection collected {:?} {:?}\",\n                    c.conn.trace_id(),\n                    c.conn.stats(),\n                    c.conn.path_stats().collect::<Vec<quiche::PathStats>>()\n                );\n\n                for id in c.conn.source_ids() {\n                    let id_owned = id.clone().into_owned();\n                    clients_ids.remove(&id_owned);\n                }\n            }\n\n            !c.conn.is_closed()\n        });\n    }\n}\n\n/// Generate a stateless retry token.\n///\n/// The token includes the static string `\"quiche\"` followed by the IP address\n/// of the client and by the original destination connection ID generated by the\n/// client.\n///\n/// Note that this function is only an example and doesn't do any cryptographic\n/// authenticate of the token. *It should not be used in production system*.\nfn mint_token(hdr: &quiche::Header, src: &net::SocketAddr) -> Vec<u8> {\n    let mut token = Vec::new();\n\n    token.extend_from_slice(b\"quiche\");\n\n    let addr = match src.ip() {\n        std::net::IpAddr::V4(a) => a.octets().to_vec(),\n        std::net::IpAddr::V6(a) => a.octets().to_vec(),\n    };\n\n    token.extend_from_slice(&addr);\n    token.extend_from_slice(&hdr.dcid);\n\n    token\n}\n\n/// Validates a stateless retry token.\n///\n/// This checks that the ticket includes the `\"quiche\"` static string, and that\n/// the client IP address matches the address stored in the ticket.\n///\n/// Note that this function is only an example and doesn't do any cryptographic\n/// authenticate of the token. *It should not be used in production system*.\nfn validate_token<'a>(\n    src: &net::SocketAddr, token: &'a [u8],\n) -> Option<quiche::ConnectionId<'a>> {\n    if token.len() < 6 {\n        return None;\n    }\n\n    if &token[..6] != b\"quiche\" {\n        return None;\n    }\n\n    let token = &token[6..];\n\n    let addr = match src.ip() {\n        std::net::IpAddr::V4(a) => a.octets().to_vec(),\n        std::net::IpAddr::V6(a) => a.octets().to_vec(),\n    };\n\n    if token.len() < addr.len() || &token[..addr.len()] != addr.as_slice() {\n        return None;\n    }\n\n    Some(quiche::ConnectionId::from_ref(&token[addr.len()..]))\n}\n\nfn handle_path_events(client: &mut Client) {\n    while let Some(qe) = client.conn.path_event_next() {\n        match qe {\n            quiche::PathEvent::New(local_addr, peer_addr) => {\n                info!(\n                    \"{} Seen new path ({}, {})\",\n                    client.conn.trace_id(),\n                    local_addr,\n                    peer_addr\n                );\n\n                // Directly probe the new path.\n                client\n                    .conn\n                    .probe_path(local_addr, peer_addr)\n                    .expect(\"cannot probe\");\n            },\n\n            quiche::PathEvent::Validated(local_addr, peer_addr) => {\n                info!(\n                    \"{} Path ({}, {}) is now validated\",\n                    client.conn.trace_id(),\n                    local_addr,\n                    peer_addr\n                );\n            },\n\n            quiche::PathEvent::FailedValidation(local_addr, peer_addr) => {\n                info!(\n                    \"{} Path ({}, {}) failed validation\",\n                    client.conn.trace_id(),\n                    local_addr,\n                    peer_addr\n                );\n            },\n\n            quiche::PathEvent::Closed(local_addr, peer_addr) => {\n                info!(\n                    \"{} Path ({}, {}) is now closed and unusable\",\n                    client.conn.trace_id(),\n                    local_addr,\n                    peer_addr\n                );\n            },\n\n            quiche::PathEvent::ReusedSourceConnectionId(cid_seq, old, new) => {\n                info!(\n                    \"{} Peer reused cid seq {} (initially {:?}) on {:?}\",\n                    client.conn.trace_id(),\n                    cid_seq,\n                    old,\n                    new\n                );\n            },\n\n            quiche::PathEvent::PeerMigrated(local_addr, peer_addr) => {\n                info!(\n                    \"{} Connection migrated to ({}, {})\",\n                    client.conn.trace_id(),\n                    local_addr,\n                    peer_addr\n                );\n            },\n        }\n    }\n}\n\n/// Set SO_TXTIME socket option.\n///\n/// This socket option is set to send to kernel the outgoing UDP\n/// packet transmission time in the sendmsg syscall.\n///\n/// Note that this socket option is set only on linux platforms.\n#[cfg(target_os = \"linux\")]\nfn set_txtime_sockopt(sock: &mio::net::UdpSocket) -> io::Result<()> {\n    use nix::sys::socket::setsockopt;\n    use nix::sys::socket::sockopt::TxTime;\n    use std::os::unix::io::AsFd;\n\n    let config = nix::libc::sock_txtime {\n        clockid: libc::CLOCK_MONOTONIC,\n        flags: 0,\n    };\n\n    setsockopt(&sock.as_fd(), TxTime, &config)?;\n\n    Ok(())\n}\n\n#[cfg(not(target_os = \"linux\"))]\nfn set_txtime_sockopt(_: &mio::net::UdpSocket) -> io::Result<()> {\n    use std::io::Error;\n\n    Err(Error::other(\"Not supported on this platform\"))\n}\n"
  },
  {
    "path": "apps/src/client.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::args::*;\nuse crate::common::*;\n\nuse std::io::prelude::*;\n\nuse std::rc::Rc;\n\nuse std::cell::RefCell;\n\nuse ring::rand::*;\n\nconst MAX_DATAGRAM_SIZE: usize = 1350;\n\n#[derive(Debug)]\npub enum ClientError {\n    HandshakeFail,\n    HttpFail,\n    Other(String),\n}\n\npub fn connect(\n    args: ClientArgs, conn_args: CommonArgs,\n    output_sink: impl FnMut(String) + 'static,\n) -> Result<(), ClientError> {\n    let mut buf = [0; 65535];\n    let mut out = [0; MAX_DATAGRAM_SIZE];\n\n    let output_sink =\n        Rc::new(RefCell::new(output_sink)) as Rc<RefCell<dyn FnMut(_)>>;\n\n    // Setup the event loop.\n    let mut poll = mio::Poll::new().unwrap();\n    let mut events = mio::Events::with_capacity(1024);\n\n    // We'll only connect to the first server provided in URL list.\n    let connect_url = &args.urls[0];\n\n    // Resolve server address.\n    let peer_addr = if let Some(addr) = &args.connect_to {\n        addr.parse().expect(\"--connect-to is expected to be a string containing an IPv4 or IPv6 address with a port. E.g. 192.0.2.0:443\")\n    } else {\n        *connect_url.socket_addrs(|| None).unwrap().first().unwrap()\n    };\n\n    // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the\n    // server address. This is needed on macOS and BSD variants that don't\n    // support binding to IN6ADDR_ANY for both v4 and v6.\n    let bind_addr = match peer_addr {\n        std::net::SocketAddr::V4(_) => format!(\"0.0.0.0:{}\", args.source_port),\n        std::net::SocketAddr::V6(_) => format!(\"[::]:{}\", args.source_port),\n    };\n\n    // Create the UDP socket backing the QUIC connection, and register it with\n    // the event loop.\n    let mut socket =\n        mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap();\n    poll.registry()\n        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)\n        .unwrap();\n\n    let migrate_socket = if args.perform_migration {\n        let mut socket =\n            mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap();\n        poll.registry()\n            .register(&mut socket, mio::Token(1), mio::Interest::READABLE)\n            .unwrap();\n\n        Some(socket)\n    } else {\n        None\n    };\n\n    // Create the configuration for the QUIC connection.\n    let mut config = quiche::Config::new(args.version).unwrap();\n\n    if let Some(ref trust_origin_ca_pem) = args.trust_origin_ca_pem {\n        config\n            .load_verify_locations_from_file(trust_origin_ca_pem)\n            .map_err(|e| {\n                ClientError::Other(format!(\"error loading origin CA file : {e}\"))\n            })?;\n    } else {\n        config.verify_peer(!args.no_verify);\n    }\n\n    config.set_application_protos(&conn_args.alpns).unwrap();\n\n    config.set_initial_rtt(conn_args.initial_rtt);\n    config.set_max_idle_timeout(conn_args.idle_timeout);\n    config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_initial_max_data(conn_args.max_data);\n    config.set_initial_max_stream_data_bidi_local(conn_args.max_stream_data);\n    config.set_initial_max_stream_data_bidi_remote(conn_args.max_stream_data);\n    config.set_initial_max_stream_data_uni(conn_args.max_stream_data);\n    config.set_initial_max_streams_bidi(conn_args.max_streams_bidi);\n    config.set_initial_max_streams_uni(conn_args.max_streams_uni);\n    config.set_disable_active_migration(!conn_args.enable_active_migration);\n    config.set_active_connection_id_limit(conn_args.max_active_cids);\n\n    config.set_max_connection_window(conn_args.max_window);\n    config.set_max_stream_window(conn_args.max_stream_window);\n\n    let mut keylog = None;\n\n    if let Some(keylog_path) = std::env::var_os(\"SSLKEYLOGFILE\") {\n        let file = std::fs::OpenOptions::new()\n            .create(true)\n            .append(true)\n            .open(keylog_path)\n            .unwrap();\n\n        keylog = Some(file);\n\n        config.log_keys();\n    }\n\n    if conn_args.no_grease {\n        config.grease(false);\n    }\n\n    if conn_args.early_data {\n        config.enable_early_data();\n    }\n\n    config\n        .set_cc_algorithm_name(&conn_args.cc_algorithm)\n        .unwrap();\n\n    if conn_args.disable_hystart {\n        config.enable_hystart(false);\n    }\n\n    if conn_args.dgrams_enabled {\n        config.enable_dgram(true, 1000, 1000);\n    }\n\n    let mut http_conn: Option<Box<dyn HttpConn>> = None;\n\n    let mut app_proto_selected = false;\n\n    // Generate a random source connection ID for the connection.\n    let rng = SystemRandom::new();\n\n    let scid = if !cfg!(feature = \"fuzzing\") {\n        let mut conn_id = [0; quiche::MAX_CONN_ID_LEN];\n        rng.fill(&mut conn_id[..]).unwrap();\n\n        conn_id.to_vec()\n    } else {\n        // When fuzzing use an all zero connection ID.\n        [0; quiche::MAX_CONN_ID_LEN].to_vec()\n    };\n\n    let scid = quiche::ConnectionId::from_ref(&scid);\n\n    let local_addr = socket.local_addr().unwrap();\n\n    // Create a QUIC connection and initiate handshake.\n    let mut conn = quiche::connect(\n        connect_url.domain(),\n        &scid,\n        local_addr,\n        peer_addr,\n        &mut config,\n    )\n    .unwrap();\n\n    if let Some(keylog) = &mut keylog {\n        if let Ok(keylog) = keylog.try_clone() {\n            conn.set_keylog(Box::new(keylog));\n        }\n    }\n\n    // Only bother with qlog if the user specified it.\n    #[cfg(feature = \"qlog\")]\n    {\n        if let Some(dir) = std::env::var_os(\"QLOGDIR\") {\n            let id = format!(\"{scid:?}\");\n            let writer = make_qlog_writer(&dir, \"client\", &id);\n\n            conn.set_qlog(\n                std::boxed::Box::new(writer),\n                \"quiche-client qlog\".to_string(),\n                format!(\"{} id={}\", \"quiche-client qlog\", id),\n            );\n        }\n    }\n\n    if let Some(session_file) = &args.session_file {\n        if let Ok(session) = std::fs::read(session_file) {\n            conn.set_session(&session).ok();\n        }\n    }\n\n    info!(\n        \"connecting to {:} from {:} with scid {:?}\",\n        peer_addr,\n        socket.local_addr().unwrap(),\n        scid,\n    );\n\n    let (write, send_info) = conn.send(&mut out).expect(\"initial send failed\");\n\n    while let Err(e) = socket.send_to(&out[..write], send_info.to) {\n        if e.kind() == std::io::ErrorKind::WouldBlock {\n            trace!(\n                \"{} -> {}: send() would block\",\n                socket.local_addr().unwrap(),\n                send_info.to\n            );\n            continue;\n        }\n\n        return Err(ClientError::Other(format!(\"send() failed: {e:?}\")));\n    }\n\n    trace!(\"written {write}\");\n\n    let app_data_start = std::time::Instant::now();\n\n    let mut pkt_count = 0;\n\n    let mut scid_sent = false;\n    let mut new_path_probed = false;\n    let mut migrated = false;\n\n    loop {\n        if !conn.is_in_early_data() || app_proto_selected {\n            poll.poll(&mut events, conn.timeout()).unwrap();\n        }\n\n        // If the event loop reported no events, it means that the timeout\n        // has expired, so handle it without attempting to read packets. We\n        // will then proceed with the send loop.\n        if events.is_empty() {\n            trace!(\"timed out\");\n\n            conn.on_timeout();\n        }\n\n        // Read incoming UDP packets from the socket and feed them to quiche,\n        // until there are no more packets to read.\n        for event in &events {\n            let socket = match event.token() {\n                mio::Token(0) => &socket,\n\n                mio::Token(1) => migrate_socket.as_ref().unwrap(),\n\n                _ => unreachable!(),\n            };\n\n            let local_addr = socket.local_addr().unwrap();\n            'read: loop {\n                let (len, from) = match socket.recv_from(&mut buf) {\n                    Ok(v) => v,\n\n                    Err(e) => {\n                        // There are no more UDP packets to read on this socket.\n                        // Process subsequent events.\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            trace!(\"{local_addr}: recv() would block\");\n                            break 'read;\n                        }\n\n                        return Err(ClientError::Other(format!(\n                            \"{local_addr}: recv() failed: {e:?}\"\n                        )));\n                    },\n                };\n\n                trace!(\"got {len} bytes from {from} to {local_addr}\");\n\n                if let Some(target_path) = conn_args.dump_packet_path.as_ref() {\n                    let path = format!(\"{target_path}/{pkt_count}.pkt\");\n\n                    if let Ok(f) = std::fs::File::create(path) {\n                        let mut f = std::io::BufWriter::new(f);\n                        f.write_all(&buf[..len]).ok();\n                    }\n                }\n\n                pkt_count += 1;\n\n                let recv_info = quiche::RecvInfo {\n                    to: local_addr,\n                    from,\n                };\n\n                // Process potentially coalesced packets.\n                let read = match conn.recv(&mut buf[..len], recv_info) {\n                    Ok(v) => v,\n\n                    Err(e) => {\n                        error!(\"{local_addr}: recv failed: {e:?}\");\n                        continue 'read;\n                    },\n                };\n\n                trace!(\"{local_addr}: processed {read} bytes\");\n            }\n        }\n\n        trace!(\"done reading\");\n\n        if conn.is_closed() {\n            info!(\n                \"connection closed, {:?} {:?}\",\n                conn.stats(),\n                conn.path_stats().collect::<Vec<quiche::PathStats>>()\n            );\n\n            if !conn.is_established() {\n                error!(\n                    \"connection timed out after {:?}\",\n                    app_data_start.elapsed(),\n                );\n\n                return Err(ClientError::HandshakeFail);\n            }\n\n            if let Some(session_file) = &args.session_file {\n                if let Some(session) = conn.session() {\n                    std::fs::write(session_file, session).ok();\n                }\n            }\n\n            if let Some(h_conn) = http_conn {\n                if h_conn.report_incomplete(&app_data_start) {\n                    return Err(ClientError::HttpFail);\n                }\n            }\n\n            break;\n        }\n\n        // Create a new application protocol session once the QUIC connection is\n        // established.\n        if (conn.is_established() || conn.is_in_early_data()) &&\n            (!args.perform_migration || migrated) &&\n            !app_proto_selected\n        {\n            // At this stage the ALPN negotiation succeeded and selected a\n            // single application protocol name. We'll use this to construct\n            // the correct type of HttpConn but `application_proto()`\n            // returns a slice, so we have to convert it to a str in order\n            // to compare to our lists of protocols. We `unwrap()` because\n            // we need the value and if something fails at this stage, there\n            // is not much anyone can do to recover.\n\n            let app_proto = conn.application_proto();\n\n            if alpns::HTTP_09.contains(&app_proto) {\n                http_conn = Some(Http09Conn::with_urls(\n                    &args.urls,\n                    args.reqs_cardinal,\n                    Rc::clone(&output_sink),\n                ));\n\n                app_proto_selected = true;\n            } else if alpns::HTTP_3.contains(&app_proto) {\n                let dgram_sender = if conn_args.dgrams_enabled {\n                    Some(Http3DgramSender::new(\n                        conn_args.dgram_count,\n                        conn_args.dgram_data.clone(),\n                        0,\n                    ))\n                } else {\n                    None\n                };\n\n                http_conn = Some(Http3Conn::with_urls(\n                    &mut conn,\n                    &args.urls,\n                    args.reqs_cardinal,\n                    &args.req_headers,\n                    &args.body,\n                    &args.method,\n                    args.send_priority_update,\n                    conn_args.max_field_section_size,\n                    conn_args.qpack_max_table_capacity,\n                    conn_args.qpack_blocked_streams,\n                    args.dump_json,\n                    dgram_sender,\n                    Rc::clone(&output_sink),\n                ));\n\n                app_proto_selected = true;\n            }\n        }\n\n        // If we have an HTTP connection, first issue the requests then\n        // process received data.\n        if let Some(h_conn) = http_conn.as_mut() {\n            h_conn.send_requests(&mut conn, &args.dump_response_path);\n            h_conn.handle_responses(&mut conn, &mut buf, &app_data_start);\n        }\n\n        // Handle path events.\n        while let Some(qe) = conn.path_event_next() {\n            match qe {\n                quiche::PathEvent::New(..) => unreachable!(),\n\n                quiche::PathEvent::Validated(local_addr, peer_addr) => {\n                    info!(\"Path ({local_addr}, {peer_addr}) is now validated\");\n                    conn.migrate(local_addr, peer_addr).unwrap();\n                    migrated = true;\n                },\n\n                quiche::PathEvent::FailedValidation(local_addr, peer_addr) => {\n                    info!(\"Path ({local_addr}, {peer_addr}) failed validation\");\n                },\n\n                quiche::PathEvent::Closed(local_addr, peer_addr) => {\n                    info!(\n                        \"Path ({local_addr}, {peer_addr}) is now closed and unusable\"\n                    );\n                },\n\n                quiche::PathEvent::ReusedSourceConnectionId(\n                    cid_seq,\n                    old,\n                    new,\n                ) => {\n                    info!(\n                        \"Peer reused cid seq {cid_seq} (initially {old:?}) on {new:?}\"\n                    );\n                },\n\n                quiche::PathEvent::PeerMigrated(..) => unreachable!(),\n            }\n        }\n\n        // See whether source Connection IDs have been retired.\n        while let Some(retired_scid) = conn.retired_scid_next() {\n            info!(\"Retiring source CID {retired_scid:?}\");\n        }\n\n        // Provides as many CIDs as possible.\n        while conn.scids_left() > 0 {\n            let (scid, reset_token) = generate_cid_and_reset_token(&rng);\n\n            if conn.new_scid(&scid, reset_token, false).is_err() {\n                break;\n            }\n\n            scid_sent = true;\n        }\n\n        if args.perform_migration &&\n            !new_path_probed &&\n            scid_sent &&\n            conn.available_dcids() > 0\n        {\n            let additional_local_addr =\n                migrate_socket.as_ref().unwrap().local_addr().unwrap();\n            conn.probe_path(additional_local_addr, peer_addr).unwrap();\n\n            new_path_probed = true;\n        }\n\n        // Generate outgoing QUIC packets and send them on the UDP socket, until\n        // quiche reports that there are no more packets to be sent.\n        let mut sockets = vec![&socket];\n        if let Some(migrate_socket) = migrate_socket.as_ref() {\n            sockets.push(migrate_socket);\n        }\n\n        for socket in sockets {\n            let local_addr = socket.local_addr().unwrap();\n\n            for peer_addr in conn.paths_iter(local_addr) {\n                loop {\n                    let (write, send_info) = match conn.send_on_path(\n                        &mut out,\n                        Some(local_addr),\n                        Some(peer_addr),\n                    ) {\n                        Ok(v) => v,\n\n                        Err(quiche::Error::Done) => {\n                            trace!(\"{local_addr} -> {peer_addr}: done writing\");\n                            break;\n                        },\n\n                        Err(e) => {\n                            error!(\n                                \"{local_addr} -> {peer_addr}: send failed: {e:?}\"\n                            );\n\n                            conn.close(false, 0x1, b\"fail\").ok();\n                            break;\n                        },\n                    };\n\n                    if let Err(e) = socket.send_to(&out[..write], send_info.to) {\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            trace!(\n                                \"{} -> {}: send() would block\",\n                                local_addr,\n                                send_info.to\n                            );\n                            break;\n                        }\n\n                        return Err(ClientError::Other(format!(\n                            \"{} -> {}: send() failed: {:?}\",\n                            local_addr, send_info.to, e\n                        )));\n                    }\n\n                    trace!(\n                        \"written {write} bytes from {local_addr} to {}\",\n                        send_info.to\n                    );\n                }\n            }\n        }\n\n        if conn.is_closed() {\n            info!(\n                \"connection closed, {:?} {:?}\",\n                conn.stats(),\n                conn.path_stats().collect::<Vec<quiche::PathStats>>()\n            );\n\n            if !conn.is_established() {\n                error!(\n                    \"connection timed out after {:?}\",\n                    app_data_start.elapsed(),\n                );\n\n                return Err(ClientError::HandshakeFail);\n            }\n\n            if let Some(session_file) = &args.session_file {\n                if let Some(session) = conn.session() {\n                    std::fs::write(session_file, session).ok();\n                }\n            }\n\n            if let Some(h_conn) = http_conn {\n                if h_conn.report_incomplete(&app_data_start) {\n                    return Err(ClientError::HttpFail);\n                }\n            }\n\n            break;\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "apps/src/common.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Quiche application utilities.\n//!\n//! This module provides some utility functions that are common to quiche\n//! applications.\n\nuse std::io::prelude::*;\n\nuse std::collections::HashMap;\n\n#[cfg(feature = \"sfv\")]\nuse std::convert::TryFrom;\n\nuse std::fmt::Write as _;\n\nuse std::rc::Rc;\n\nuse std::cell::RefCell;\n\nuse std::path;\n\nuse ring::rand::SecureRandom;\n\nuse quiche::ConnectionId;\n\nuse quiche::h3::NameValue;\nuse quiche::h3::Priority;\n\npub fn stdout_sink(out: String) {\n    print!(\"{out}\");\n}\n\nconst H3_MESSAGE_ERROR: u64 = 0x10E;\n\n/// ALPN helpers.\n///\n/// This module contains constants and functions for working with ALPN.\npub mod alpns {\n    pub const HTTP_09: [&[u8]; 2] = [b\"hq-interop\", b\"http/0.9\"];\n    pub const HTTP_3: [&[u8]; 1] = [b\"h3\"];\n}\n\npub struct PartialRequest {\n    pub req: Vec<u8>,\n}\n\npub struct PartialResponse {\n    pub headers: Option<Vec<quiche::h3::Header>>,\n    pub priority: Option<quiche::h3::Priority>,\n\n    pub body: Vec<u8>,\n\n    pub written: usize,\n}\n\npub type ClientId = u64;\n\npub struct Client {\n    pub conn: quiche::Connection,\n\n    pub http_conn: Option<Box<dyn HttpConn>>,\n\n    pub client_id: ClientId,\n\n    pub app_proto_selected: bool,\n\n    pub partial_requests: std::collections::HashMap<u64, PartialRequest>,\n\n    pub partial_responses: std::collections::HashMap<u64, PartialResponse>,\n\n    pub max_datagram_size: usize,\n\n    pub loss_rate: f64,\n\n    pub max_send_burst: usize,\n}\n\npub type ClientIdMap = HashMap<ConnectionId<'static>, ClientId>;\npub type ClientMap = HashMap<ClientId, Client>;\n\n/// Makes a buffered writer for a resource with a target URL.\n///\n/// The file will have the same name as the resource's last path segment value.\n/// Multiple requests for the same URL are indicated by the value of `cardinal`,\n/// any value \"N\" greater than 1, will cause \".N\" to be appended to the\n/// filename.\nfn make_resource_writer(\n    url: &url::Url, target_path: &Option<String>, cardinal: u64,\n) -> Option<std::io::BufWriter<std::fs::File>> {\n    if let Some(tp) = target_path {\n        let resource =\n            url.path_segments().map(|c| c.collect::<Vec<_>>()).unwrap();\n\n        let mut path = format!(\"{}/{}\", tp, resource.iter().last().unwrap());\n\n        if cardinal > 1 {\n            path = format!(\"{path}.{cardinal}\");\n        }\n\n        match std::fs::File::create(&path) {\n            Ok(f) => return Some(std::io::BufWriter::new(f)),\n\n            Err(e) => panic!(\n                \"Error creating file for {url}, attempted path was {path}: {e}\"\n            ),\n        }\n    }\n\n    None\n}\n\nfn autoindex(path: path::PathBuf, index: &str) -> path::PathBuf {\n    if let Some(path_str) = path.to_str() {\n        if path_str.ends_with('/') {\n            let path_str = format!(\"{path_str}{index}\");\n            return path::PathBuf::from(&path_str);\n        }\n    }\n\n    path\n}\n\n/// Makes a buffered writer for a qlog.\npub fn make_qlog_writer(\n    dir: &std::ffi::OsStr, role: &str, id: &str,\n) -> std::io::BufWriter<std::fs::File> {\n    let mut path = std::path::PathBuf::from(dir);\n    let filename = format!(\"{role}-{id}.sqlog\");\n    path.push(filename);\n\n    match std::fs::File::create(&path) {\n        Ok(f) => std::io::BufWriter::new(f),\n\n        Err(e) =>\n            panic!(\"Error creating qlog file attempted path was {path:?}: {e}\"),\n    }\n}\n\nfn dump_json(reqs: &[Http3Request], output_sink: &mut dyn FnMut(String)) {\n    let mut out = String::new();\n\n    writeln!(out, \"{{\").unwrap();\n    writeln!(out, \"  \\\"entries\\\": [\").unwrap();\n    let mut reqs = reqs.iter().peekable();\n\n    while let Some(req) = reqs.next() {\n        writeln!(out, \"  {{\").unwrap();\n        writeln!(out, \"    \\\"request\\\":{{\").unwrap();\n        writeln!(out, \"      \\\"headers\\\":[\").unwrap();\n\n        let mut req_hdrs = req.hdrs.iter().peekable();\n        while let Some(h) = req_hdrs.next() {\n            writeln!(out, \"        {{\").unwrap();\n            writeln!(\n                out,\n                \"          \\\"name\\\": \\\"{}\\\",\",\n                std::str::from_utf8(h.name()).unwrap()\n            )\n            .unwrap();\n            writeln!(\n                out,\n                \"          \\\"value\\\": \\\"{}\\\"\",\n                std::str::from_utf8(h.value()).unwrap().replace('\"', \"\\\\\\\"\")\n            )\n            .unwrap();\n\n            if req_hdrs.peek().is_some() {\n                writeln!(out, \"        }},\").unwrap();\n            } else {\n                writeln!(out, \"        }}\").unwrap();\n            }\n        }\n        writeln!(out, \"      ]}},\").unwrap();\n\n        writeln!(out, \"    \\\"response\\\":{{\").unwrap();\n        writeln!(out, \"      \\\"headers\\\":[\").unwrap();\n\n        let mut response_hdrs = req.response_hdrs.iter().peekable();\n        while let Some(h) = response_hdrs.next() {\n            writeln!(out, \"        {{\").unwrap();\n            writeln!(\n                out,\n                \"          \\\"name\\\": \\\"{}\\\",\",\n                std::str::from_utf8(h.name()).unwrap()\n            )\n            .unwrap();\n            writeln!(\n                out,\n                \"          \\\"value\\\": \\\"{}\\\"\",\n                std::str::from_utf8(h.value()).unwrap().replace('\"', \"\\\\\\\"\")\n            )\n            .unwrap();\n\n            if response_hdrs.peek().is_some() {\n                writeln!(out, \"        }},\").unwrap();\n            } else {\n                writeln!(out, \"        }}\").unwrap();\n            }\n        }\n        writeln!(out, \"      ],\").unwrap();\n        writeln!(out, \"      \\\"body\\\": {:?}\", req.response_body).unwrap();\n        writeln!(out, \"    }}\").unwrap();\n\n        if reqs.peek().is_some() {\n            writeln!(out, \"}},\").unwrap();\n        } else {\n            writeln!(out, \"}}\").unwrap();\n        }\n    }\n    writeln!(out, \"]\").unwrap();\n    writeln!(out, \"}}\").unwrap();\n\n    output_sink(out);\n}\n\npub fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> {\n    hdrs.iter()\n        .map(|h| {\n            let name = String::from_utf8_lossy(h.name()).to_string();\n            let value = String::from_utf8_lossy(h.value()).to_string();\n\n            (name, value)\n        })\n        .collect()\n}\n\n/// Generate a new pair of Source Connection ID and reset token.\npub fn generate_cid_and_reset_token<T: SecureRandom>(\n    rng: &T,\n) -> (quiche::ConnectionId<'static>, u128) {\n    let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n    rng.fill(&mut scid).unwrap();\n    let scid = scid.to_vec().into();\n    let mut reset_token = [0; 16];\n    rng.fill(&mut reset_token).unwrap();\n    let reset_token = u128::from_be_bytes(reset_token);\n    (scid, reset_token)\n}\n\n/// Construct a priority field value from quiche apps custom query string.\npub fn priority_field_value_from_query_string(url: &url::Url) -> Option<String> {\n    let mut priority = \"\".to_string();\n    for param in url.query_pairs() {\n        if param.0 == \"u\" {\n            write!(priority, \"{}={},\", param.0, param.1).ok();\n        }\n\n        if param.0 == \"i\" && param.1 == \"1\" {\n            priority.push_str(\"i,\");\n        }\n    }\n\n    if !priority.is_empty() {\n        // remove trailing comma\n        priority.pop();\n\n        Some(priority)\n    } else {\n        None\n    }\n}\n\n/// Construct a Priority from quiche apps custom query string.\npub fn priority_from_query_string(url: &url::Url) -> Option<Priority> {\n    let mut urgency = None;\n    let mut incremental = None;\n    for param in url.query_pairs() {\n        if param.0 == \"u\" {\n            urgency = Some(param.1.parse::<u8>().unwrap());\n        }\n\n        if param.0 == \"i\" && param.1 == \"1\" {\n            incremental = Some(true);\n        }\n    }\n\n    match (urgency, incremental) {\n        (Some(u), Some(i)) => Some(Priority::new(u, i)),\n\n        (Some(u), None) => Some(Priority::new(u, false)),\n\n        (None, Some(i)) => Some(Priority::new(3, i)),\n\n        (None, None) => None,\n    }\n}\n\nfn send_h3_dgram(\n    conn: &mut quiche::Connection, flow_id: u64, dgram_content: &[u8],\n) -> quiche::Result<()> {\n    info!(\n        \"sending HTTP/3 DATAGRAM on flow_id={flow_id} with data {dgram_content:?}\"\n    );\n\n    let len = octets::varint_len(flow_id) + dgram_content.len();\n    let mut d = vec![0; len];\n    let mut b = octets::OctetsMut::with_slice(&mut d);\n\n    b.put_varint(flow_id)\n        .map_err(|_| quiche::Error::BufferTooShort)?;\n    b.put_bytes(dgram_content)\n        .map_err(|_| quiche::Error::BufferTooShort)?;\n\n    conn.dgram_send(&d)\n}\n\npub trait HttpConn {\n    fn send_requests(\n        &mut self, conn: &mut quiche::Connection, target_path: &Option<String>,\n    );\n\n    fn handle_responses(\n        &mut self, conn: &mut quiche::Connection, buf: &mut [u8],\n        req_start: &std::time::Instant,\n    );\n\n    fn report_incomplete(&self, start: &std::time::Instant) -> bool;\n\n    fn handle_requests(\n        &mut self, conn: &mut quiche::Connection,\n        partial_requests: &mut HashMap<u64, PartialRequest>,\n        partial_responses: &mut HashMap<u64, PartialResponse>, root: &str,\n        index: &str, buf: &mut [u8],\n    ) -> quiche::h3::Result<()>;\n\n    fn handle_writable(\n        &mut self, conn: &mut quiche::Connection,\n        partial_responses: &mut HashMap<u64, PartialResponse>, stream_id: u64,\n    );\n}\n\npub fn writable_response_streams(\n    conn: &quiche::Connection,\n) -> impl Iterator<Item = u64> {\n    conn.writable().filter(|id| id % 4 == 0)\n}\n\n/// Represents an HTTP/0.9 formatted request.\npub struct Http09Request {\n    url: url::Url,\n    cardinal: u64,\n    request_line: String,\n    stream_id: Option<u64>,\n    response_writer: Option<std::io::BufWriter<std::fs::File>>,\n}\n\n/// Represents an HTTP/3 formatted request.\nstruct Http3Request {\n    url: url::Url,\n    cardinal: u64,\n    stream_id: Option<u64>,\n    hdrs: Vec<quiche::h3::Header>,\n    priority: Option<Priority>,\n    response_hdrs: Vec<quiche::h3::Header>,\n    response_body: Vec<u8>,\n    response_body_max: usize,\n    response_writer: Option<std::io::BufWriter<std::fs::File>>,\n}\n\ntype Http3ResponseBuilderResult = std::result::Result<\n    (Vec<quiche::h3::Header>, Vec<u8>, Vec<u8>),\n    (u64, String),\n>;\n\npub struct Http09Conn {\n    stream_id: u64,\n    reqs_sent: usize,\n    reqs_complete: usize,\n    reqs: Vec<Http09Request>,\n    output_sink: Rc<RefCell<dyn FnMut(String)>>,\n}\n\nimpl Default for Http09Conn {\n    fn default() -> Self {\n        Http09Conn {\n            stream_id: Default::default(),\n            reqs_sent: Default::default(),\n            reqs_complete: Default::default(),\n            reqs: Default::default(),\n            output_sink: Rc::new(RefCell::new(stdout_sink)),\n        }\n    }\n}\n\nimpl Http09Conn {\n    pub fn with_urls(\n        urls: &[url::Url], reqs_cardinal: u64,\n        output_sink: Rc<RefCell<dyn FnMut(String)>>,\n    ) -> Box<dyn HttpConn> {\n        let mut reqs = Vec::new();\n        for url in urls {\n            for i in 1..=reqs_cardinal {\n                let request_line = format!(\"GET {}\\r\\n\", url.path());\n                reqs.push(Http09Request {\n                    url: url.clone(),\n                    cardinal: i,\n                    request_line,\n                    stream_id: None,\n                    response_writer: None,\n                });\n            }\n        }\n\n        let h_conn = Http09Conn {\n            stream_id: 0,\n            reqs_sent: 0,\n            reqs_complete: 0,\n            reqs,\n            output_sink,\n        };\n\n        Box::new(h_conn)\n    }\n}\n\nimpl HttpConn for Http09Conn {\n    fn send_requests(\n        &mut self, conn: &mut quiche::Connection, target_path: &Option<String>,\n    ) {\n        let mut reqs_done = 0;\n\n        for req in self.reqs.iter_mut().skip(self.reqs_sent) {\n            match conn.stream_send(\n                self.stream_id,\n                req.request_line.as_bytes(),\n                true,\n            ) {\n                Ok(v) => v,\n\n                Err(quiche::Error::StreamLimit) => {\n                    debug!(\"not enough stream credits, retry later...\");\n                    break;\n                },\n\n                Err(e) => {\n                    error!(\"failed to send request {e:?}\");\n                    break;\n                },\n            };\n\n            debug!(\"sending HTTP request {:?}\", req.request_line);\n\n            req.stream_id = Some(self.stream_id);\n            req.response_writer =\n                make_resource_writer(&req.url, target_path, req.cardinal);\n\n            self.stream_id += 4;\n\n            reqs_done += 1;\n        }\n\n        self.reqs_sent += reqs_done;\n    }\n\n    fn handle_responses(\n        &mut self, conn: &mut quiche::Connection, buf: &mut [u8],\n        req_start: &std::time::Instant,\n    ) {\n        // Process all readable streams.\n        for s in conn.readable() {\n            while let Ok((read, fin)) = conn.stream_recv(s, buf) {\n                trace!(\"received {read} bytes\");\n\n                let stream_buf = &buf[..read];\n\n                trace!(\n                    \"stream {} has {} bytes (fin? {})\",\n                    s,\n                    stream_buf.len(),\n                    fin\n                );\n\n                let req = self\n                    .reqs\n                    .iter_mut()\n                    .find(|r| r.stream_id == Some(s))\n                    .unwrap();\n\n                match &mut req.response_writer {\n                    Some(rw) => {\n                        rw.write_all(&buf[..read]).ok();\n                    },\n\n                    None => {\n                        self.output_sink.borrow_mut()(unsafe {\n                            String::from_utf8_unchecked(stream_buf.to_vec())\n                        });\n                    },\n                }\n\n                // The server reported that it has no more data to send on\n                // a client-initiated\n                // bidirectional stream, which means\n                // we got the full response. If all responses are received\n                // then close the connection.\n                if &s % 4 == 0 && fin {\n                    self.reqs_complete += 1;\n                    let reqs_count = self.reqs.len();\n\n                    debug!(\n                        \"{}/{} responses received\",\n                        self.reqs_complete, reqs_count\n                    );\n\n                    if self.reqs_complete == reqs_count {\n                        info!(\n                            \"{}/{} response(s) received in {:?}, closing...\",\n                            self.reqs_complete,\n                            reqs_count,\n                            req_start.elapsed()\n                        );\n\n                        match conn.close(true, 0x00, b\"kthxbye\") {\n                            // Already closed.\n                            Ok(_) | Err(quiche::Error::Done) => (),\n\n                            Err(e) => panic!(\"error closing conn: {e:?}\"),\n                        }\n\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    fn report_incomplete(&self, start: &std::time::Instant) -> bool {\n        if self.reqs_complete != self.reqs.len() {\n            error!(\n                \"connection timed out after {:?} and only completed {}/{} requests\",\n                start.elapsed(),\n                self.reqs_complete,\n                self.reqs.len()\n            );\n\n            return true;\n        }\n\n        false\n    }\n\n    fn handle_requests(\n        &mut self, conn: &mut quiche::Connection,\n        partial_requests: &mut HashMap<u64, PartialRequest>,\n        partial_responses: &mut HashMap<u64, PartialResponse>, root: &str,\n        index: &str, buf: &mut [u8],\n    ) -> quiche::h3::Result<()> {\n        // Process all readable streams.\n        for s in conn.readable() {\n            while let Ok((read, fin)) = conn.stream_recv(s, buf) {\n                trace!(\"{} received {} bytes\", conn.trace_id(), read);\n\n                let stream_buf = &buf[..read];\n\n                trace!(\n                    \"{} stream {} has {} bytes (fin? {})\",\n                    conn.trace_id(),\n                    s,\n                    stream_buf.len(),\n                    fin\n                );\n\n                let stream_buf =\n                    if let Some(partial) = partial_requests.get_mut(&s) {\n                        partial.req.extend_from_slice(stream_buf);\n\n                        if !partial.req.ends_with(b\"\\r\\n\") {\n                            return Ok(());\n                        }\n\n                        &partial.req\n                    } else {\n                        if !stream_buf.ends_with(b\"\\r\\n\") {\n                            let request = PartialRequest {\n                                req: stream_buf.to_vec(),\n                            };\n\n                            partial_requests.insert(s, request);\n                            return Ok(());\n                        }\n\n                        stream_buf\n                    };\n\n                if stream_buf.starts_with(b\"GET \") {\n                    let uri = &stream_buf[4..stream_buf.len() - 2];\n                    let uri = String::from_utf8(uri.to_vec()).unwrap();\n                    let uri = String::from(uri.lines().next().unwrap());\n                    let uri = path::Path::new(&uri);\n                    let mut path = path::PathBuf::from(root);\n\n                    partial_requests.remove(&s);\n\n                    for c in uri.components() {\n                        if let path::Component::Normal(v) = c {\n                            path.push(v)\n                        }\n                    }\n\n                    path = autoindex(path, index);\n\n                    info!(\n                        \"{} got GET request for {:?} on stream {}\",\n                        conn.trace_id(),\n                        path,\n                        s\n                    );\n\n                    let body = std::fs::read(path.as_path())\n                        .unwrap_or_else(|_| b\"Not Found!\\r\\n\".to_vec());\n\n                    info!(\n                        \"{} sending response of size {} on stream {}\",\n                        conn.trace_id(),\n                        body.len(),\n                        s\n                    );\n\n                    let written = match conn.stream_send(s, &body, true) {\n                        Ok(v) => v,\n\n                        Err(quiche::Error::Done) => 0,\n\n                        Err(e) => {\n                            error!(\n                                \"{} stream send failed {:?}\",\n                                conn.trace_id(),\n                                e\n                            );\n                            return Err(From::from(e));\n                        },\n                    };\n\n                    if written < body.len() {\n                        let response = PartialResponse {\n                            headers: None,\n                            priority: None,\n                            body,\n                            written,\n                        };\n\n                        partial_responses.insert(s, response);\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn handle_writable(\n        &mut self, conn: &mut quiche::Connection,\n        partial_responses: &mut HashMap<u64, PartialResponse>, stream_id: u64,\n    ) {\n        let stream_cap = conn.stream_capacity(stream_id);\n\n        debug!(\n            \"{} response stream {} is writable with capacity {:?}\",\n            conn.trace_id(),\n            stream_id,\n            stream_cap,\n        );\n\n        if !partial_responses.contains_key(&stream_id) {\n            return;\n        }\n\n        let resp = partial_responses.get_mut(&stream_id).unwrap();\n        let body = &resp.body[resp.written..];\n\n        let written = match conn.stream_send(stream_id, body, true) {\n            Ok(v) => v,\n\n            Err(quiche::Error::Done) => 0,\n\n            Err(e) => {\n                partial_responses.remove(&stream_id);\n\n                error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n                return;\n            },\n        };\n\n        resp.written += written;\n\n        if resp.written == resp.body.len() {\n            partial_responses.remove(&stream_id);\n        }\n    }\n}\n\npub struct Http3DgramSender {\n    dgram_count: u64,\n    pub dgram_content: String,\n    pub flow_id: u64,\n    pub dgrams_sent: u64,\n}\n\nimpl Http3DgramSender {\n    pub fn new(dgram_count: u64, dgram_content: String, flow_id: u64) -> Self {\n        Self {\n            dgram_count,\n            dgram_content,\n            flow_id,\n            dgrams_sent: 0,\n        }\n    }\n}\n\nfn make_h3_config(\n    max_field_section_size: Option<u64>, qpack_max_table_capacity: Option<u64>,\n    qpack_blocked_streams: Option<u64>,\n) -> quiche::h3::Config {\n    let mut config = quiche::h3::Config::new().unwrap();\n\n    if let Some(v) = max_field_section_size {\n        config.set_max_field_section_size(v);\n    }\n\n    if let Some(v) = qpack_max_table_capacity {\n        // quiche doesn't support dynamic QPACK, so clamp to 0 for now.\n        config.set_qpack_max_table_capacity(v.clamp(0, 0));\n    }\n\n    if let Some(v) = qpack_blocked_streams {\n        // quiche doesn't support dynamic QPACK, so clamp to 0 for now.\n        config.set_qpack_blocked_streams(v.clamp(0, 0));\n    }\n\n    config\n}\n\npub struct Http3Conn {\n    h3_conn: quiche::h3::Connection,\n    reqs_hdrs_sent: usize,\n    reqs_complete: usize,\n    largest_processed_request: u64,\n    reqs: Vec<Http3Request>,\n    body: Option<Vec<u8>>,\n    sent_body_bytes: HashMap<u64, usize>,\n    dump_json: bool,\n    dgram_sender: Option<Http3DgramSender>,\n    output_sink: Rc<RefCell<dyn FnMut(String)>>,\n}\n\nimpl Http3Conn {\n    #[allow(clippy::too_many_arguments)]\n    pub fn with_urls(\n        conn: &mut quiche::Connection, urls: &[url::Url], reqs_cardinal: u64,\n        req_headers: &[String], body: &Option<Vec<u8>>, method: &str,\n        send_priority_update: bool, max_field_section_size: Option<u64>,\n        qpack_max_table_capacity: Option<u64>,\n        qpack_blocked_streams: Option<u64>, dump_json: Option<usize>,\n        dgram_sender: Option<Http3DgramSender>,\n        output_sink: Rc<RefCell<dyn FnMut(String)>>,\n    ) -> Box<dyn HttpConn> {\n        let mut reqs = Vec::new();\n        for url in urls {\n            for i in 1..=reqs_cardinal {\n                let authority = match url.port() {\n                    Some(port) => format!(\"{}:{}\", url.host_str().unwrap(), port),\n\n                    None => url.host_str().unwrap().to_string(),\n                };\n\n                let mut hdrs = vec![\n                    quiche::h3::Header::new(b\":method\", method.as_bytes()),\n                    quiche::h3::Header::new(b\":scheme\", url.scheme().as_bytes()),\n                    quiche::h3::Header::new(b\":authority\", authority.as_bytes()),\n                    quiche::h3::Header::new(\n                        b\":path\",\n                        url[url::Position::BeforePath..].as_bytes(),\n                    ),\n                    quiche::h3::Header::new(b\"user-agent\", b\"quiche\"),\n                ];\n\n                let priority = if send_priority_update {\n                    priority_from_query_string(url)\n                } else {\n                    None\n                };\n\n                // Add custom headers to the request.\n                for header in req_headers {\n                    let header_split: Vec<&str> =\n                        header.splitn(2, \": \").collect();\n\n                    if header_split.len() != 2 {\n                        panic!(\"malformed header provided - \\\"{header}\\\"\");\n                    }\n\n                    hdrs.push(quiche::h3::Header::new(\n                        header_split[0].as_bytes(),\n                        header_split[1].as_bytes(),\n                    ));\n                }\n\n                if body.is_some() {\n                    hdrs.push(quiche::h3::Header::new(\n                        b\"content-length\",\n                        body.as_ref().unwrap().len().to_string().as_bytes(),\n                    ));\n                }\n\n                reqs.push(Http3Request {\n                    url: url.clone(),\n                    cardinal: i,\n                    hdrs,\n                    priority,\n                    response_hdrs: Vec::new(),\n                    response_body: Vec::new(),\n                    response_body_max: dump_json.unwrap_or_default(),\n                    stream_id: None,\n                    response_writer: None,\n                });\n            }\n        }\n\n        let h_conn = Http3Conn {\n            h3_conn: quiche::h3::Connection::with_transport(\n                conn,\n                &make_h3_config(\n                    max_field_section_size,\n                    qpack_max_table_capacity,\n                    qpack_blocked_streams,\n                ),\n            ).expect(\"Unable to create HTTP/3 connection, check the server's uni stream limit and window size\"),\n            reqs_hdrs_sent: 0,\n            reqs_complete: 0,\n            largest_processed_request: 0,\n            reqs,\n            body: body.as_ref().map(|b| b.to_vec()),\n            sent_body_bytes: HashMap::new(),\n            dump_json: dump_json.is_some(),\n            dgram_sender,\n            output_sink,\n        };\n\n        Box::new(h_conn)\n    }\n\n    pub fn with_conn(\n        conn: &mut quiche::Connection, max_field_section_size: Option<u64>,\n        qpack_max_table_capacity: Option<u64>,\n        qpack_blocked_streams: Option<u64>,\n        dgram_sender: Option<Http3DgramSender>,\n        output_sink: Rc<RefCell<dyn FnMut(String)>>,\n    ) -> std::result::Result<Box<dyn HttpConn>, String> {\n        let h3_conn = quiche::h3::Connection::with_transport(\n            conn,\n            &make_h3_config(\n                max_field_section_size,\n                qpack_max_table_capacity,\n                qpack_blocked_streams,\n            ),\n        ).map_err(|_| \"Unable to create HTTP/3 connection, check the client's uni stream limit and window size\")?;\n\n        let h_conn = Http3Conn {\n            h3_conn,\n            reqs_hdrs_sent: 0,\n            reqs_complete: 0,\n            largest_processed_request: 0,\n            reqs: Vec::new(),\n            body: None,\n            sent_body_bytes: HashMap::new(),\n            dump_json: false,\n            dgram_sender,\n            output_sink,\n        };\n\n        Ok(Box::new(h_conn))\n    }\n\n    /// Builds an HTTP/3 response given a request.\n    fn build_h3_response(\n        root: &str, index: &str, request: &[quiche::h3::Header],\n    ) -> Http3ResponseBuilderResult {\n        let mut file_path = path::PathBuf::from(root);\n        let mut scheme = None;\n        let mut authority = None;\n        let mut host = None;\n        let mut path = None;\n        let mut method = None;\n        let mut priority = vec![];\n\n        // Parse some of the request headers.\n        for hdr in request {\n            match hdr.name() {\n                b\":scheme\" => {\n                    if scheme.is_some() {\n                        return Err((\n                            H3_MESSAGE_ERROR,\n                            \":scheme cannot be duplicated\".to_string(),\n                        ));\n                    }\n\n                    scheme = Some(std::str::from_utf8(hdr.value()).unwrap());\n                },\n\n                b\":authority\" => {\n                    if authority.is_some() {\n                        return Err((\n                            H3_MESSAGE_ERROR,\n                            \":authority cannot be duplicated\".to_string(),\n                        ));\n                    }\n\n                    authority = Some(std::str::from_utf8(hdr.value()).unwrap());\n                },\n\n                b\":path\" => {\n                    if path.is_some() {\n                        return Err((\n                            H3_MESSAGE_ERROR,\n                            \":path cannot be duplicated\".to_string(),\n                        ));\n                    }\n\n                    path = Some(std::str::from_utf8(hdr.value()).unwrap())\n                },\n\n                b\":method\" => {\n                    if method.is_some() {\n                        return Err((\n                            H3_MESSAGE_ERROR,\n                            \":method cannot be duplicated\".to_string(),\n                        ));\n                    }\n\n                    method = Some(std::str::from_utf8(hdr.value()).unwrap())\n                },\n\n                b\":protocol\" => {\n                    return Err((\n                        H3_MESSAGE_ERROR,\n                        \":protocol not supported\".to_string(),\n                    ));\n                },\n\n                b\"priority\" => priority = hdr.value().to_vec(),\n\n                b\"host\" => host = Some(std::str::from_utf8(hdr.value()).unwrap()),\n\n                _ => (),\n            }\n        }\n\n        let decided_method = match method {\n            Some(method) => {\n                match method {\n                    \"\" =>\n                        return Err((\n                            H3_MESSAGE_ERROR,\n                            \":method value cannot be empty\".to_string(),\n                        )),\n\n                    \"CONNECT\" => {\n                        // not allowed\n                        let headers = vec![\n                            quiche::h3::Header::new(\n                                b\":status\",\n                                \"405\".to_string().as_bytes(),\n                            ),\n                            quiche::h3::Header::new(b\"server\", b\"quiche\"),\n                        ];\n\n                        return Ok((headers, b\"\".to_vec(), Default::default()));\n                    },\n\n                    _ => method,\n                }\n            },\n\n            None =>\n                return Err((\n                    H3_MESSAGE_ERROR,\n                    \":method cannot be missing\".to_string(),\n                )),\n        };\n\n        let decided_scheme = match scheme {\n            Some(scheme) => {\n                if scheme != \"http\" && scheme != \"https\" {\n                    let headers = vec![\n                        quiche::h3::Header::new(\n                            b\":status\",\n                            \"400\".to_string().as_bytes(),\n                        ),\n                        quiche::h3::Header::new(b\"server\", b\"quiche\"),\n                    ];\n\n                    return Ok((\n                        headers,\n                        b\"Invalid scheme\".to_vec(),\n                        Default::default(),\n                    ));\n                }\n\n                scheme\n            },\n\n            None =>\n                return Err((\n                    H3_MESSAGE_ERROR,\n                    \":scheme cannot be missing\".to_string(),\n                )),\n        };\n\n        let decided_host = match (authority, host) {\n            (None, Some(\"\")) =>\n                return Err((\n                    H3_MESSAGE_ERROR,\n                    \"host value cannot be empty\".to_string(),\n                )),\n\n            (Some(\"\"), None) =>\n                return Err((\n                    H3_MESSAGE_ERROR,\n                    \":authority value cannot be empty\".to_string(),\n                )),\n\n            (Some(\"\"), Some(\"\")) =>\n                return Err((\n                    H3_MESSAGE_ERROR,\n                    \":authority and host value cannot be empty\".to_string(),\n                )),\n\n            (None, None) =>\n                return Err((\n                    H3_MESSAGE_ERROR,\n                    \":authority and host missing\".to_string(),\n                )),\n\n            // Any other combo, prefer :authority\n            (..) => authority.unwrap(),\n        };\n\n        let decided_path = match path {\n            Some(\"\") =>\n                return Err((\n                    H3_MESSAGE_ERROR,\n                    \":path value cannot be empty\".to_string(),\n                )),\n\n            None =>\n                return Err((\n                    H3_MESSAGE_ERROR,\n                    \":path cannot be missing\".to_string(),\n                )),\n\n            Some(path) => path,\n        };\n\n        let url = format!(\"{decided_scheme}://{decided_host}{decided_path}\");\n        let url = url::Url::parse(&url).unwrap();\n\n        let pathbuf = path::PathBuf::from(url.path());\n        let pathbuf = autoindex(pathbuf, index);\n\n        // Priority query string takes precedence over the header.\n        // So replace the header with one built here.\n        let query_priority = priority_field_value_from_query_string(&url);\n\n        if let Some(p) = query_priority {\n            priority = p.as_bytes().to_vec();\n        }\n\n        let (status, body) = match decided_method {\n            \"GET\" => {\n                const STREAM_BYTES_PREFIX: &str = \"/stream-bytes/\";\n                const STREAM_BYTES_FILL: u8 = 0x57;\n\n                if let Some(suffix) = url.path().strip_prefix(STREAM_BYTES_PREFIX)\n                {\n                    let n = suffix.parse::<usize>().unwrap_or(0);\n                    (200, vec![STREAM_BYTES_FILL; n])\n                } else {\n                    for c in pathbuf.components() {\n                        if let path::Component::Normal(v) = c {\n                            file_path.push(v)\n                        }\n                    }\n\n                    match std::fs::read(file_path.as_path()) {\n                        Ok(data) => (200, data),\n\n                        Err(_) => (404, b\"Not Found!\".to_vec()),\n                    }\n                }\n            },\n\n            _ => (405, Vec::new()),\n        };\n\n        let headers = vec![\n            quiche::h3::Header::new(b\":status\", status.to_string().as_bytes()),\n            quiche::h3::Header::new(b\"server\", b\"quiche\"),\n            quiche::h3::Header::new(\n                b\"content-length\",\n                body.len().to_string().as_bytes(),\n            ),\n        ];\n\n        Ok((headers, body, priority))\n    }\n}\n\nimpl HttpConn for Http3Conn {\n    fn send_requests(\n        &mut self, conn: &mut quiche::Connection, target_path: &Option<String>,\n    ) {\n        let mut reqs_done = 0;\n\n        // First send headers.\n        for req in self.reqs.iter_mut().skip(self.reqs_hdrs_sent) {\n            let s = match self.h3_conn.send_request(\n                conn,\n                &req.hdrs,\n                self.body.is_none(),\n            ) {\n                Ok(v) => v,\n\n                Err(quiche::h3::Error::TransportError(\n                    quiche::Error::StreamLimit,\n                )) => {\n                    debug!(\"not enough stream credits, retry later...\");\n                    break;\n                },\n\n                Err(quiche::h3::Error::StreamBlocked) => {\n                    debug!(\"stream is blocked, retry later...\");\n                    break;\n                },\n\n                Err(e) => {\n                    error!(\"failed to send request {e:?}\");\n                    break;\n                },\n            };\n\n            debug!(\"Sent HTTP request {:?}\", &req.hdrs);\n\n            if let Some(priority) = &req.priority {\n                // If sending the priority fails, don't try again.\n                self.h3_conn\n                    .send_priority_update_for_request(conn, s, priority)\n                    .ok();\n            }\n\n            req.stream_id = Some(s);\n            req.response_writer =\n                make_resource_writer(&req.url, target_path, req.cardinal);\n            self.sent_body_bytes.insert(s, 0);\n\n            reqs_done += 1;\n        }\n        self.reqs_hdrs_sent += reqs_done;\n\n        // Then send any remaining body.\n        if let Some(body) = &self.body {\n            for (stream_id, sent_bytes) in self.sent_body_bytes.iter_mut() {\n                if *sent_bytes == body.len() {\n                    continue;\n                }\n\n                // Always try to send all remaining bytes, so always set fin to\n                // true.\n                let sent = match self.h3_conn.send_body(\n                    conn,\n                    *stream_id,\n                    &body[*sent_bytes..],\n                    true,\n                ) {\n                    Ok(v) => v,\n\n                    Err(quiche::h3::Error::Done) => 0,\n\n                    Err(e) => {\n                        error!(\"failed to send request body {e:?}\");\n                        continue;\n                    },\n                };\n\n                *sent_bytes += sent;\n            }\n        }\n\n        // And finally any DATAGRAMS.\n        if let Some(ds) = self.dgram_sender.as_mut() {\n            let mut dgrams_done = 0;\n\n            for _ in ds.dgrams_sent..ds.dgram_count {\n                match send_h3_dgram(conn, ds.flow_id, ds.dgram_content.as_bytes())\n                {\n                    Ok(v) => v,\n\n                    Err(e) => {\n                        error!(\"failed to send dgram {e:?}\");\n                        break;\n                    },\n                }\n\n                dgrams_done += 1;\n            }\n\n            ds.dgrams_sent += dgrams_done;\n        }\n    }\n\n    fn handle_responses(\n        &mut self, conn: &mut quiche::Connection, buf: &mut [u8],\n        req_start: &std::time::Instant,\n    ) {\n        loop {\n            match self.h3_conn.poll(conn) {\n                Ok((stream_id, quiche::h3::Event::Headers { list, .. })) => {\n                    debug!(\n                        \"got response headers {:?} on stream id {}\",\n                        hdrs_to_strings(&list),\n                        stream_id\n                    );\n\n                    let req = self\n                        .reqs\n                        .iter_mut()\n                        .find(|r| r.stream_id == Some(stream_id))\n                        .unwrap();\n\n                    req.response_hdrs = list;\n                },\n\n                Ok((stream_id, quiche::h3::Event::Data)) => {\n                    while let Ok(read) =\n                        self.h3_conn.recv_body(conn, stream_id, buf)\n                    {\n                        debug!(\n                            \"got {read} bytes of response data on stream {stream_id}\"\n                        );\n\n                        let req = self\n                            .reqs\n                            .iter_mut()\n                            .find(|r| r.stream_id == Some(stream_id))\n                            .unwrap();\n\n                        let len = std::cmp::min(\n                            read,\n                            req.response_body_max - req.response_body.len(),\n                        );\n                        req.response_body.extend_from_slice(&buf[..len]);\n\n                        match &mut req.response_writer {\n                            Some(rw) => {\n                                rw.write_all(&buf[..read]).ok();\n                            },\n\n                            None =>\n                                if !self.dump_json {\n                                    self.output_sink.borrow_mut()(unsafe {\n                                        String::from_utf8_unchecked(\n                                            buf[..read].to_vec(),\n                                        )\n                                    });\n                                },\n                        }\n                    }\n                },\n\n                Ok((_stream_id, quiche::h3::Event::Finished)) => {\n                    self.reqs_complete += 1;\n                    let reqs_count = self.reqs.len();\n\n                    debug!(\n                        \"{}/{} responses received\",\n                        self.reqs_complete, reqs_count\n                    );\n\n                    if self.reqs_complete == reqs_count {\n                        info!(\n                            \"{}/{} response(s) received in {:?}, closing...\",\n                            self.reqs_complete,\n                            reqs_count,\n                            req_start.elapsed()\n                        );\n\n                        if self.dump_json {\n                            dump_json(\n                                &self.reqs,\n                                &mut *self.output_sink.borrow_mut(),\n                            );\n                        }\n\n                        match conn.close(true, 0x100, b\"kthxbye\") {\n                            // Already closed.\n                            Ok(_) | Err(quiche::Error::Done) => (),\n\n                            Err(e) => panic!(\"error closing conn: {e:?}\"),\n                        }\n\n                        break;\n                    }\n                },\n\n                Ok((_stream_id, quiche::h3::Event::Reset(e))) => {\n                    error!(\"request was reset by peer with {e}, closing...\");\n\n                    match conn.close(true, 0x100, b\"kthxbye\") {\n                        // Already closed.\n                        Ok(_) | Err(quiche::Error::Done) => (),\n\n                        Err(e) => panic!(\"error closing conn: {e:?}\"),\n                    }\n\n                    break;\n                },\n\n                Ok((\n                    prioritized_element_id,\n                    quiche::h3::Event::PriorityUpdate,\n                )) => {\n                    info!(\n                        \"{} PRIORITY_UPDATE triggered for element ID={}\",\n                        conn.trace_id(),\n                        prioritized_element_id\n                    );\n                },\n\n                Ok((goaway_id, quiche::h3::Event::GoAway)) => {\n                    info!(\n                        \"{} got GOAWAY with ID {} \",\n                        conn.trace_id(),\n                        goaway_id\n                    );\n                },\n\n                Err(quiche::h3::Error::Done) => {\n                    break;\n                },\n\n                Err(e) => {\n                    error!(\"HTTP/3 processing failed: {e:?}\");\n\n                    break;\n                },\n            }\n        }\n\n        // Process datagram-related events.\n        while let Ok(len) = conn.dgram_recv(buf) {\n            let mut b = octets::Octets::with_slice(buf);\n            if let Ok(flow_id) = b.get_varint() {\n                info!(\n                    \"Received DATAGRAM flow_id={} len={} data={:?}\",\n                    flow_id,\n                    len,\n                    buf[b.off()..len].to_vec()\n                );\n            }\n        }\n    }\n\n    fn report_incomplete(&self, start: &std::time::Instant) -> bool {\n        if self.reqs_complete != self.reqs.len() {\n            error!(\n                \"connection timed out after {:?} and only completed {}/{} requests\",\n                start.elapsed(),\n                self.reqs_complete,\n                self.reqs.len()\n            );\n\n            if self.dump_json {\n                dump_json(&self.reqs, &mut *self.output_sink.borrow_mut());\n            }\n\n            return true;\n        }\n\n        false\n    }\n\n    fn handle_requests(\n        &mut self, conn: &mut quiche::Connection,\n        _partial_requests: &mut HashMap<u64, PartialRequest>,\n        partial_responses: &mut HashMap<u64, PartialResponse>, root: &str,\n        index: &str, buf: &mut [u8],\n    ) -> quiche::h3::Result<()> {\n        // Process HTTP stream-related events.\n        //\n        // This loops over any and all received HTTP requests and sends just the\n        // HTTP response headers.\n        loop {\n            match self.h3_conn.poll(conn) {\n                Ok((stream_id, quiche::h3::Event::Headers { list, .. })) => {\n                    info!(\n                        \"{} got request {:?} on stream id {}\",\n                        conn.trace_id(),\n                        hdrs_to_strings(&list),\n                        stream_id\n                    );\n\n                    self.largest_processed_request =\n                        std::cmp::max(self.largest_processed_request, stream_id);\n\n                    // We decide the response based on headers alone, so\n                    // stop reading the request stream so that any body\n                    // is ignored and pointless Data events are not\n                    // generated.\n                    conn.stream_shutdown(stream_id, quiche::Shutdown::Read, 0)\n                        .unwrap();\n\n                    let (mut headers, body, mut priority) =\n                        match Http3Conn::build_h3_response(root, index, &list) {\n                            Ok(v) => v,\n\n                            Err((error_code, _)) => {\n                                conn.stream_shutdown(\n                                    stream_id,\n                                    quiche::Shutdown::Write,\n                                    error_code,\n                                )\n                                .unwrap();\n                                continue;\n                            },\n                        };\n\n                    match self.h3_conn.take_last_priority_update(stream_id) {\n                        Ok(v) => {\n                            priority = v;\n                        },\n\n                        Err(quiche::h3::Error::Done) => (),\n\n                        Err(e) => error!(\n                            \"{} error taking PRIORITY_UPDATE {}\",\n                            conn.trace_id(),\n                            e\n                        ),\n                    }\n\n                    if !priority.is_empty() {\n                        headers.push(quiche::h3::Header::new(\n                            b\"priority\",\n                            priority.as_slice(),\n                        ));\n                    }\n\n                    #[cfg(feature = \"sfv\")]\n                    let priority =\n                        quiche::h3::Priority::try_from(priority.as_slice())\n                            .unwrap_or_default();\n\n                    #[cfg(not(feature = \"sfv\"))]\n                    let priority = quiche::h3::Priority::default();\n\n                    info!(\n                        \"{} prioritizing response on stream {} as {:?}\",\n                        conn.trace_id(),\n                        stream_id,\n                        priority\n                    );\n\n                    match self.h3_conn.send_response_with_priority(\n                        conn, stream_id, &headers, &priority, false,\n                    ) {\n                        Ok(v) => v,\n\n                        Err(quiche::h3::Error::StreamBlocked) => {\n                            let response = PartialResponse {\n                                headers: Some(headers),\n                                priority: Some(priority),\n                                body,\n                                written: 0,\n                            };\n\n                            partial_responses.insert(stream_id, response);\n                            continue;\n                        },\n\n                        Err(e) => {\n                            error!(\n                                \"{} stream send failed {:?}\",\n                                conn.trace_id(),\n                                e\n                            );\n\n                            break;\n                        },\n                    }\n\n                    let response = PartialResponse {\n                        headers: None,\n                        priority: None,\n                        body,\n                        written: 0,\n                    };\n\n                    partial_responses.insert(stream_id, response);\n                },\n\n                Ok((stream_id, quiche::h3::Event::Data)) => {\n                    info!(\n                        \"{} got data on stream id {}\",\n                        conn.trace_id(),\n                        stream_id\n                    );\n                },\n\n                Ok((_stream_id, quiche::h3::Event::Finished)) => (),\n\n                Ok((_stream_id, quiche::h3::Event::Reset { .. })) => (),\n\n                Ok((\n                    prioritized_element_id,\n                    quiche::h3::Event::PriorityUpdate,\n                )) => {\n                    info!(\n                        \"{} PRIORITY_UPDATE triggered for element ID={}\",\n                        conn.trace_id(),\n                        prioritized_element_id\n                    );\n                },\n\n                Ok((goaway_id, quiche::h3::Event::GoAway)) => {\n                    trace!(\n                        \"{} got GOAWAY with ID {} \",\n                        conn.trace_id(),\n                        goaway_id\n                    );\n                    self.h3_conn\n                        .send_goaway(conn, self.largest_processed_request)?;\n                },\n\n                Err(quiche::h3::Error::Done) => {\n                    break;\n                },\n\n                Err(e) => {\n                    error!(\"{} HTTP/3 error {:?}\", conn.trace_id(), e);\n\n                    return Err(e);\n                },\n            }\n        }\n\n        // Visit all writable response streams to send HTTP content.\n        for stream_id in writable_response_streams(conn) {\n            self.handle_writable(conn, partial_responses, stream_id);\n        }\n\n        // Process datagram-related events.\n        while let Ok(len) = conn.dgram_recv(buf) {\n            let mut b = octets::Octets::with_slice(buf);\n            if let Ok(flow_id) = b.get_varint() {\n                info!(\n                    \"Received DATAGRAM flow_id={} len={} data={:?}\",\n                    flow_id,\n                    len,\n                    buf[b.off()..len].to_vec()\n                );\n            }\n        }\n\n        if let Some(ds) = self.dgram_sender.as_mut() {\n            let mut dgrams_done = 0;\n\n            for _ in ds.dgrams_sent..ds.dgram_count {\n                match send_h3_dgram(conn, ds.flow_id, ds.dgram_content.as_bytes())\n                {\n                    Ok(v) => v,\n\n                    Err(e) => {\n                        error!(\"failed to send dgram {e:?}\");\n                        break;\n                    },\n                }\n\n                dgrams_done += 1;\n            }\n\n            ds.dgrams_sent += dgrams_done;\n        }\n\n        Ok(())\n    }\n\n    fn handle_writable(\n        &mut self, conn: &mut quiche::Connection,\n        partial_responses: &mut HashMap<u64, PartialResponse>, stream_id: u64,\n    ) {\n        let stream_cap = conn.stream_capacity(stream_id);\n\n        debug!(\n            \"{} response stream {} is writable with capacity {:?}\",\n            conn.trace_id(),\n            stream_id,\n            stream_cap,\n        );\n\n        if !partial_responses.contains_key(&stream_id) {\n            return;\n        }\n\n        let resp = partial_responses.get_mut(&stream_id).unwrap();\n\n        if let (Some(headers), Some(priority)) = (&resp.headers, &resp.priority) {\n            match self.h3_conn.send_response_with_priority(\n                conn, stream_id, headers, priority, false,\n            ) {\n                Ok(_) => (),\n\n                Err(quiche::h3::Error::StreamBlocked) => {\n                    return;\n                },\n\n                Err(e) => {\n                    error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n                    return;\n                },\n            }\n        }\n\n        resp.headers = None;\n        resp.priority = None;\n\n        let body = &resp.body[resp.written..];\n\n        let written = match self.h3_conn.send_body(conn, stream_id, body, true) {\n            Ok(v) => v,\n\n            Err(quiche::h3::Error::Done) => 0,\n\n            Err(e) => {\n                partial_responses.remove(&stream_id);\n\n                error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n                return;\n            },\n        };\n\n        resp.written += written;\n\n        if resp.written == resp.body.len() {\n            partial_responses.remove(&stream_id);\n        }\n    }\n}\n"
  },
  {
    "path": "apps/src/lib.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[macro_use]\nextern crate log;\n\npub mod args;\npub mod client;\npub mod common;\npub mod sendto;\n"
  },
  {
    "path": "apps/src/sendto.rs",
    "content": "// Copyright (C) 2021, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::cmp;\n\nuse std::io;\n\n/// For Linux, try to detect GSO is available.\n#[cfg(target_os = \"linux\")]\npub fn detect_gso(socket: &mio::net::UdpSocket, segment_size: usize) -> bool {\n    use nix::sys::socket::setsockopt;\n    use nix::sys::socket::sockopt::UdpGsoSegment;\n    use std::os::unix::io::AsFd;\n\n    setsockopt(&socket.as_fd(), UdpGsoSegment, &(segment_size as i32)).is_ok()\n}\n\n/// For non-Linux, there is no GSO support.\n#[cfg(not(target_os = \"linux\"))]\npub fn detect_gso(_socket: &mio::net::UdpSocket, _segment_size: usize) -> bool {\n    false\n}\n\n/// Send packets using sendmsg() with GSO.\n#[cfg(target_os = \"linux\")]\nfn send_to_gso_pacing(\n    socket: &mio::net::UdpSocket, buf: &[u8], send_info: &quiche::SendInfo,\n    segment_size: usize,\n) -> io::Result<usize> {\n    use nix::sys::socket::sendmsg;\n    use nix::sys::socket::ControlMessage;\n    use nix::sys::socket::MsgFlags;\n    use nix::sys::socket::SockaddrStorage;\n    use std::io::IoSlice;\n    use std::os::unix::io::AsRawFd;\n\n    let iov = [IoSlice::new(buf)];\n    let segment_size = segment_size as u16;\n    let dst = SockaddrStorage::from(send_info.to);\n    let sockfd = socket.as_raw_fd();\n\n    // GSO option.\n    let cmsg_gso = ControlMessage::UdpGsoSegments(&segment_size);\n\n    // Pacing option.\n    let send_time = std_time_to_u64(&send_info.at);\n    let cmsg_txtime = ControlMessage::TxTime(&send_time);\n\n    match sendmsg(\n        sockfd,\n        &iov,\n        &[cmsg_gso, cmsg_txtime],\n        MsgFlags::empty(),\n        Some(&dst),\n    ) {\n        Ok(v) => Ok(v),\n        Err(e) => Err(e.into()),\n    }\n}\n\n/// For non-Linux platforms.\n#[cfg(not(target_os = \"linux\"))]\nfn send_to_gso_pacing(\n    _socket: &mio::net::UdpSocket, _buf: &[u8], _send_info: &quiche::SendInfo,\n    _segment_size: usize,\n) -> io::Result<usize> {\n    panic!(\"send_to_gso() should not be called on non-linux platforms\");\n}\n\n/// A wrapper function of send_to().\n///\n/// When GSO and SO_TXTIME are enabled, send packets using send_to_gso().\n/// Otherwise, send packets using socket.send_to().\npub fn send_to(\n    socket: &mio::net::UdpSocket, buf: &[u8], send_info: &quiche::SendInfo,\n    segment_size: usize, pacing: bool, enable_gso: bool,\n) -> io::Result<usize> {\n    if pacing && enable_gso {\n        match send_to_gso_pacing(socket, buf, send_info, segment_size) {\n            Ok(v) => {\n                return Ok(v);\n            },\n            Err(e) => {\n                return Err(e);\n            },\n        }\n    }\n\n    let mut off = 0;\n    let mut left = buf.len();\n    let mut written = 0;\n\n    while left > 0 {\n        let pkt_len = cmp::min(left, segment_size);\n\n        match socket.send_to(&buf[off..off + pkt_len], send_info.to) {\n            Ok(v) => {\n                written += v;\n            },\n            Err(e) => return Err(e),\n        }\n\n        off += pkt_len;\n        left -= pkt_len;\n    }\n\n    Ok(written)\n}\n\n#[cfg(target_os = \"linux\")]\nfn std_time_to_u64(time: &std::time::Instant) -> u64 {\n    const NANOS_PER_SEC: u64 = 1_000_000_000;\n\n    const INSTANT_ZERO: std::time::Instant =\n        unsafe { std::mem::transmute(std::time::UNIX_EPOCH) };\n\n    let raw_time = time.duration_since(INSTANT_ZERO);\n\n    let sec = raw_time.as_secs();\n    let nsec = raw_time.subsec_nanos();\n\n    sec * NANOS_PER_SEC + nsec as u64\n}\n"
  },
  {
    "path": "buffer-pool/Cargo.toml",
    "content": "[package]\nname = \"buffer-pool\"\nversion = \"0.2.1\"\nrepository = { workspace = true }\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\ndescription = \"Pooled in-memory buffers\"\n\n[dependencies]\ncrossbeam = { workspace = true, features = [\"alloc\"] }\nfoundations = { workspace = true, default-features = false, features = [\n  \"metrics\",\n] }\n"
  },
  {
    "path": "buffer-pool/src/buffer.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::ops::Deref;\nuse std::ops::DerefMut;\n\nuse crate::buffer_pool::consume_buffer_total_bytes;\nuse crate::Reuse;\n\n/// A convinience wrapper around Vec that allows to \"consume\" data from the\n/// front *without* shifting.\n///\n/// This is not unlike `VecDeque` but more ergonomic\n/// for the operations we require. Conceptually `VecDeque` is two slices, and\n/// this is one slice. Also there is no `set_len` for `VecDeque`, so it has to\n/// be converted to `Vec` and then back again.\n#[derive(Default, Debug)]\npub struct ConsumeBuffer {\n    inner: Vec<u8>,\n    head: usize,\n}\n\nimpl Deref for ConsumeBuffer {\n    type Target = [u8];\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner[self.head..]\n    }\n}\n\nimpl DerefMut for ConsumeBuffer {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.inner[self.head..]\n    }\n}\n\nimpl Reuse for ConsumeBuffer {\n    fn reuse(&mut self, val: usize) -> bool {\n        let old_capacity = self.inner.capacity();\n        self.inner.clear();\n        self.inner.shrink_to(val);\n        self.update_metrics_after_resize(old_capacity);\n        self.head = 0;\n        self.inner.capacity() > 0\n    }\n\n    fn capacity(&self) -> usize {\n        self.inner.capacity()\n    }\n}\n\nimpl Drop for ConsumeBuffer {\n    fn drop(&mut self) {\n        consume_buffer_total_bytes().dec_by(self.inner.capacity() as u64);\n    }\n}\n\nimpl ConsumeBuffer {\n    pub fn from_vec(inner: Vec<u8>) -> Self {\n        consume_buffer_total_bytes().inc_by(inner.capacity() as u64);\n        ConsumeBuffer { inner, head: 0 }\n    }\n\n    pub fn into_vec(mut self) -> Vec<u8> {\n        // As ConsumeBuffer has a Drop impl we have use `take` to get at the inner\n        // vec out instead of being able to move it out directly. This also means\n        // we need to update the metrics by hand as the buffer is no longer owned\n        // by us.\n        let mut inner = std::mem::take(&mut self.inner);\n        consume_buffer_total_bytes().dec_by(inner.capacity() as u64);\n        inner.drain(0..self.head);\n        inner\n    }\n\n    pub fn pop_front(&mut self, count: usize) {\n        assert!(self.head + count <= self.inner.len());\n        self.head += count;\n    }\n\n    pub fn expand(&mut self, count: usize) {\n        let old_capacity = self.inner.capacity();\n        self.inner.reserve_exact(count);\n        self.update_metrics_after_resize(old_capacity);\n        // SAFETY: u8 is always initialized and we reserved the capacity.\n        unsafe { self.inner.set_len(count) };\n    }\n\n    pub fn truncate(&mut self, count: usize) {\n        self.inner.truncate(self.head + count);\n    }\n\n    pub fn add_prefix(&mut self, prefix: &[u8]) -> bool {\n        if self.head < prefix.len() {\n            return false;\n        }\n\n        self.head -= prefix.len();\n        self.inner[self.head..self.head + prefix.len()].copy_from_slice(prefix);\n\n        true\n    }\n\n    fn update_metrics_after_resize(&mut self, old_capacity: usize) {\n        let new_capacity = self.inner.capacity();\n        if new_capacity < old_capacity {\n            consume_buffer_total_bytes()\n                .dec_by((old_capacity - new_capacity) as u64);\n        } else if new_capacity > old_capacity {\n            consume_buffer_total_bytes()\n                .inc_by((new_capacity - old_capacity) as u64);\n        }\n    }\n}\n\nimpl<'a> Extend<&'a u8> for ConsumeBuffer {\n    fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, iter: T) {\n        let old_capacity = self.inner.capacity();\n        self.inner.extend(iter);\n        self.update_metrics_after_resize(old_capacity);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_metrics() {\n        {\n            let mut buf = ConsumeBuffer::default();\n            assert_eq!(buf.inner.capacity(), 0);\n            assert_eq!(consume_buffer_total_bytes().get(), 0);\n\n            buf.extend(&[0, 1, 2, 3, 4]);\n            assert_eq!(\n                consume_buffer_total_bytes().get(),\n                buf.inner.capacity() as u64\n            );\n        }\n        // drop buf\n        assert_eq!(consume_buffer_total_bytes().get(), 0);\n\n        let mut buf_a = ConsumeBuffer::from_vec(vec![0, 1, 2, 3, 4]);\n        let buf_b = ConsumeBuffer::from_vec(vec![5, 6, 7]);\n        assert_eq!(\n            consume_buffer_total_bytes().get(),\n            (buf_a.inner.capacity() + buf_b.inner.capacity()) as u64\n        );\n\n        buf_a.expand(100000);\n        assert_eq!(\n            consume_buffer_total_bytes().get(),\n            (buf_a.inner.capacity() + buf_b.inner.capacity()) as u64\n        );\n\n        buf_b.into_vec();\n        assert_eq!(\n            consume_buffer_total_bytes().get(),\n            buf_a.inner.capacity() as u64\n        );\n\n        drop(buf_a);\n        assert_eq!(consume_buffer_total_bytes().get(), 0);\n    }\n}\n"
  },
  {
    "path": "buffer-pool/src/lib.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod buffer;\nmod raw_pool_buf_io;\n\nuse std::collections::VecDeque;\nuse std::ops::Deref;\nuse std::ops::DerefMut;\nuse std::sync::atomic::AtomicUsize;\nuse std::sync::atomic::Ordering;\n\nuse crossbeam::queue::SegQueue;\n\nuse foundations::telemetry::metrics::metrics;\nuse foundations::telemetry::metrics::Gauge;\n\npub use crate::buffer::*;\npub use crate::raw_pool_buf_io::*;\n\n#[metrics]\npub mod buffer_pool {\n    /// Number of objects available for reuse in the pool.\n    pub fn pool_idle_count(name: &'static str) -> Gauge;\n    /// Memory footprint of objects currently in the pool.\n    pub fn pool_idle_bytes(name: &'static str) -> Gauge;\n    /// Number of objects currently active and in-use.\n    pub fn pool_active_count(name: &'static str) -> Gauge;\n    /// Total number of bytes allocated across all `ConsumeBuffer` objects.\n    ///\n    /// We're not able to track this with better granularity because\n    /// the ConsumeBuffers may be resized, and they don't know their pools.\n    pub fn consume_buffer_total_bytes() -> Gauge;\n}\n\n/// A sharded pool of elements.\n#[derive(Debug)]\npub struct Pool<const S: usize, T: 'static> {\n    /// List of distinct shards to reduce contention.\n    queues: [QueueShard<T>; S],\n    /// The index of the next shard to use, in round-robin order.\n    next_shard: AtomicUsize,\n}\n\n#[derive(Debug)]\nstruct QueueShard<T> {\n    /// The inner stack of pooled values.\n    queue: SegQueue<T>,\n    /// The number of elements currently stored in this shard.\n    elem_cnt: AtomicUsize,\n    /// The value to use when calling [`Reuse::reuse`]. Typically the capacity\n    /// to keep in a reused buffer.\n    trim: usize,\n    /// The max number of values to keep in the shard.\n    max: usize,\n    /// Name of the pool, for metrics.\n    name: &'static str,\n}\n\nimpl<T> QueueShard<T> {\n    const fn new(trim: usize, max: usize, name: &'static str) -> Self {\n        QueueShard {\n            queue: SegQueue::new(),\n            elem_cnt: AtomicUsize::new(0),\n            trim,\n            max,\n            name,\n        }\n    }\n}\n\n/// A value borrowed from the [`Pool`] that can be dereferenced to `T`.\n#[derive(Debug)]\npub struct Pooled<T: Default + Reuse + 'static> {\n    inner: T,\n    pool: &'static QueueShard<T>,\n}\n\nimpl<T: Default + Reuse> Pooled<T> {\n    fn new(inner: T, shard: &'static QueueShard<T>) -> Self {\n        buffer_pool::pool_active_count(shard.name).inc();\n        Pooled { inner, pool: shard }\n    }\n\n    pub fn into_inner(mut self) -> T {\n        std::mem::take(&mut self.inner)\n    }\n}\n\nimpl<T: Default + Reuse> Drop for Pooled<T> {\n    fn drop(&mut self) {\n        let QueueShard {\n            queue,\n            elem_cnt,\n            trim,\n            max,\n            name,\n        } = self.pool;\n        // The memory associated with this object is no longer live.\n        buffer_pool::pool_active_count(name).dec();\n        if self.inner.reuse(*trim) {\n            if elem_cnt.fetch_add(1, Ordering::Acquire) < *max {\n                // If returning the element to the queue would not exceed max\n                // number of elements, return it\n                buffer_pool::pool_idle_count(name).inc();\n                buffer_pool::pool_idle_bytes(name)\n                    .inc_by(self.inner.capacity() as u64);\n                queue.push(std::mem::take(&mut self.inner));\n                return;\n            }\n            // There was no room for the buffer, return count to previous value\n            // and drop\n            elem_cnt.fetch_sub(1, Ordering::Release);\n        }\n        // If item did not qualify for return, drop it\n    }\n}\n\n// Currently there is no way to const init an array that does not implement\n// Copy, so this macro generates initializators for up to 32 shards. If\n// const Default is ever stabilized this will all go away.\nmacro_rules! array_impl_new_queues {\n    {$n:expr, $t:ident $($ts:ident)*} => {\n        impl<$t: Default + Reuse> Pool<{$n}, $t> {\n            #[allow(dead_code)]\n            pub const fn new(limit: usize, trim: usize, name: &'static str) -> Self {\n                let limit = limit / $n;\n                Pool {\n                    queues: [QueueShard::new(trim, limit, name), $(QueueShard::<$ts>::new(trim, limit, name)),*],\n                    next_shard: AtomicUsize::new(0),\n                }\n            }\n        }\n\n        array_impl_new_queues!{($n - 1), $($ts)*}\n    };\n    {$n:expr,} => {  };\n}\n\narray_impl_new_queues! { 32, T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T T }\n\nimpl<const S: usize, T: Default + Reuse> Pool<S, T> {\n    /// Get a value from the pool, or create a new default value if the\n    /// assigned shard is currently empty.\n    pub fn get(&'static self) -> Pooled<T> {\n        let shard = self.next_shard.fetch_add(1, Ordering::Relaxed) % S;\n        let shard = &self.queues[shard];\n        let inner = match shard.queue.pop() {\n            Some(el) => {\n                shard.elem_cnt.fetch_sub(1, Ordering::Relaxed);\n                buffer_pool::pool_idle_count(shard.name).dec();\n                buffer_pool::pool_idle_bytes(shard.name)\n                    .dec_by(el.capacity() as u64);\n                el\n            },\n            None => Default::default(),\n        };\n\n        Pooled::new(inner, shard)\n    }\n\n    /// Create a new default value assigned for a pool, if it is ends up\n    /// being expanded and eligible for reuse it will return to the pool,\n    /// otherwise it will end up being dropped.\n    pub fn get_empty(&'static self) -> Pooled<T> {\n        let shard = self.next_shard.load(Ordering::Relaxed) % S;\n        let shard = &self.queues[shard];\n        Pooled::new(Default::default(), shard)\n    }\n\n    /// Get a value from the pool and apply the provided transformation on\n    /// it before returning.\n    pub fn get_with(&'static self, f: impl Fn(&mut T)) -> Pooled<T> {\n        let mut pooled = self.get();\n        f(&mut pooled);\n        pooled\n    }\n\n    pub fn from_owned(&'static self, inner: T) -> Pooled<T> {\n        let shard = self.next_shard.fetch_add(1, Ordering::Relaxed) % S;\n        let shard = &self.queues[shard];\n        Pooled::new(inner, shard)\n    }\n}\n\nimpl<'a, const S: usize, T: Default + Extend<&'a u8> + Reuse> Pool<S, T> {\n    /// Get a value from the pool and extend it with the provided slice.\n    pub fn with_slice(&'static self, v: &'a [u8]) -> Pooled<T> {\n        let mut buf = self.get();\n        buf.deref_mut().extend(v);\n        buf\n    }\n}\n\nimpl<T: Default + Reuse> Deref for Pooled<T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.inner\n    }\n}\n\nimpl<T: Default + Reuse> DerefMut for Pooled<T> {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.inner\n    }\n}\n\n/// A trait that prepares an item to be returned to the pool. For example\n/// clearing it. `true` is returned if the item should be returned to the pool,\n/// `false` if it should be dropped.\npub trait Reuse {\n    fn reuse(&mut self, trim: usize) -> bool;\n\n    /// Returns the capacity of the object in bytes, to allow for more precise\n    /// tracking.\n    fn capacity(&self) -> usize;\n}\n\nimpl Reuse for Vec<u8> {\n    fn reuse(&mut self, trim: usize) -> bool {\n        self.clear();\n        self.shrink_to(trim);\n        self.capacity() > 0\n    }\n\n    fn capacity(&self) -> usize {\n        self.capacity()\n    }\n}\n\nimpl Reuse for VecDeque<u8> {\n    fn reuse(&mut self, val: usize) -> bool {\n        self.clear();\n        self.shrink_to(val);\n        self.capacity() > 0\n    }\n\n    fn capacity(&self) -> usize {\n        self.capacity()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_sharding() {\n        const SHARDS: usize = 3;\n        const MAX_IN_SHARD: usize = 2;\n        const POOL_NAME: &str = \"test_sharding_pool\";\n\n        let pool = Box::leak(Box::new(Pool::<SHARDS, Vec<u8>>::new(\n            SHARDS * MAX_IN_SHARD,\n            4,\n            POOL_NAME,\n        )));\n\n        let bufs = (0..SHARDS * 4).map(|_| pool.get()).collect::<Vec<_>>();\n\n        for shard in pool.queues.iter() {\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), 0);\n        }\n        assert_eq!(buffer_pool::pool_idle_count(POOL_NAME).get(), 0);\n        assert_eq!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(\n            buffer_pool::pool_active_count(POOL_NAME).get(),\n            bufs.len() as u64\n        );\n\n        for (i, buf) in bufs.iter().enumerate() {\n            assert!(buf.is_empty());\n            // Check the buffer is sharded properly.\n            assert_eq!(\n                buf.pool as *const _,\n                &pool.queues[i % SHARDS] as *const _\n            );\n        }\n\n        // Shards are still empty.\n        for shard in pool.queues.iter() {\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), 0);\n        }\n        assert_eq!(buffer_pool::pool_idle_count(POOL_NAME).get(), 0);\n        assert_eq!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(\n            buffer_pool::pool_active_count(POOL_NAME).get(),\n            bufs.len() as u64\n        );\n\n        // Now drop the buffers, they will not go into the pool because they have\n        // no capacity, so reuse returns false. What is the point in\n        // pooling empty buffers?\n        drop(bufs);\n        assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 0);\n        assert_eq!(buffer_pool::pool_idle_count(POOL_NAME).get(), 0);\n\n        // Get buffers with capacity next.\n        let bufs = (0..SHARDS * 4)\n            .map(|_| pool.get_with(|b| b.extend(&[0, 1])))\n            .collect::<Vec<_>>();\n\n        for (i, buf) in bufs.iter().enumerate() {\n            // Check the buffer is sharded properly.\n            assert_eq!(\n                buf.pool as *const _,\n                &pool.queues[i % SHARDS] as *const _\n            );\n            // Check that the buffer was properly extended\n            assert_eq!(&buf[..], &[0, 1]);\n        }\n        assert_eq!(\n            buffer_pool::pool_active_count(POOL_NAME).get(),\n            bufs.len() as u64\n        );\n\n        drop(bufs);\n\n        for shard in pool.queues.iter() {\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), MAX_IN_SHARD);\n        }\n        assert_eq!(\n            buffer_pool::pool_idle_count(POOL_NAME).get(),\n            (SHARDS * MAX_IN_SHARD) as u64\n        );\n        assert_ne!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 0);\n\n        // Now get buffers again, this time they should come from the pool.\n        let bufs = (0..SHARDS).map(|_| pool.get()).collect::<Vec<_>>();\n\n        for (i, buf) in bufs.iter().enumerate() {\n            // Check that the buffer was properly cleared.\n            assert!(buf.is_empty());\n            // Check the buffer is sharded properly.\n            assert_eq!(\n                buf.pool as *const _,\n                &pool.queues[i % SHARDS] as *const _\n            );\n        }\n\n        for shard in pool.queues.iter() {\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), 1);\n        }\n        assert_eq!(buffer_pool::pool_idle_count(POOL_NAME).get(), SHARDS as u64);\n        assert_ne!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(\n            buffer_pool::pool_active_count(POOL_NAME).get(),\n            bufs.len() as u64\n        );\n\n        // Get more buffers from the pool.\n        let bufs2 = (0..SHARDS).map(|_| pool.get()).collect::<Vec<_>>();\n        for shard in pool.queues.iter() {\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), 0);\n        }\n        assert_eq!(buffer_pool::pool_idle_count(POOL_NAME).get(), 0);\n        assert_eq!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(\n            buffer_pool::pool_active_count(POOL_NAME).get(),\n            (bufs.len() + bufs2.len()) as u64\n        );\n\n        // Get even more buffers.\n        let bufs3 = (0..SHARDS).map(|_| pool.get()).collect::<Vec<_>>();\n        for shard in pool.queues.iter() {\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), 0);\n        }\n        assert_eq!(buffer_pool::pool_idle_count(POOL_NAME).get(), 0);\n        assert_eq!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(\n            buffer_pool::pool_active_count(POOL_NAME).get(),\n            (bufs.len() + bufs2.len() + bufs3.len()) as u64\n        );\n\n        // Now begin dropping.\n        drop(bufs);\n        for shard in pool.queues.iter() {\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), 1);\n        }\n        assert_eq!(buffer_pool::pool_idle_count(POOL_NAME).get(), SHARDS as u64);\n        assert_ne!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(\n            buffer_pool::pool_active_count(POOL_NAME).get(),\n            (bufs2.len() + bufs3.len()) as u64\n        );\n\n        drop(bufs2);\n        for shard in pool.queues.iter() {\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), MAX_IN_SHARD);\n        }\n        assert_eq!(\n            buffer_pool::pool_idle_count(POOL_NAME).get(),\n            (SHARDS * MAX_IN_SHARD) as u64\n        );\n        assert_ne!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(\n            buffer_pool::pool_active_count(POOL_NAME).get(),\n            bufs3.len() as u64\n        );\n\n        drop(bufs3);\n        for shard in pool.queues.iter() {\n            // Can't get over limit.\n            assert_eq!(shard.elem_cnt.load(Ordering::Relaxed), MAX_IN_SHARD);\n        }\n        assert_eq!(\n            buffer_pool::pool_idle_count(POOL_NAME).get(),\n            (SHARDS * MAX_IN_SHARD) as u64\n        );\n        assert_ne!(buffer_pool::pool_idle_bytes(POOL_NAME).get(), 0);\n        assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 0);\n    }\n\n    #[test]\n    fn test_creation() {\n        const SHARDS: usize = 3;\n        const MAX_IN_SHARD: usize = 2;\n        const POOL_NAME: &str = \"test_creation_pool\";\n\n        let pool = Box::leak(Box::new(Pool::<SHARDS, Vec<u8>>::new(\n            SHARDS * MAX_IN_SHARD,\n            4,\n            POOL_NAME,\n        )));\n\n        assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 0);\n\n        {\n            let _buf1 = pool.get();\n            assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 1);\n\n            let _buf2 = pool.get_empty();\n            assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 2);\n\n            let _buf3 = pool.get_with(|_| ());\n            assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 3);\n\n            let _buf4 = pool.from_owned(vec![0, 1, 2, 4]);\n            assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 4);\n        }\n\n        assert_eq!(buffer_pool::pool_active_count(POOL_NAME).get(), 0);\n    }\n}\n"
  },
  {
    "path": "buffer-pool/src/raw_pool_buf_io.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::io;\nuse std::task::Context;\nuse std::task::Poll;\n\nuse crate::ConsumeBuffer;\nuse crate::Pooled;\n\npub type PooledBuf = Pooled<ConsumeBuffer>;\n\n/// A trait to optimize read and write operations on pooled buffers.\npub trait RawPoolBufIo: Send {\n    fn poll_send_reserve(&mut self, cx: &mut Context) -> Poll<io::Result<()>>;\n\n    fn send_buf(&mut self, buf: PooledBuf, fin: bool) -> io::Result<()>;\n\n    fn poll_recv_buf(&mut self, cx: &mut Context) -> Poll<io::Result<PooledBuf>>;\n}\n\npub trait RawPoolBufDatagramIo: Send {\n    fn poll_send_datagrams(\n        &mut self, cx: &mut Context, datagrams: &mut [PooledBuf],\n    ) -> Poll<io::Result<usize>>;\n\n    fn poll_recv_dgram(\n        &mut self, cx: &mut Context,\n    ) -> Poll<io::Result<PooledBuf>>;\n\n    fn poll_recv_datagrams(\n        &mut self, cx: &mut Context, buffer: &mut Vec<PooledBuf>, limit: usize,\n    ) -> Poll<io::Result<usize>> {\n        for i in 0..limit {\n            match self.poll_recv_dgram(cx) {\n                Poll::Ready(Ok(buf)) => buffer.push(buf),\n                Poll::Ready(Err(err)) =>\n                    if i > 0 {\n                        return Poll::Ready(Ok(i));\n                    } else {\n                        return Poll::Ready(Err(err));\n                    },\n                Poll::Pending =>\n                    if i > 0 {\n                        return Poll::Ready(Ok(i));\n                    } else {\n                        return Poll::Pending;\n                    },\n            }\n        }\n\n        Poll::Ready(Ok(limit))\n    }\n}\n"
  },
  {
    "path": "catalog-info.yaml",
    "content": "apiVersion: backstage.io/v1alpha1\nkind: Component\nmetadata:\n  title: \"quiche\"\n  name: \"quiche\"\n  description: \"Savoury implementation of the QUIC transport protocol and HTTP/3.\"\n  annotations:\n    backstage.io/source-location: url:https://github.com/cloudflare/quiche\n    cloudflare.com/jira-project-key: \"FLPROTO\"\n    cloudflare.com/software-excellence-opt-in: \"true\"\n  links:\n    - title: Documentation\n      url: https://docs.quic.tech/quiche/\n      icon: docs\n  tags:\n    - external\n    - edge\nspec:\n  type: \"library\"\n  lifecycle: \"Active\"\n  owner: \"teams/fl-protocols\"\n  dependsOn:\n    - component:default/boringssl\n"
  },
  {
    "path": "clippy.toml",
    "content": "cognitive-complexity-threshold = 100\n"
  },
  {
    "path": "datagram-socket/Cargo.toml",
    "content": "[package]\nname = \"datagram-socket\"\nversion = \"0.7.0\"\nrepository = { workspace = true }\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\ndescription = \"Utilities for working with datagram sockets\"\n\n[dependencies]\nbuffer-pool = { workspace = true }\nfutures-util = { workspace = true }\nlibc = { workspace = true }\nsmallvec = { workspace = true }\ntokio = { workspace = true, features = [\"net\", \"rt\"] }\n\n[dev-dependencies]\ntokio = { workspace = true, features = [\"macros\", \"rt-multi-thread\"] }\n"
  },
  {
    "path": "datagram-socket/src/datagram.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse buffer_pool::RawPoolBufDatagramIo;\nuse futures_util::future::poll_fn;\nuse futures_util::ready;\nuse futures_util::FutureExt;\nuse std::future::Future;\nuse std::io;\nuse std::net::Ipv4Addr;\nuse std::net::SocketAddr;\nuse std::net::SocketAddrV4;\nuse std::sync::Arc;\nuse std::task::Context;\nuse std::task::Poll;\nuse tokio::io::ReadBuf;\nuse tokio::net::UdpSocket;\nuse tokio::task::coop::unconstrained;\n\n#[cfg(unix)]\nuse std::os::fd::AsFd;\n#[cfg(unix)]\nuse std::os::fd::BorrowedFd;\n#[cfg(unix)]\nuse std::os::fd::FromRawFd;\n#[cfg(unix)]\nuse std::os::fd::IntoRawFd;\n#[cfg(unix)]\nuse std::os::fd::OwnedFd;\n#[cfg(unix)]\nuse tokio::net::UnixDatagram;\n\nuse crate::socket_stats::AsSocketStats;\n\n// This is the largest datagram we expect to support.\n// UDP and Unix sockets can support larger datagrams than this, but we only\n// expect to support packets coming to/from the Internet.\npub const MAX_DATAGRAM_SIZE: usize = 1500;\n\npub trait DatagramSocketWithStats: DatagramSocket {}\n\nimpl<T> DatagramSocketWithStats for T where T: DatagramSocket + AsSocketStats {}\n\n/// Describes an implementation of a connected datagram socket.\n///\n/// Rather than using Socket for datagram-oriented sockets, the DatagramSocket\n/// trait purposely does not implement AsyncRead/AsyncWrite, which are traits\n/// with stream semantics. For example, the `AsyncReadExt::read_exact` method\n/// which issues as many reads as possible to fill the buffer provided.\n///\n/// For a similar reason, [`std::net::UdpSocket`] does not implement\n/// [`io::Read`] nor does [`tokio::net::UdpSocket`] implement\n/// [`tokio::io::AsyncRead`].\npub trait DatagramSocket:\n    DatagramSocketSend + DatagramSocketRecv + 'static\n{\n    #[cfg(unix)]\n    fn as_raw_io(&self) -> Option<BorrowedFd<'_>>;\n\n    #[cfg(unix)]\n    fn into_fd(self) -> Option<OwnedFd>;\n\n    fn as_buf_io(&mut self) -> Option<&mut dyn RawPoolBufDatagramIo> {\n        None\n    }\n}\n\n/// Describes the send half of a connected datagram socket.\npub trait DatagramSocketSend: Sync {\n    /// Attempts to send data on the socket to the remote address to which it\n    /// was previously connected.\n    ///\n    /// Note that on multiple calls to a `poll_*` method in the send direction,\n    /// only the `Waker` from the `Context` passed to the most recent call will\n    /// be scheduled to receive a wakeup.\n    ///\n    /// # Return value\n    ///\n    /// The function returns:\n    ///\n    /// * `Poll::Pending` if the socket is not available to write\n    /// * `Poll::Ready(Ok(n))` `n` is the number of bytes sent\n    /// * `Poll::Ready(Err(e))` if an error is encountered.\n    ///\n    /// # Errors\n    ///\n    /// This function may encounter any standard I/O error except `WouldBlock`.\n    fn poll_send(&self, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>>;\n\n    /// Attempts to send data on the socket to a given address.\n    ///\n    /// If this socket only supports a single address, it should forward to\n    /// `send`. It should *not* panic or discard the data.\n    /// It's recommended that this return an error if `addr` doesn't match the\n    /// only supported address.\n    ///\n    /// Note that on multiple calls to a `poll_*` method in the send direction,\n    /// only the `Waker` from the `Context` passed to the most recent call\n    /// will be scheduled to receive a wakeup.\n    ///\n    /// # Return value\n    ///\n    /// The function returns:\n    ///\n    /// * `Poll::Pending` if the socket is not ready to write\n    /// * `Poll::Ready(Ok(n))` `n` is the number of bytes sent.\n    /// * `Poll::Ready(Err(e))` if an error is encountered.\n    ///\n    /// # Errors\n    ///\n    /// This function may encounter any standard I/O error except `WouldBlock`.\n    fn poll_send_to(\n        &self, cx: &mut Context, buf: &[u8], addr: SocketAddr,\n    ) -> Poll<io::Result<usize>>;\n\n    /// Attempts to send multiple packets of data on the socket to the remote\n    /// address to which it was previously connected.\n    ///\n    /// Note that on multiple calls to a `poll_*` method in the send direction,\n    /// only the `Waker` from the `Context` passed to the most recent call\n    /// will be scheduled to receive a wakeup.\n    ///\n    /// # Return value\n    ///\n    /// The function returns:\n    ///\n    /// * `Poll::Pending` if the socket is not ready to write\n    /// * `Poll::Ready(Ok(n))` `n` is the number of packets sent. If any packet\n    ///   was sent only partially, that information is lost.\n    /// * `Poll::Ready(Err(e))` if an error is encountered.\n    ///\n    /// # Errors\n    ///\n    /// This function may encounter any standard I/O error except `WouldBlock`.\n    fn poll_send_many(\n        &self, cx: &mut Context, bufs: &[ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        let mut sent = 0;\n\n        for buf in bufs {\n            match self.poll_send(cx, buf.filled()) {\n                Poll::Ready(Ok(_)) => sent += 1,\n                Poll::Ready(err) => {\n                    if sent == 0 {\n                        return Poll::Ready(err);\n                    }\n                    break;\n                },\n                Poll::Pending => {\n                    if sent == 0 {\n                        return Poll::Pending;\n                    }\n                    break;\n                },\n            }\n        }\n\n        Poll::Ready(Ok(sent))\n    }\n\n    /// If the underlying socket is a `UdpSocket`, return the reference to it.\n    fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        None\n    }\n\n    /// Returns the socket address of the remote peer this socket was connected\n    /// to.\n    fn peer_addr(&self) -> Option<SocketAddr> {\n        None\n    }\n}\n\n/// Writes datagrams to a socket.\n///\n/// Implemented as an extension trait, adding utility methods to all\n/// [`DatagramSocketSend`] types. Callers will tend to import this trait instead\n/// of [`DatagramSocketSend`].\n///\n/// [`DatagramSocketSend`]: DatagramSocketSend\npub trait DatagramSocketSendExt: DatagramSocketSend {\n    /// Sends data on the socket to the remote address that the socket is\n    /// connected to.\n    fn send(&self, buf: &[u8]) -> impl Future<Output = io::Result<usize>> {\n        poll_fn(move |cx| self.poll_send(cx, buf))\n    }\n\n    /// Sends data on the socket to the given address. On success, returns the\n    /// number of bytes written.\n    fn send_to(\n        &self, buf: &[u8], addr: SocketAddr,\n    ) -> impl Future<Output = io::Result<usize>> {\n        poll_fn(move |cx| self.poll_send_to(cx, buf, addr))\n    }\n\n    /// Sends multiple data packets on the socket to the to the remote address\n    /// that the socket is connected to. On success, returns the number of\n    /// packets sent.\n    fn send_many(\n        &self, bufs: &[ReadBuf<'_>],\n    ) -> impl Future<Output = io::Result<usize>> {\n        poll_fn(move |cx| self.poll_send_many(cx, bufs))\n    }\n\n    fn try_send(&self, buf: &[u8]) -> io::Result<usize> {\n        match unconstrained(poll_fn(|cx| self.poll_send(cx, buf))).now_or_never()\n        {\n            Some(result) => result,\n            None => Err(io::ErrorKind::WouldBlock.into()),\n        }\n    }\n\n    fn try_send_many(&self, bufs: &[ReadBuf<'_>]) -> io::Result<usize> {\n        match unconstrained(poll_fn(|cx| self.poll_send_many(cx, bufs)))\n            .now_or_never()\n        {\n            Some(result) => result,\n            None => Err(io::ErrorKind::WouldBlock.into()),\n        }\n    }\n}\n\n/// Describes the receive half of a connected datagram socket.\npub trait DatagramSocketRecv: Send {\n    /// Attempts to receive a single datagram message on the socket from the\n    /// remote address to which it is `connect`ed.\n    ///\n    /// Note that on multiple calls to a `poll_*` method in the `recv`\n    /// direction, only the `Waker` from the `Context` passed to the most\n    /// recent call will be scheduled to receive a wakeup.\n    ///\n    /// # Return value\n    ///\n    /// The function returns:\n    ///\n    /// * `Poll::Pending` if the socket is not ready to read\n    /// * `Poll::Ready(Ok(()))` reads data `ReadBuf` if the socket is ready\n    /// * `Poll::Ready(Err(e))` if an error is encountered.\n    ///\n    /// # Errors\n    ///\n    /// This function may encounter any standard I/O error except `WouldBlock`.\n    fn poll_recv(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<()>>;\n\n    /// Attempts to receive a single datagram on the socket.\n    ///\n    /// Note that on multiple calls to a `poll_*` method in the `recv`\n    /// direction, only the `Waker` from the `Context` passed to the most\n    /// recent call will be scheduled to receive a wakeup.\n    ///\n    /// # Return value\n    ///\n    /// The function returns:\n    ///\n    /// * `Poll::Pending` if the socket is not ready to read\n    /// * `Poll::Ready(Ok(addr))` reads data from `addr` into `ReadBuf` if the\n    ///   socket is ready\n    /// * `Poll::Ready(Err(e))` if an error is encountered.\n    ///\n    /// # Errors\n    ///\n    /// This function may encounter any standard I/O error except `WouldBlock`.\n    fn poll_recv_from(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<SocketAddr>> {\n        self.poll_recv(cx, buf).map_ok(|_| {\n            SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0))\n        })\n    }\n\n    /// Attempts to receive multiple datagrams on the socket from the remote\n    /// address to which it is `connect`ed.\n    ///\n    /// Note that on multiple calls to a `poll_*` method in the `recv`\n    /// direction, only the `Waker` from the `Context` passed to the most\n    /// recent call will be scheduled to receive a wakeup.\n    ///\n    /// # Return value\n    ///\n    /// The function returns:\n    ///\n    /// * `Poll::Pending` if the socket is not ready to read\n    /// * `Poll::Ready(Ok(n))` reads data `ReadBuf` if the socket is ready `n`\n    ///   is the number of datagrams read.\n    /// * `Poll::Ready(Err(e))` if an error is encountered.\n    ///\n    /// # Errors\n    ///\n    /// This function may encounter any standard I/O error except `WouldBlock`.\n    fn poll_recv_many(\n        &mut self, cx: &mut Context<'_>, bufs: &mut [ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        let mut read = 0;\n\n        for buf in bufs {\n            match self.poll_recv(cx, buf) {\n                Poll::Ready(Ok(())) => read += 1,\n\n                Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),\n\n                // Only return `Poll::Ready` if at least one datagram was\n                // successfully read, otherwise block.\n                Poll::Pending if read == 0 => return Poll::Pending,\n                Poll::Pending => break,\n            }\n        }\n\n        Poll::Ready(Ok(read))\n    }\n\n    /// If the underlying socket is a `UdpSocket`, return the reference to it.\n    fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        None\n    }\n}\n\n/// Reads datagrams from a socket.\n///\n/// Implemented as an extension trait, adding utility methods to all\n/// [`DatagramSocketRecv`] types. Callers will tend to import this trait instead\n/// of [`DatagramSocketRecv`].\n///\n/// [`DatagramSocketRecv`]: DatagramSocketRecv\npub trait DatagramSocketRecvExt: DatagramSocketRecv {\n    /// Receives a single datagram message on the socket from the remote address\n    /// to which it is connected. On success, returns the number of bytes read.\n    fn recv(\n        &mut self, buf: &mut [u8],\n    ) -> impl Future<Output = io::Result<usize>> + Send {\n        poll_fn(|cx| {\n            let mut buf = ReadBuf::new(buf);\n\n            ready!(self.poll_recv(cx, &mut buf)?);\n\n            Poll::Ready(Ok(buf.filled().len()))\n        })\n    }\n\n    /// Receives a single datagram message on the socket. On success, returns\n    /// the number of bytes read and the origin.\n    fn recv_from(\n        &mut self, buf: &mut [u8],\n    ) -> impl Future<Output = io::Result<(usize, SocketAddr)>> + Send {\n        poll_fn(|cx| {\n            let mut buf = ReadBuf::new(buf);\n\n            let addr = ready!(self.poll_recv_from(cx, &mut buf)?);\n\n            Poll::Ready(Ok((buf.filled().len(), addr)))\n        })\n    }\n\n    /// Receives multiple datagrams on the socket from the remote address\n    /// to which it is connected. Returns the number of buffers used (i.e.\n    /// number of datagrams read). Each used buffer can be read up to its\n    /// `filled().len()`.\n    fn recv_many(\n        &mut self, bufs: &mut [ReadBuf<'_>],\n    ) -> impl Future<Output = io::Result<usize>> + Send {\n        poll_fn(|cx| self.poll_recv_many(cx, bufs))\n    }\n}\n\nimpl<T: DatagramSocketSend + ?Sized> DatagramSocketSendExt for T {}\n\nimpl<T: DatagramSocketRecv + ?Sized> DatagramSocketRecvExt for T {}\n\n/// A convenience method that can be implemented for any type if it wants\n/// to forward its `DatagramSocketSend` functionality to an inner field/socket.\n/// This automatically derives `DatagramSocketSend`.\npub trait AsDatagramSocketSend {\n    type AsSend: DatagramSocketSend + ?Sized;\n\n    fn as_datagram_socket_send(&self) -> &Self::AsSend;\n}\n\n/// A convenience method that can be implemented for any type if it wants\n/// to forward its `DatagramSocketRecv` functionality to an inner field/socket.\n/// This automatically derives `DatagramSocketRecv`.\npub trait AsDatagramSocketRecv {\n    type AsRecv: DatagramSocketRecv + ?Sized;\n\n    fn as_datagram_socket_recv(&mut self) -> &mut Self::AsRecv;\n    fn as_shared_datagram_socket_recv(&self) -> &Self::AsRecv;\n}\n\nimpl<T: AsDatagramSocketSend + Sync> DatagramSocketSend for T {\n    #[inline]\n    fn poll_send(&self, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {\n        self.as_datagram_socket_send().poll_send(cx, buf)\n    }\n\n    #[inline]\n    fn poll_send_to(\n        &self, cx: &mut Context, buf: &[u8], addr: SocketAddr,\n    ) -> Poll<io::Result<usize>> {\n        self.as_datagram_socket_send().poll_send_to(cx, buf, addr)\n    }\n\n    #[inline]\n    fn poll_send_many(\n        &self, cx: &mut Context, bufs: &[ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        self.as_datagram_socket_send().poll_send_many(cx, bufs)\n    }\n\n    #[inline]\n    fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        self.as_datagram_socket_send().as_udp_socket()\n    }\n\n    #[inline]\n    fn peer_addr(&self) -> Option<SocketAddr> {\n        self.as_datagram_socket_send().peer_addr()\n    }\n}\n\nimpl<T: AsDatagramSocketRecv + Send> DatagramSocketRecv for T {\n    #[inline]\n    fn poll_recv(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<()>> {\n        self.as_datagram_socket_recv().poll_recv(cx, buf)\n    }\n\n    #[inline]\n    fn poll_recv_from(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<SocketAddr>> {\n        self.as_datagram_socket_recv().poll_recv_from(cx, buf)\n    }\n\n    #[inline]\n    fn poll_recv_many(\n        &mut self, cx: &mut Context<'_>, bufs: &mut [ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        self.as_datagram_socket_recv().poll_recv_many(cx, bufs)\n    }\n\n    #[inline]\n    fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        self.as_shared_datagram_socket_recv().as_udp_socket()\n    }\n}\n\nimpl<T> AsDatagramSocketSend for &mut T\nwhere\n    T: DatagramSocketSend + Send + ?Sized,\n{\n    type AsSend = T;\n\n    fn as_datagram_socket_send(&self) -> &Self::AsSend {\n        self\n    }\n}\n\nimpl<T> AsDatagramSocketSend for Box<T>\nwhere\n    T: DatagramSocketSend + Send + ?Sized,\n{\n    type AsSend = T;\n\n    fn as_datagram_socket_send(&self) -> &Self::AsSend {\n        self\n    }\n}\n\nimpl<T> AsDatagramSocketSend for Arc<T>\nwhere\n    T: DatagramSocketSend + Send + ?Sized,\n{\n    type AsSend = T;\n\n    fn as_datagram_socket_send(&self) -> &Self::AsSend {\n        self\n    }\n}\n\nimpl<T> AsDatagramSocketRecv for &mut T\nwhere\n    T: DatagramSocketRecv + Send + ?Sized,\n{\n    type AsRecv = T;\n\n    fn as_datagram_socket_recv(&mut self) -> &mut Self::AsRecv {\n        self\n    }\n\n    fn as_shared_datagram_socket_recv(&self) -> &Self::AsRecv {\n        self\n    }\n}\n\nimpl<T> AsDatagramSocketRecv for Box<T>\nwhere\n    T: DatagramSocketRecv + Send + ?Sized,\n{\n    type AsRecv = T;\n\n    fn as_datagram_socket_recv(&mut self) -> &mut Self::AsRecv {\n        self\n    }\n\n    fn as_shared_datagram_socket_recv(&self) -> &Self::AsRecv {\n        self\n    }\n}\n\nimpl DatagramSocket for UdpSocket {\n    #[cfg(unix)]\n    fn as_raw_io(&self) -> Option<BorrowedFd<'_>> {\n        Some(self.as_fd())\n    }\n\n    #[cfg(unix)]\n    fn into_fd(self) -> Option<OwnedFd> {\n        Some(into_owned_fd(self.into_std().ok()?))\n    }\n}\n\nimpl DatagramSocketSend for UdpSocket {\n    #[inline]\n    fn poll_send(&self, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {\n        UdpSocket::poll_send(self, cx, buf)\n    }\n\n    #[inline]\n    fn poll_send_to(\n        &self, cx: &mut Context, buf: &[u8], addr: SocketAddr,\n    ) -> Poll<io::Result<usize>> {\n        UdpSocket::poll_send_to(self, cx, buf, addr)\n    }\n\n    #[cfg(target_os = \"linux\")]\n    #[inline]\n    fn poll_send_many(\n        &self, cx: &mut Context, bufs: &[ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        crate::poll_sendmmsg!(self, cx, bufs)\n    }\n\n    fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        Some(self)\n    }\n\n    fn peer_addr(&self) -> Option<SocketAddr> {\n        self.peer_addr().ok()\n    }\n}\n\nimpl DatagramSocketRecv for UdpSocket {\n    #[inline]\n    fn poll_recv(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<()>> {\n        UdpSocket::poll_recv(self, cx, buf)\n    }\n\n    #[cfg(target_os = \"linux\")]\n    #[inline]\n    fn poll_recv_many(\n        &mut self, cx: &mut Context<'_>, bufs: &mut [ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        crate::poll_recvmmsg!(self, cx, bufs)\n    }\n\n    fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        Some(self)\n    }\n}\n\nimpl DatagramSocket for Arc<UdpSocket> {\n    #[cfg(unix)]\n    fn as_raw_io(&self) -> Option<BorrowedFd<'_>> {\n        Some(self.as_fd())\n    }\n\n    #[cfg(unix)]\n    fn into_fd(self) -> Option<OwnedFd> {\n        None\n    }\n}\n\nimpl DatagramSocketRecv for Arc<UdpSocket> {\n    #[inline]\n    fn poll_recv(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<()>> {\n        UdpSocket::poll_recv(self, cx, buf)\n    }\n\n    #[inline]\n    fn poll_recv_from(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<SocketAddr>> {\n        UdpSocket::poll_recv_from(self, cx, buf)\n    }\n\n    #[cfg(target_os = \"linux\")]\n    #[inline]\n    fn poll_recv_many(\n        &mut self, cx: &mut Context<'_>, bufs: &mut [ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        crate::poll_recvmmsg!(self, cx, bufs)\n    }\n\n    fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        Some(self)\n    }\n}\n\n#[cfg(unix)]\nimpl DatagramSocket for UnixDatagram {\n    fn as_raw_io(&self) -> Option<BorrowedFd<'_>> {\n        Some(self.as_fd())\n    }\n\n    fn into_fd(self) -> Option<OwnedFd> {\n        Some(into_owned_fd(self.into_std().ok()?))\n    }\n}\n\n#[cfg(unix)]\nimpl DatagramSocketSend for UnixDatagram {\n    #[inline]\n    fn poll_send(&self, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {\n        UnixDatagram::poll_send(self, cx, buf)\n    }\n\n    #[inline]\n    fn poll_send_to(\n        &self, _: &mut Context, _: &[u8], _: SocketAddr,\n    ) -> Poll<io::Result<usize>> {\n        Poll::Ready(Err(io::Error::new(\n            io::ErrorKind::Unsupported,\n            \"invalid address family\",\n        )))\n    }\n\n    #[cfg(target_os = \"linux\")]\n    #[inline]\n    fn poll_send_many(\n        &self, cx: &mut Context, bufs: &[ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        crate::poll_sendmmsg!(self, cx, bufs)\n    }\n}\n\n#[cfg(unix)]\nimpl DatagramSocketRecv for UnixDatagram {\n    #[inline]\n    fn poll_recv(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<()>> {\n        UnixDatagram::poll_recv(self, cx, buf)\n    }\n\n    #[cfg(target_os = \"linux\")]\n    #[inline]\n    fn poll_recv_many(\n        &mut self, cx: &mut Context<'_>, bufs: &mut [ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        crate::poll_recvmmsg!(self, cx, bufs)\n    }\n}\n\n#[cfg(unix)]\nimpl DatagramSocketRecv for Arc<UnixDatagram> {\n    #[inline]\n    fn poll_recv(\n        &mut self, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>,\n    ) -> Poll<io::Result<()>> {\n        UnixDatagram::poll_recv(self, cx, buf)\n    }\n\n    #[cfg(target_os = \"linux\")]\n    #[inline]\n    fn poll_recv_many(\n        &mut self, cx: &mut Context<'_>, bufs: &mut [ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        crate::poll_recvmmsg!(self, cx, bufs)\n    }\n}\n\n/// `Into<OwnedFd>::into` for types (tokio sockets etc) that don't implement\n/// `From<OwnedFd>`.\n#[cfg(unix)]\nfn into_owned_fd<F: IntoRawFd>(into_fd: F) -> OwnedFd {\n    unsafe { OwnedFd::from_raw_fd(into_fd.into_raw_fd()) }\n}\n\n/// A cheap wrapper around a datagram socket which describes if it is connected\n/// to an explicit peer.\n///\n/// This struct essentially forwards its underlying socket's `send_to()` method\n/// to `send()` if the socket is explicitly connected to a peer. This is helpful\n/// for preventing issues on platforms that do not support `send_to` on\n/// already-connected sockets.\n///\n/// # Warning\n/// A socket's \"connectedness\" is determined once, when it is created. If the\n/// socket is created as connected, then later disconnected from its peer, its\n/// `send_to()` call will fail.\n///\n/// For example, MacOS errors if `send_to` is used on a socket that's already\n/// connected. Only `send` can be used. By using `MaybeConnectedSocket`, you can\n/// use the same `send` and `send_to` APIs in both client- and server-side code.\n/// Clients, usually with connected sockets, will then forward `send_to` to\n/// `send`, whereas servers, usually with unconnected sockets, will use\n/// `send_to`.\n#[derive(Clone)]\npub struct MaybeConnectedSocket<T> {\n    inner: T,\n    peer: Option<SocketAddr>,\n}\n\nimpl<T: DatagramSocketSend> MaybeConnectedSocket<T> {\n    pub fn new(inner: T) -> Self {\n        Self {\n            peer: inner.peer_addr(),\n            inner,\n        }\n    }\n\n    /// Provides access to the wrapped socket, allowing the user to override\n    /// `send_to()` behavior if required.\n    pub fn inner(&self) -> &T {\n        &self.inner\n    }\n\n    /// Consumes `self`, returning the wrapped socket.\n    pub fn into_inner(self) -> T {\n        self.inner\n    }\n}\n\nimpl<T: DatagramSocketSend> DatagramSocketSend for MaybeConnectedSocket<T> {\n    #[inline]\n    fn poll_send(&self, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {\n        self.inner.poll_send(cx, buf)\n    }\n\n    #[inline]\n    fn poll_send_to(\n        &self, cx: &mut Context, buf: &[u8], addr: SocketAddr,\n    ) -> Poll<io::Result<usize>> {\n        if let Some(peer) = self.peer {\n            debug_assert_eq!(peer, addr);\n            self.inner.poll_send(cx, buf)\n        } else {\n            self.inner.poll_send_to(cx, buf, addr)\n        }\n    }\n\n    #[inline]\n    fn poll_send_many(\n        &self, cx: &mut Context, bufs: &[ReadBuf<'_>],\n    ) -> Poll<io::Result<usize>> {\n        self.inner.poll_send_many(cx, bufs)\n    }\n\n    #[inline]\n    fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        self.inner.as_udp_socket()\n    }\n\n    #[inline]\n    fn peer_addr(&self) -> Option<SocketAddr> {\n        self.peer\n    }\n}\n"
  },
  {
    "path": "datagram-socket/src/lib.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod datagram;\nmod shutdown;\nmod socket_stats;\n\n#[cfg(target_os = \"linux\")]\n#[macro_use]\nmod mmsg;\n\npub use self::datagram::*;\n\n#[cfg(target_os = \"linux\")]\npub use mmsg::*;\n\n#[cfg(unix)]\nuse std::os::fd::AsRawFd;\n\npub use self::shutdown::*;\npub use self::socket_stats::*;\n\n#[cfg(target_os = \"linux\")]\npub fn is_nonblocking(fd: &impl AsRawFd) -> std::io::Result<bool> {\n    let flags = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETFL) };\n\n    if flags == -1 {\n        return Err(std::io::Error::last_os_error());\n    }\n\n    if flags & libc::O_NONBLOCK != 0 {\n        Ok(true)\n    } else {\n        Ok(false)\n    }\n}\n\n#[cfg(all(unix, not(target_os = \"linux\")))]\npub fn is_nonblocking(_fd: &impl AsRawFd) -> std::io::Result<bool> {\n    Ok(true)\n}\n"
  },
  {
    "path": "datagram-socket/src/mmsg.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::io::IoSlice;\nuse std::io::{\n    self,\n};\nuse std::os::fd::AsRawFd;\nuse std::os::fd::BorrowedFd;\n\nuse smallvec::SmallVec;\nuse tokio::io::ReadBuf;\n\nconst MAX_MMSG: usize = 16;\n\npub fn recvmmsg(fd: BorrowedFd, bufs: &mut [ReadBuf<'_>]) -> io::Result<usize> {\n    let mut msgvec: SmallVec<[libc::mmsghdr; MAX_MMSG]> = SmallVec::new();\n    let mut slices: SmallVec<[IoSlice; MAX_MMSG]> = SmallVec::new();\n\n    let mut ret = 0;\n\n    for bufs in bufs.chunks_mut(MAX_MMSG) {\n        msgvec.clear();\n        slices.clear();\n\n        for buf in bufs.iter_mut() {\n            // Safety: will not read the maybe uninitialized bytes.\n            let b = unsafe {\n                &mut *(buf.unfilled_mut() as *mut [std::mem::MaybeUninit<u8>]\n                    as *mut [u8])\n            };\n\n            slices.push(IoSlice::new(b));\n\n            msgvec.push(libc::mmsghdr {\n                msg_hdr: libc::msghdr {\n                    msg_name: std::ptr::null_mut(),\n                    msg_namelen: 0,\n                    msg_iov: slices.last_mut().unwrap() as *mut _ as *mut _,\n                    msg_iovlen: 1,\n                    msg_control: std::ptr::null_mut(),\n                    msg_controllen: 0,\n                    msg_flags: 0,\n                },\n                msg_len: buf.capacity().try_into().unwrap(),\n            });\n        }\n\n        let result = unsafe {\n            libc::recvmmsg(\n                fd.as_raw_fd(),\n                msgvec.as_mut_ptr(),\n                msgvec.len() as _,\n                0,\n                std::ptr::null_mut(),\n            )\n        };\n\n        if result == -1 {\n            break;\n        }\n\n        for i in 0..result as usize {\n            let filled = msgvec[i].msg_len as usize;\n            unsafe { bufs[i].assume_init(filled) };\n            bufs[i].advance(filled);\n            ret += 1;\n        }\n\n        if (result as usize) < MAX_MMSG {\n            break;\n        }\n    }\n\n    if ret == 0 {\n        return Err(io::Error::last_os_error());\n    }\n\n    Ok(ret)\n}\n\npub fn sendmmsg(fd: BorrowedFd, bufs: &[ReadBuf<'_>]) -> io::Result<usize> {\n    let mut msgvec: SmallVec<[libc::mmsghdr; MAX_MMSG]> = SmallVec::new();\n    let mut slices: SmallVec<[IoSlice; MAX_MMSG]> = SmallVec::new();\n\n    let mut ret = 0;\n\n    for bufs in bufs.chunks(MAX_MMSG) {\n        msgvec.clear();\n        slices.clear();\n\n        for buf in bufs.iter() {\n            slices.push(IoSlice::new(buf.filled()));\n\n            msgvec.push(libc::mmsghdr {\n                msg_hdr: libc::msghdr {\n                    msg_name: std::ptr::null_mut(),\n                    msg_namelen: 0,\n                    msg_iov: slices.last_mut().unwrap() as *mut _ as *mut _,\n                    msg_iovlen: 1,\n                    msg_control: std::ptr::null_mut(),\n                    msg_controllen: 0,\n                    msg_flags: 0,\n                },\n                msg_len: buf.capacity().try_into().unwrap(),\n            });\n        }\n\n        let result = unsafe {\n            libc::sendmmsg(\n                fd.as_raw_fd(),\n                msgvec.as_mut_ptr(),\n                msgvec.len() as _,\n                0,\n            )\n        };\n\n        if result == -1 {\n            break;\n        }\n\n        ret += result as usize;\n\n        if (result as usize) < MAX_MMSG {\n            break;\n        }\n    }\n\n    if ret == 0 {\n        return Err(io::Error::last_os_error());\n    }\n\n    Ok(ret)\n}\n\n#[macro_export]\nmacro_rules! poll_recvmmsg {\n    ($self: expr, $cx: ident, $bufs: ident) => {\n        loop {\n            match $self.poll_recv_ready($cx)? {\n                Poll::Ready(()) => {\n                    match $self.try_io(tokio::io::Interest::READABLE, || {\n                        $crate::mmsg::recvmmsg($self.as_fd(), $bufs)\n                    }) {\n                        Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}  // Have to poll for recv ready\n                        res => break Poll::Ready(res),\n                    }\n                }\n                Poll::Pending => break Poll::Pending,\n            }\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! poll_sendmmsg {\n    ($self: expr, $cx: ident, $bufs: ident) => {\n        loop {\n            match $self.poll_send_ready($cx)? {\n                Poll::Ready(()) => {\n                    match $self.try_io(tokio::io::Interest::WRITABLE, || {\n                        $crate::mmsg::sendmmsg($self.as_fd(), $bufs)\n                    }) {\n                        Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} // Have to poll for send ready\n                        res => break Poll::Ready(res),\n                    }\n                }\n                Poll::Pending => break Poll::Pending,\n            }\n        }\n    };\n}\n\n#[cfg(test)]\nmod tests {\n    use std::io;\n\n    use tokio::io::ReadBuf;\n    use tokio::net::UnixDatagram;\n\n    use crate::DatagramSocketRecvExt;\n    use crate::DatagramSocketSendExt;\n\n    #[tokio::test]\n    async fn recvmmsg() -> io::Result<()> {\n        let (s, mut r) = UnixDatagram::pair()?;\n        let mut bufs = [[0u8; 128]; 128];\n\n        for i in 0..5 {\n            s.send(&[i; 128]).await?;\n        }\n\n        let mut rbufs: Vec<_> =\n            bufs.iter_mut().map(|s| ReadBuf::new(&mut s[..])).collect();\n        assert_eq!(r.recv_many(&mut rbufs).await?, 5);\n\n        for (i, buf) in rbufs[0..5].iter().enumerate() {\n            assert_eq!(buf.filled(), &[i as u8; 128]);\n        }\n\n        for i in 0..92 {\n            s.send(&[i; 128]).await?;\n        }\n\n        let mut rbufs: Vec<_> =\n            bufs.iter_mut().map(|s| ReadBuf::new(&mut s[..])).collect();\n        assert_eq!(r.recv_many(&mut rbufs).await?, 92);\n\n        for (i, buf) in rbufs[0..92].iter().enumerate() {\n            assert_eq!(buf.filled(), &[i as u8; 128]);\n        }\n\n        Ok(())\n    }\n\n    #[tokio::test]\n    async fn sendmmsg() -> io::Result<()> {\n        let (s, r) = UnixDatagram::pair()?;\n        let mut bufs: [_; 128] = std::array::from_fn(|i| [i as u8; 128]);\n\n        let wbufs: Vec<_> = bufs\n            .iter_mut()\n            .map(|s| {\n                let mut b = ReadBuf::new(&mut s[..]);\n                b.set_filled(128);\n                b\n            })\n            .collect();\n\n        assert_eq!(s.send_many(&wbufs[..5]).await?, 5);\n\n        let mut rbuf = [0u8; 128];\n\n        for i in 0..5 {\n            assert_eq!(r.recv(&mut rbuf).await?, 128);\n            assert_eq!(rbuf, [i as u8; 128]);\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "datagram-socket/src/shutdown.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::future::poll_fn;\nuse std::future::Future;\nuse std::io;\nuse std::sync::Arc;\nuse std::task::Context;\nuse std::task::Poll;\n\nuse tokio::net::UdpSocket;\n\n#[cfg(unix)]\nuse tokio::net::UnixDatagram;\n\npub trait ShutdownConnection {\n    /// Initiates or attempts to shut down this writer, returning success when\n    /// the I/O connection has completely shut down.\n    ///\n    /// # Return value\n    ///\n    /// This function returns a `Poll<io::Result<()>>` classified as such:\n    ///\n    /// * `Poll::Ready(Ok(()))` - indicates that the connection was successfully\n    ///   shut down and is now safe to deallocate/drop/close resources\n    ///   associated with it. This method means that the current task will no\n    ///   longer receive any notifications due to this method and the I/O object\n    ///   itself is likely no longer usable.\n    ///\n    /// * `Poll::Pending` - indicates that shutdown is initiated but could not\n    ///   complete just yet. This may mean that more I/O needs to happen to\n    ///   continue this shutdown operation. The current task is scheduled to\n    ///   receive a notification when it's otherwise ready to continue the\n    ///   shutdown operation. When woken up this method should be called again.\n    ///\n    /// * `Poll::Ready(Err(e))` - indicates a fatal error has happened with\n    ///   shutdown, indicating that the shutdown operation did not complete\n    ///   successfully. This typically means that the I/O object is no longer\n    ///   usable.\n    ///\n    /// # Errors\n    ///\n    /// This function can return normal I/O errors through `Err`, described\n    /// above. Additionally this method may also render the underlying\n    /// `Write::write` method no longer usable (e.g. will return errors in the\n    /// future). It's recommended that once `shutdown` is called the\n    /// `write` method is no longer called.\n    fn poll_shutdown(&mut self, cx: &mut Context) -> Poll<io::Result<()>>;\n}\n\nimpl ShutdownConnection for UdpSocket {\n    #[inline]\n    fn poll_shutdown(&mut self, _cx: &mut Context) -> Poll<io::Result<()>> {\n        Poll::Ready(Ok(()))\n    }\n}\n\n#[cfg(unix)]\nimpl ShutdownConnection for UnixDatagram {\n    #[inline]\n    fn poll_shutdown(&mut self, _cx: &mut Context) -> Poll<io::Result<()>> {\n        Poll::Ready(Ok(()))\n    }\n}\n\nimpl<T: ShutdownConnection + Send + Sync> ShutdownConnection for Arc<T> {\n    #[inline]\n    fn poll_shutdown(&mut self, _cx: &mut Context) -> Poll<io::Result<()>> {\n        Poll::Ready(Ok(()))\n    }\n}\n\n/// Shuts down a datagram oriented connection.\n///\n/// Implemented as an extension trait, adding utility methods to all\n/// [`ShutdownConnection`] types. Callers will tend to import this trait instead\n/// of [`ShutdownConnection`].\n///\n/// [`ShutdownConnection`]: ShutdownConnection\npub trait ShutdownConnectionExt: ShutdownConnection {\n    #[inline]\n    fn shutdown_connection(&mut self) -> impl Future<Output = io::Result<()>> {\n        poll_fn(move |cx| self.poll_shutdown(cx))\n    }\n}\n\nimpl<T: ShutdownConnection + ?Sized> ShutdownConnectionExt for T {}\n"
  },
  {
    "path": "datagram-socket/src/socket_stats.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::ops::Deref;\nuse std::sync::atomic::AtomicI64;\nuse std::sync::atomic::AtomicU64;\nuse std::sync::atomic::AtomicU8;\nuse std::sync::atomic::Ordering;\nuse std::sync::Arc;\n#[cfg(target_os = \"linux\")]\nuse std::sync::OnceLock;\nuse std::sync::RwLock;\nuse std::time::Duration;\nuse std::time::SystemTime;\n\npub trait AsSocketStats {\n    fn as_socket_stats(&self) -> SocketStats;\n\n    fn as_quic_stats(&self) -> Option<&Arc<QuicAuditStats>> {\n        None\n    }\n}\n\n#[derive(Debug, Clone, Copy, Default)]\npub struct SocketStats {\n    pub pmtu: u16,\n    pub rtt_us: i64,\n    pub min_rtt_us: i64,\n    pub max_rtt_us: i64,\n    pub rtt_var_us: i64,\n    pub cwnd: u64,\n    pub total_pto_count: u64,\n    pub packets_sent: u64,\n    pub packets_recvd: u64,\n    pub packets_lost: u64,\n    pub packets_lost_spurious: u64,\n    pub packets_retrans: u64,\n    pub bytes_sent: u64,\n    pub bytes_recvd: u64,\n    pub bytes_lost: u64,\n    pub bytes_retrans: u64,\n    pub bytes_unsent: u64,\n    pub delivery_rate: u64,\n    pub max_bandwidth: Option<u64>,\n    pub startup_exit: Option<StartupExit>,\n    pub bytes_in_flight_duration_us: u64,\n}\n\n/// Statistics from when a CCA first exited the startup phase.\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct StartupExit {\n    pub cwnd: usize,\n    pub bandwidth: Option<u64>,\n    pub reason: StartupExitReason,\n}\n\n/// The reason a CCA exited the startup phase.\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum StartupExitReason {\n    /// Exit slow start or BBR startup due to excessive loss\n    Loss,\n\n    /// Exit BBR startup due to bandwidth plateau.\n    BandwidthPlateau,\n\n    /// Exit BBR startup due to persistent queue.\n    PersistentQueue,\n\n    /// Exit HyStart++ conservative slow start after the max rounds allowed.\n    ConservativeSlowStartRounds,\n}\n\ntype BoxError = Box<dyn std::error::Error + Send + Sync>;\n\n#[derive(Debug)]\npub struct QuicAuditStats {\n    /// A transport-level connection error code received from the client.\n    recvd_conn_close_transport_error_code: AtomicI64,\n    /// A transport-level connection error code sent to the client.\n    sent_conn_close_transport_error_code: AtomicI64,\n    /// An application-level connection error code received from the client.\n    recvd_conn_close_application_error_code: AtomicI64,\n    /// An application-level connection error code sent to the client.\n    sent_conn_close_application_error_code: AtomicI64,\n    /// Time taken for the QUIC handshake in microseconds.\n    transport_handshake_duration_us: AtomicI64,\n    /// The start time of the handshake.\n    transport_handshake_start: Arc<RwLock<Option<SystemTime>>>,\n    /// The reason the QUIC connection was closed\n    connection_close_reason: RwLock<Option<BoxError>>,\n    /// Max recorded bandwidth.\n    max_bandwidth: AtomicU64,\n    /// Loss at max recorded bandwidth.\n    max_loss_pct: AtomicU8,\n    /// The value of the first `SO_RECVMARK` control message received for the\n    /// connection.\n    ///\n    /// Linux-only.\n    #[cfg(target_os = \"linux\")]\n    initial_so_mark: OnceLock<[u8; 4]>,\n    /// The server's chosen QUIC connection ID.\n    ///\n    /// The QUIC connection ID is presently an array of 20 bytes (160 bits)\n    pub quic_connection_id: Vec<u8>,\n}\n\nimpl QuicAuditStats {\n    #[inline]\n    pub fn new(quic_connection_id: Vec<u8>) -> Self {\n        Self {\n            recvd_conn_close_transport_error_code: AtomicI64::new(-1),\n            sent_conn_close_transport_error_code: AtomicI64::new(-1),\n            recvd_conn_close_application_error_code: AtomicI64::new(-1),\n            sent_conn_close_application_error_code: AtomicI64::new(-1),\n            transport_handshake_duration_us: AtomicI64::new(-1),\n            transport_handshake_start: Arc::new(RwLock::new(None)),\n            connection_close_reason: RwLock::new(None),\n            max_bandwidth: AtomicU64::new(0),\n            max_loss_pct: AtomicU8::new(0),\n            #[cfg(target_os = \"linux\")]\n            initial_so_mark: OnceLock::new(),\n            quic_connection_id,\n        }\n    }\n\n    #[inline]\n    pub fn recvd_conn_close_transport_error_code(&self) -> i64 {\n        self.recvd_conn_close_transport_error_code\n            .load(Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn sent_conn_close_transport_error_code(&self) -> i64 {\n        self.sent_conn_close_transport_error_code\n            .load(Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn recvd_conn_close_application_error_code(&self) -> i64 {\n        self.recvd_conn_close_application_error_code\n            .load(Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn sent_conn_close_application_error_code(&self) -> i64 {\n        self.sent_conn_close_application_error_code\n            .load(Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn set_recvd_conn_close_transport_error_code(\n        &self, recvd_conn_close_transport_error_code: i64,\n    ) {\n        self.recvd_conn_close_transport_error_code\n            .store(recvd_conn_close_transport_error_code, Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn set_sent_conn_close_transport_error_code(\n        &self, sent_conn_close_transport_error_code: i64,\n    ) {\n        self.sent_conn_close_transport_error_code\n            .store(sent_conn_close_transport_error_code, Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn set_recvd_conn_close_application_error_code(\n        &self, recvd_conn_close_application_error_code: i64,\n    ) {\n        self.recvd_conn_close_application_error_code\n            .store(recvd_conn_close_application_error_code, Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn set_sent_conn_close_application_error_code(\n        &self, sent_conn_close_application_error_code: i64,\n    ) {\n        self.sent_conn_close_application_error_code\n            .store(sent_conn_close_application_error_code, Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn transport_handshake_duration_us(&self) -> i64 {\n        self.transport_handshake_duration_us.load(Ordering::SeqCst)\n    }\n\n    #[inline]\n    pub fn set_transport_handshake_start(&self, start_time: SystemTime) {\n        *self.transport_handshake_start.write().unwrap() = Some(start_time);\n    }\n\n    #[inline]\n    pub fn set_transport_handshake_duration(&self, duration: Duration) {\n        let dur = i64::try_from(duration.as_micros()).unwrap_or(-1);\n        self.transport_handshake_duration_us\n            .store(dur, Ordering::SeqCst);\n    }\n\n    #[inline]\n    pub fn transport_handshake_start(&self) -> Arc<RwLock<Option<SystemTime>>> {\n        Arc::clone(&self.transport_handshake_start)\n    }\n\n    #[inline]\n    pub fn connection_close_reason(\n        &self,\n    ) -> impl Deref<Target = Option<BoxError>> + '_ {\n        self.connection_close_reason.read().unwrap()\n    }\n\n    #[inline]\n    pub fn set_connection_close_reason(&self, error: BoxError) {\n        *self.connection_close_reason.write().unwrap() = Some(error);\n    }\n\n    #[inline]\n    pub fn set_max_bandwidth(&self, max_bandwidth: u64) {\n        self.max_bandwidth.store(max_bandwidth, Ordering::Release)\n    }\n\n    #[inline]\n    pub fn max_bandwidth(&self) -> u64 {\n        self.max_bandwidth.load(Ordering::Acquire)\n    }\n\n    #[inline]\n    pub fn set_max_loss_pct(&self, max_loss_pct: u8) {\n        self.max_loss_pct.store(max_loss_pct, Ordering::Release)\n    }\n\n    #[inline]\n    pub fn max_loss_pct(&self) -> u8 {\n        self.max_loss_pct.load(Ordering::Acquire)\n    }\n\n    #[inline]\n    #[cfg(target_os = \"linux\")]\n    pub fn set_initial_so_mark_data(&self, value: Option<[u8; 4]>) {\n        if let Some(inner) = value {\n            let _ = self.initial_so_mark.set(inner);\n        }\n    }\n\n    #[inline]\n    #[cfg(target_os = \"linux\")]\n    pub fn initial_so_mark_data(&self) -> Option<&[u8; 4]> {\n        self.initial_so_mark.get()\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\npub enum StreamClosureKind {\n    None,\n    Implicit,\n    Explicit,\n}\n"
  },
  {
    "path": "fuzz/.gitignore",
    "content": "target\nartifacts\n"
  },
  {
    "path": "fuzz/Cargo.toml",
    "content": "[package]\nname = \"quiche-fuzz\"\nversion = \"0.1.0\"\nauthors = [\"Alessandro Ghedini <alessandro@ghedini.me>\"]\nedition = \"2018\"\npublish = false\n\n[package.metadata]\ncargo-fuzz = true\n\n[profile.dev]\nopt-level = 3\n\n[dependencies]\nenv_logger = \"0.11\"\nlibfuzzer-sys = \"0.4\"\nquiche = { path = \"../quiche\", features = [\"fuzzing\"] }\n\n# Prevent this from interfering with workspaces\n[workspace]\nmembers = [\".\"]\n\n[[bin]]\nname = \"packet_recv_client\"\npath = \"src/packet_recv_client.rs\"\n\n[[bin]]\nname = \"packet_recv_server\"\npath = \"src/packet_recv_server.rs\"\n\n[[bin]]\nname = \"packets_recv_server\"\npath = \"src/packets_recv_server.rs\"\n\n[[bin]]\nname = \"packets_posths_server\"\npath = \"src/packets_posths_server.rs\"\n\n[[bin]]\nname = \"qpack_decode\"\npath = \"src/qpack_decode.rs\"\n\n[profile.release]\ndebug = true\ndebug-assertions = true\noverflow-checks = true\n"
  },
  {
    "path": "fuzz/Dockerfile",
    "content": "FROM rustlang/rust:nightly as fuzz\n\nWORKDIR /build\n\nCOPY Cargo.toml Cargo.toml\nCOPY Makefile Makefile\nCOPY apps/ ./apps/\nCOPY fuzz/ ./fuzz/\nCOPY octets/ ./octets/\nCOPY qlog/ ./qlog/\nCOPY quiche/ ./quiche/\n\nRUN apt-get update && apt-get install -y cmake && rm -rf /var/lib/apt/lists/*\n\nRUN cargo install cargo-fuzz\nRUN make build-fuzz\n\n##\n## quiche-libfuzzer: quiche image for fuzzing\n##\nFROM debian:latest as quiche-libfuzzer\n\nLABEL maintainer=\"alessandro@cloudflare.com\"\n\nWORKDIR /home/mayhem/\n\nRUN apt-get update && apt-get install -y ca-certificates llvm && rm -rf /var/lib/apt/lists/*\n\nCOPY fuzz/cert.crt ./\nCOPY fuzz/cert.key ./\n\nCOPY --from=fuzz \\\n     /build/fuzz/target/x86_64-unknown-linux-gnu/release/packet_recv_client \\\n     /build/fuzz/target/x86_64-unknown-linux-gnu/release/packet_recv_server \\\n     /build/fuzz/target/x86_64-unknown-linux-gnu/release/packets_recv_server \\\n     /build/fuzz/target/x86_64-unknown-linux-gnu/release/packets_posths_server \\\n     /build/fuzz/target/x86_64-unknown-linux-gnu/release/qpack_decode \\\n     ./\n"
  },
  {
    "path": "fuzz/README.md",
    "content": "This crate provides fuzzers based on [libfuzzer](https://llvm.org/docs/LibFuzzer.html).\n\nAvailable fuzzers:\n\n* packet\\_recv\\_client: Processes a single incoming packet (including frames) at\n  a time from the client side.\n\n* packet\\_recv\\_server: Processes a single incoming packet (including frames) at\n  a time from the server side.\n\n* qpack\\_decode: Parses a single QPACK header block at a time.\n\n## Generating seeds\n\nRun `tools/gen_fuzz_seeds.sh` from the root of the repository.\n\n## Generating code coverage\n\nRun the following command from the root of the repository:\n\n```\n$ cargo +nightly fuzz coverage <target> fuzz/corpus/<target>\n```\n\nWhere `<target>` is one of the fuzzers listed above.\n\nAn HTML report can be generated using `llvm-cov`. Note that the same version as\nthe one used by `cargo-fuzz` must be used. If installing the\n`llvm-tools-preview` component using `rustup`, `llvm-cov` can be found somewhere\nunder `~/.rustup/toolchains` (or wherever the toolchains are installed).\n\nFor example:\n\n```\n$ ~/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-cov show --ignore-filename-regex='cargo/registry' --ignore-filename-regex='/rustc' --show-instantiations --show-line-counts-or-regions --Xdemangler=rustfilt --instr-profile /home/ghedo/devel/quiche/fuzz/coverage/packet_recv_server/coverage.profdata /home/ghedo/devel/quiche/target/x86_64-unknown-linux-gnu/coverage/x86_64-unknown-linux-gnu/release/packet_recv_server --format=html --output-dir \"/tmp/cov\"\n```\n\nThe `index.html` file under `/tmp/cov` can then be opened in a browser to view\nthe report.\n\n## Start run on Mayhem\n\nBuild and publish the fuzzing Docker image:\n\n```\nmake docker-fuzz docker-fuzz-publish\n```\n\nThen run the following command under `fuzz/mayhem/`:\n\n```\n$ mayhem run --all <target>\n```\n\nWhere `<target>` is one of the fuzzers listed above.\n\n## Sync test cases from Mayhem\n\nRun the following command under `fuzz/mayhem/`:\n\n```\n$ mayhem sync <target>\n```\n\nWhere `<target>` is one of the fuzzers listed above.\n\nThen minimize the inputs:\n\n```\n$ cargo +nightly fuzz cmin -Oa <target>\n```\n"
  },
  {
    "path": "fuzz/cert.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIICCjCCAa+gAwIBAgIUF98LyjChPz8gcFcrQx6UAtWxSw0wCgYIKoZIzj0EAwIw\nWTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\ndGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJcXVpYy50ZWNoMCAXDTIz\nMTAxMTA4NDgzNVoYDzIxMjMwOTE3MDg0ODM1WjBZMQswCQYDVQQGEwJVUzETMBEG\nA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg\nTHRkMRIwEAYDVQQDDAlxdWljLnRlY2gwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\nAASlj0woNNcF3iLLvVtKqZpCWlywxMcLBkJE5eekJbGB4Bt9YltxOj/N06yYzAvk\nPlrHEPZyFG1oSjAxPQrDQlbCo1MwUTAdBgNVHQ4EFgQUbFg0BZeGcsxb1p8b3Ufn\n/KjfTaowHwYDVR0jBBgwFoAUbFg0BZeGcsxb1p8b3Ufn/KjfTaowDwYDVR0TAQH/\nBAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEA2dGMMuY3SsvYvaVSD2JiHr2E1Ojq\n2o1XUatLUYKk65ECIQDeDvybx6CIo/e/+0inRIuKls2/t0HyDegKD9mGxJhEpg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "fuzz/cert.key",
    "content": "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEINW2GdrK8G+viSH/vAmg6wtOr5UY/SEY14U/9VPedlNYoAoGCCqGSM49\nAwEHoUQDQgAEpY9MKDTXBd4iy71bSqmaQlpcsMTHCwZCROXnpCWxgeAbfWJbcTo/\nzdOsmMwL5D5axxD2chRtaEowMT0Kw0JWwg==\n-----END EC PRIVATE KEY-----\n"
  },
  {
    "path": "fuzz/corpus/packet_recv_client/52538a80094f7b62948fd31e68fd17a315d8dc91",
    "content": ""
  },
  {
    "path": "fuzz/corpus/packet_recv_client/ac9231da4082430afe8f4d40127814c613648d8e",
    "content": "\t"
  },
  {
    "path": "fuzz/corpus/packet_recv_server/2416b12c35a58e5f6cf2bc0d33ad4119f8208714",
    "content": "_"
  },
  {
    "path": "fuzz/corpus/packet_recv_server/52538a80094f7b62948fd31e68fd17a315d8dc91",
    "content": ""
  },
  {
    "path": "fuzz/corpus/packet_recv_server/54b8cf23f97a0a8653e76a77b6339978f29b2913",
    "content": "\u0006\u0006\u0006\u0006\u0006\u0006\u0006\u0006\u0006%\u0006\u0007\u0006\u000f\u0006\u0006\u0006\u0006\u0006>\u001c\u0006(\n\u0001"
  },
  {
    "path": "fuzz/corpus/packet_recv_server/ac9231da4082430afe8f4d40127814c613648d8e",
    "content": "\t"
  },
  {
    "path": "fuzz/corpus/qpack_decode/0389b46ef8babfb1a5a543f7430c66e4ef38da69",
    "content": ";"
  },
  {
    "path": "fuzz/corpus/qpack_decode/03da02bdb5382901c3081c7628d0fe0d3ed08480",
    "content": "$"
  },
  {
    "path": "fuzz/corpus/qpack_decode/05e5a9f40a49ed6e519faf8c98665f6945a76e85",
    "content": "11"
  },
  {
    "path": "fuzz/corpus/qpack_decode/0678a8571fbef7d23cfd6b7f9c290faec65d9a6a",
    "content": "?!\u001e?!\u0006"
  },
  {
    "path": "fuzz/corpus/qpack_decode/0769c7bc1c230bd061b0d7b799ddedf9e3879b29",
    "content": "[\u0003"
  },
  {
    "path": "fuzz/corpus/qpack_decode/08a583c0fc6609ebc4f993732f6ffd8b06214b4c",
    "content": "\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/0a710b2b1235e97561f628b66ab92ecbe32edae9",
    "content": "\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/0b163753986ab8121b1e9cec3cafef819e760c8e",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/0c55df3c612d22fb13e991cafc0bf2cf41f37ec3",
    "content": "AN\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/0e193d05e77da73a2e2fb4863d0bc87ba401d109",
    "content": "@"
  },
  {
    "path": "fuzz/corpus/qpack_decode/0ebf87e99f2585f844687adbe4a8b8153d2f88c3",
    "content": "($1**1**1**1**"
  },
  {
    "path": "fuzz/corpus/qpack_decode/11fe182aaee18d0f3c116134ecec55a29ff3b2e0",
    "content": "ll-"
  },
  {
    "path": "fuzz/corpus/qpack_decode/1256f042c18db040bb768fe235c460449da5a8b2",
    "content": "\u0013'\u000b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/131f91d526dd5804dd8f87200467408757ef5fe8",
    "content": "C"
  },
  {
    "path": "fuzz/corpus/qpack_decode/13397261a996f329edf9dc405491afb9dc1754ac",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/13f4bf3f5c8ad3bdd600f935613e1f953647c1ab",
    "content": "@D\u0001?~\u0001\t]\u0001?~\u0001?D\u0001]\u0001?~\u0001?]\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/145fb971177b418075853fe53ef7c7c61e6125c7",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/1535300c7581bc65e5dcd660ff68ffe2a3061117",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/16cd5a44f52703a9a049a91522e2c7f7c22fd195",
    "content": "*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/1708e3c33398a5ce728134c261f7d4b82727f415",
    "content": "\u0011"
  },
  {
    "path": "fuzz/corpus/qpack_decode/17d6fdc9d91f517d76ef4742446386a0f06e2559",
    "content": "0\u0016"
  },
  {
    "path": "fuzz/corpus/qpack_decode/180a5ecd0cb6814e8c6ad86a24e878aed6e52736",
    "content": "((c\u0001c"
  },
  {
    "path": "fuzz/corpus/qpack_decode/18eea3b493b082490165d372eafba9e495bff3a2",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/191e6f0e8b11f7b5fc279d9dcfde3fa8d4d9c92a",
    "content": "AN\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/1cb53efeab8cb7153d77118db868580a991ee913",
    "content": "})с)с)с)с"
  },
  {
    "path": "fuzz/corpus/qpack_decode/1cb8dfcd5bd58feff7cd4b8090b6f9ff287c1ad3",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/1cc1a4a09897a4943b359c6bbe686dbaaaac2109",
    "content": "+"
  },
  {
    "path": "fuzz/corpus/qpack_decode/1d76cef839ee530ef9221cd33c439649de627947",
    "content": "&"
  },
  {
    "path": "fuzz/corpus/qpack_decode/1e07554d871857d402da21228e5da62ffa31c5e0",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/1e4048ffc3fcaf1e716476a7ba772763c9cc90d9",
    "content": "\u0001&\"0'\u0001&\"0'\u0001&"
  },
  {
    "path": "fuzz/corpus/qpack_decode/201342cdddb3b8c686fb502664ac91617a4aef89",
    "content": "\u0013N\u000b\u000b\u000b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/20c6a49a1636af8568f2e0873d4b76d8e59c164f",
    "content": "(1*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/213e374bf2669c82e5ed6a1451b491d3d7d3e341",
    "content": "\n(-"
  },
  {
    "path": "fuzz/corpus/qpack_decode/23b5f31a247edf77fb3064ac694ce18e74ac0c5c",
    "content": "\u0004}\u0007"
  },
  {
    "path": "fuzz/corpus/qpack_decode/272fe3549b8e8d0e84bfbc32ff32593165bce833",
    "content": " # #"
  },
  {
    "path": "fuzz/corpus/qpack_decode/27e9edabbd020c9a29cb58d6dd6a7c2e8f0c7740",
    "content": "7"
  },
  {
    "path": "fuzz/corpus/qpack_decode/291912a79d5fcd55e47f6e4fbac6c82a697fb249",
    "content": "$?~"
  },
  {
    "path": "fuzz/corpus/qpack_decode/2bbecaa186302b609e2f499bc0146b4904b369df",
    "content": "m!xmx\u000f\u000f"
  },
  {
    "path": "fuzz/corpus/qpack_decode/2d92a1deb1df02a146bbfaf50d5ae7156943939e",
    "content": "\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/2e359dfc8bcdc849f9bdf8b1b5ba1707b960533f",
    "content": "\u0011\u0010"
  },
  {
    "path": "fuzz/corpus/qpack_decode/2fcc837fdcf19093fc07481e105d0e106c603f02",
    "content": "\u0002&&"
  },
  {
    "path": "fuzz/corpus/qpack_decode/3130e6e549eb6bf75bfdf15f6f0e2a9a490821d0",
    "content": "1)"
  },
  {
    "path": "fuzz/corpus/qpack_decode/318edab5378d4d506bea461b58afea23e7e27fbb",
    "content": "+"
  },
  {
    "path": "fuzz/corpus/qpack_decode/31ea2d281fd0e7db70cc9c5f4b916708d7e9392c",
    "content": "\u0001@@"
  },
  {
    "path": "fuzz/corpus/qpack_decode/323456136961397da3c0429a6492bd1f9190757a",
    "content": "\u000161?\u0001?1\u00010'\u0001\u001d?1~\u0001?!:\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/3400cabf81d7839890cb2045f9b64eff662c9330",
    "content": "\n(((("
  },
  {
    "path": "fuzz/corpus/qpack_decode/351ebcc9797063b4f3fe80b7c117621acec912ea",
    "content": "POS?9\u000f"
  },
  {
    "path": "fuzz/corpus/qpack_decode/37b7e167db697587b5661c569ac38ffb5f498e41",
    "content": "\n*("
  },
  {
    "path": "fuzz/corpus/qpack_decode/39412207a2044393327d87cbaabddde289f4d434",
    "content": "'2\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013\u0013"
  },
  {
    "path": "fuzz/corpus/qpack_decode/3b2fd4c127c54578e31a151052e23bbdc035fb9d",
    "content": "A"
  },
  {
    "path": "fuzz/corpus/qpack_decode/3b351c9b1b7ee7c6a3a88001c18557b91f977a31",
    "content": "f"
  },
  {
    "path": "fuzz/corpus/qpack_decode/3d2d01a661aa7d23989b113fad1835f983d857ea",
    "content": "20 "
  },
  {
    "path": "fuzz/corpus/qpack_decode/3f26bf019f07d4df95145e61127528bda535981d",
    "content": "*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/3f4bf38ee41bc3c86809c2884de76b8195418e2e",
    "content": "\r"
  },
  {
    "path": "fuzz/corpus/qpack_decode/3fa5bfd93317ad25772680071d5ac3259cd2384f",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/3fc3e1d986713a39fd7a522ca03cc840be9ce5e8",
    "content": "10\u000f"
  },
  {
    "path": "fuzz/corpus/qpack_decode/40d92f230c5257a383424d5db622e0fb88274911",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/417a31b64daf2a5b24cdfa2ccdbbfb729dafbb70",
    "content": "!6"
  },
  {
    "path": "fuzz/corpus/qpack_decode/434233ebede6c984ab2acc096a19f1a343277142",
    "content": "\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/44b33774470d8f7fbc227e1c5ffa90add6c6a0f1",
    "content": "--"
  },
  {
    "path": "fuzz/corpus/qpack_decode/45187832dae51e95536168a4d2493f59c3f89765",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/453ea3a491331d7bd3d63603a80546a14ab2fae8",
    "content": "+Pte\u0013\u0013\u0013\u0013"
  },
  {
    "path": "fuzz/corpus/qpack_decode/4776eabfe1d9ec687982caa8f2f4e64d17481228",
    "content": "11"
  },
  {
    "path": "fuzz/corpus/qpack_decode/487da629861a85e37c66b8e2a607189a0c24947d",
    "content": "\u000bJ\u000b\u000b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/4a4d329659e1c831db7a1522dd6b0dab0c719ff5",
    "content": "\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/4b12f06f88334c62a540bb83a5ad65d92c1159e6",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/4bf9dc9638a574b7025c0e560af75cf3796edd6f",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/4d3b8a3c50cb37f29ee87dcd3116413107277b84",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/4f7e760cd98486e9dcdd3315a05778d9a7f20c13",
    "content": "\u0005\b\b\b\b\b\b\b\b\b\b\b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/4fbec485b09eba1377acc3af638827b7eac3ae36",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/50c4509efda8dcbea0e0eec77b59aa624e176d94",
    "content": "**\n(*1*\n(1**1*\n(1**\n(*1***\b(*(*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/50df183b80223dde85dd784425447f922e0c6f11",
    "content": "(99"
  },
  {
    "path": "fuzz/corpus/qpack_decode/51b514f0a18f88f7a1cd4d8df74e05a50fef9218",
    "content": "=%"
  },
  {
    "path": "fuzz/corpus/qpack_decode/526de4e1097375983dadc85a08f327047649ded4",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/54009614f8e5896556131bfc2e9676c12d69a11a",
    "content": "z"
  },
  {
    "path": "fuzz/corpus/qpack_decode/541bb4db914646ca8bfa3e4548101d77abafec9c",
    "content": "\u0001\bw\u0001\bz\u0001\by\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/54c65adb6cb5a0d154adfbbd3dbe86cb174eea73",
    "content": "??2\u001d&\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/55c38e9253befc318a7196921cc0092689c17537",
    "content": "?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/587769c92dd937366fd2ead23e769c393f0a6fdc",
    "content": "\n.((((((8("
  },
  {
    "path": "fuzz/corpus/qpack_decode/58fbb611237c8c0fae4ba801b5de12304f91b15c",
    "content": "**\n(*1*\n(1**\n(*1**1*\n(1**\n(*1***\b(**\n(*1*\n(1**\n(*1*\n(1**\n(*1**"
  },
  {
    "path": "fuzz/corpus/qpack_decode/5b1047c5201637a55f95889e2ac707abd5dfa825",
    "content": "\u0001?w\u0001?w\u0001?w\u0001?w\u0001;w\u0001?w\u0001;w\u0001?w\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/5b8a0b61912fcc0d9a3bdbf2959b8421e80e07b5",
    "content": "ll-"
  },
  {
    "path": "fuzz/corpus/qpack_decode/5c41c5cd2b66d690404746bd14329bba14d67a9b",
    "content": ";,#,\u0003"
  },
  {
    "path": "fuzz/corpus/qpack_decode/5df91c5df959d8ec3ac4da0636526c9a572bb769",
    "content": "\nA"
  },
  {
    "path": "fuzz/corpus/qpack_decode/5e37509ce51787d029472017487a5b21d23e0ece",
    "content": "-"
  },
  {
    "path": "fuzz/corpus/qpack_decode/5e7bd00a6f0c996622aab0d05c4f5d2f8751dfb4",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/6123af566cad0b86fe22a983b6702f1ca4da0157",
    "content": "z"
  },
  {
    "path": "fuzz/corpus/qpack_decode/6146e8e16f7e4182e92410337d3c06d2b929e68f",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/61eb4bcb9f234c00bd5231b36a143c2bef3f512a",
    "content": "\u0002?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/639777406e879d6ba483c85bafa806670db74fea",
    "content": "\u0002w\u0007"
  },
  {
    "path": "fuzz/corpus/qpack_decode/647e242902e268b0435254c784787528dcec8b22",
    "content": "Dw\u0001\u0001}\u0001\u0001~\u0001\u0001~\u0001D"
  },
  {
    "path": "fuzz/corpus/qpack_decode/64a1bede71641692664d69d4f95adc35953f733f",
    "content": "(c"
  },
  {
    "path": "fuzz/corpus/qpack_decode/64f4c0a2c65b2307e28486e212d8061ced9f0d21",
    "content": "#"
  },
  {
    "path": "fuzz/corpus/qpack_decode/65c0591f541af77e010716ce5553ae615785be3d",
    "content": "\u0001 \u0001[ \u0001[~\u0001? \u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/663f5765df1e7de8a9bb911547c108f621c1b4eb",
    "content": "#\u001cO"
  },
  {
    "path": "fuzz/corpus/qpack_decode/669436d423138023429e78aaabff715d271e8e03",
    "content": "}M"
  },
  {
    "path": "fuzz/corpus/qpack_decode/6abda42115d3c9dc45890121afb3d9f688959ff4",
    "content": "\n(1**\b(**\b(**\b(**\b(**\n(**\b(**\b(**\n(**\n(*1**\b(**\n(**\b(**\b(**\n(**\b(*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/6c8f26698182d8ad63f897e92cf8863459d728ca",
    "content": "*\b\b\b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/6cb208ccdc982010795407a2e96651e468fad313",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/6e57784e75db443ae9419f08c82aa84ffe1b4b46",
    "content": ",\u0002\u0017\u0017\u0017"
  },
  {
    "path": "fuzz/corpus/qpack_decode/70c9156a6d37c9f56a8c44a47ec9ed456b822dc8",
    "content": "="
  },
  {
    "path": "fuzz/corpus/qpack_decode/717fd94b003a8b72c2ea42e2f67cfbe2281da118",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/7190700d24d8d33d1a5b6e56c4af965a8a944541",
    "content": "\u0004}("
  },
  {
    "path": "fuzz/corpus/qpack_decode/736ec334b327f40b0834454303b19e7615dff1e3",
    "content": "\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/73b875e881b5588e9119fa83f3c23c7019414547",
    "content": "\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/73cdd545cb9f0e7e357673a625ca10993758f404",
    "content": "?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/74f85de06629b9e866398c154ca87952fd990948",
    "content": "460\u0001>"
  },
  {
    "path": "fuzz/corpus/qpack_decode/756952f980bfd880a05f9973718edda98dada4d8",
    "content": "\u0001\u000b\u000b\u000b\u000b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/75e80ec43df21fc8fa2fe49078d70d4cc392e52f",
    "content": "&"
  },
  {
    "path": "fuzz/corpus/qpack_decode/770fe083b0716f6042bb3d24a624e1f5901f9bd8",
    "content": "OOP\u0002\u000f%"
  },
  {
    "path": "fuzz/corpus/qpack_decode/7754a75544d8fb96c08f94894e0120f3b29d4d06",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/785f8c9d8025cc5d65840af5ccff5c7e08c8c855",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/79d667ed0657468d439109cb4bc7e2955103bd90",
    "content": "\u0001*2\u0001{\u0001U"
  },
  {
    "path": "fuzz/corpus/qpack_decode/7a939d3971804e3750decfc05458f72444c908d4",
    "content": "(*(**(*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/7b5235bc7684f131d3b91588be3b5279b9a2d023",
    "content": "EAL\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/7ba99e1d77bf3c9fea760a6d0a4f6c40451c43a2",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/7bde08642b72f7ee94ca4411d004097185d0591f",
    "content": "1)"
  },
  {
    "path": "fuzz/corpus/qpack_decode/7ed7943400658ed3503632443baff0bc87eb0426",
    "content": "["
  },
  {
    "path": "fuzz/corpus/qpack_decode/7fcb35f23a8754a3f9e58c8cb429c1067e9967db",
    "content": "\u0001??\u0001?1?\u0001?1\u0001?1'\u0001/1\u0001?1\u0001>1'\u0001?1?\u0001?1\u0001?1\u0001?1\u0001?1'\u0001?1?\u0001?1\u0001?1'\u0001/1\u0001?1'\u0001?1?\u0001?1\u0001?1'\u0001/1\u0001?1\u0001?1'\u0001?1?\u0001?1\u0001?1\u0001?1\u0001?1'\u0001?1?\u0001?1\u0001?1$\u0001?1o\u0001?1~\u0001?!:\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/7fd3d98bad8f3a2e785ecc0ef0f6967be57452f2",
    "content": "\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/803cee12c08d6b4b9c3cde4ffdfaba068b2cdee4",
    "content": "(!-!-(!-("
  },
  {
    "path": "fuzz/corpus/qpack_decode/810a06d12925ae06b2d54ea5407023adce355c32",
    "content": ",\u0017"
  },
  {
    "path": "fuzz/corpus/qpack_decode/83490f0a81a95963b203d7c7827c82e3b03c8e6e",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/858416dc22c6c02c59065cbde2faba3d09729add",
    "content": "\u0001D]\u0001\u0005z\u0001?z\u0001\u0005z\u0001\u0005]\u0001\u0005z\u0001?]\u0001\u0005]\u0001\u0005z\u0001D]\u0001\u0005z\u0001D]\u0001\u0005z\u0001?z\u0001D]\u0001\u0005z\u0001?z\u0001\u0005z\u0001?]\u0001\u0005z\u0001D]\u0001\u0005z\u0001D]\u0001\u0005z\u0001\u0005z\u0001?]\u0001\u0005]\u0001\u0005z\u0001D]\u0001?z\u0001D]\u0001\u0005z\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/85e53271e14006f0265921d02d4d736cdc580b0b",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/869989fb0161c02968c6bd7bbf56dae9fb3e77b0",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/86c1a83d4e198ca6723093ddf12a3949aaf85544",
    "content": "(("
  },
  {
    "path": "fuzz/corpus/qpack_decode/86e1b600bb9f8e83720c6a526f3bc479506fe109",
    "content": "2(("
  },
  {
    "path": "fuzz/corpus/qpack_decode/87a882b0ae341d53a545f20032cee1eab3e62eea",
    "content": "(((((((((((((((("
  },
  {
    "path": "fuzz/corpus/qpack_decode/891163a5f443198c9578eee25ab7d291a2f62b59",
    "content": "HE"
  },
  {
    "path": "fuzz/corpus/qpack_decode/8a21554ccbb758bf272c44ed2bfb245122766ea2",
    "content": "($1*\u0001*1*\u0001*1**1**"
  },
  {
    "path": "fuzz/corpus/qpack_decode/8b8edf8bcbebd1c9c62d9336118b8b44ac6a4209",
    "content": "\f"
  },
  {
    "path": "fuzz/corpus/qpack_decode/90ce68b6646e84c9f116340784ab3ad5ff624687",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/934d273830e2a7a5c07f38e59239e6758dddfc44",
    "content": "M[˘"
  },
  {
    "path": "fuzz/corpus/qpack_decode/945ae4d631071140021ac173601aef8db262d0d5",
    "content": "\u000b(0"
  },
  {
    "path": "fuzz/corpus/qpack_decode/95295d3777556ef727f606b8e791ec8ef400b713",
    "content": "0"
  },
  {
    "path": "fuzz/corpus/qpack_decode/95e1c34fcc230143378464a20b4e85ec6c37aee5",
    "content": "\n?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/95ed069cd723680afda0c3d2904acf069c9064f8",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/9656eec3092f2163ad358c7eec031fdf30820dea",
    "content": "\u0003"
  },
  {
    "path": "fuzz/corpus/qpack_decode/9713162d3cecbc5394629d9623f8d737c3be7a33",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/9a9e1d6e7b350bc7c3ba7a9cc13a562ada0312b0",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/9b0f4da82f1d8e0efe3fd602941dddbc5eee3d08",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/9b7c3ec6265973493d02ba2b98e3ebdf5ba6323d",
    "content": "`\u0002`1\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/9c24367ab94cda9d22ca3464016ee4b743e2308d",
    "content": "И"
  },
  {
    "path": "fuzz/corpus/qpack_decode/9c3c634ff44887ede4f9b028a3ac209c7d4ea26c",
    "content": "'\u0013\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b\u000b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/9df0df441a54bdf165c2ce4643fbd15aae1fa463",
    "content": "\u0012[U"
  },
  {
    "path": "fuzz/corpus/qpack_decode/9f33ead21ba43e84ec1a570dd4e709664f8eda7c",
    "content": "?!\u001e?!\u001e?!!"
  },
  {
    "path": "fuzz/corpus/qpack_decode/9f934df7c406b1f9bfa339130306ccc002747eac",
    "content": "\u000f\u000f\u000f\u000f\u000f\u000f\u000f\u000f"
  },
  {
    "path": "fuzz/corpus/qpack_decode/9fa727784ecae3d419f51dedc744ac7e9130d455",
    "content": "\n("
  },
  {
    "path": "fuzz/corpus/qpack_decode/9ff1234ffe8936933291568ac21530bdc9ee89bb",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/a0722448a80d3043f686ad771fc2d3fb26b99fc7",
    "content": "\u0002\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007"
  },
  {
    "path": "fuzz/corpus/qpack_decode/a67555fd82d1d5587f41a3fa8447e36be09eaac5",
    "content": "((((((((((((((((((((((((((((((((("
  },
  {
    "path": "fuzz/corpus/qpack_decode/a6caac2b0aa0c607c04c465bb282ec11aa389cb1",
    "content": "U\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/a7bd4d4065760d9150f3a8f9962416d7ee4a680e",
    "content": "\n((("
  },
  {
    "path": "fuzz/corpus/qpack_decode/a7dd39fb46714cc10f5b008b6f52b82c9fb3b05c",
    "content": "\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/a995595ca2fce5c7678c6b611b2de4bb57f49c83",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/a9a61e835552cf56620ec2f22cd76989dbeb4ddb",
    "content": "*1**1**1**1**1**1**1**1*+"
  },
  {
    "path": "fuzz/corpus/qpack_decode/ae80c54c939ef06fdf396beafe5e6ed05c1a1d04",
    "content": "%"
  },
  {
    "path": "fuzz/corpus/qpack_decode/b01836131ee3e9976a3e3d019c721997614ffb67",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/b09fb2197744b96c8c894f30540298b9081ab2e6",
    "content": "\u001b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/b0de7fc0f0a12e637a923ec311d3ba65fee56ec1",
    "content": ".."
  },
  {
    "path": "fuzz/corpus/qpack_decode/b1d09cdd023a54e2c8f72543aa708a3475616f4c",
    "content": "!)!"
  },
  {
    "path": "fuzz/corpus/qpack_decode/b57f34aaab6de1aab5122a61ed8f846bbf6c5642",
    "content": "**\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\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\n\n\n\n"
  },
  {
    "path": "fuzz/corpus/qpack_decode/b8ae903b8170bc36cc86faae9f1a51ec7fe2ab67",
    "content": "(B(\u000f(B(K( BR BK((y(\u000f(BBR KB((\u0012B+!B(\u000f(B(K( BR BK((y(\u000f(BBR KB((\u0012B+!R KB((\u0012(B(\u000f(BBR BBK((y(\u000f(BBR KB(B(\u000f(BK( R KB((\u0012(B(\u000f(BBR BBK((y(\u000f(BBR KB(B(\u000f(BK( B"
  },
  {
    "path": "fuzz/corpus/qpack_decode/b8f08a794900413cd882d0d5ff73da15be3fa33b",
    "content": "300(-"
  },
  {
    "path": "fuzz/corpus/qpack_decode/bab08480ac09fca88d50a3a6ebd9369789436df7",
    "content": "lYl-"
  },
  {
    "path": "fuzz/corpus/qpack_decode/bae73a3a1dfcd41675ec68a4e2e00c08cb9d103f",
    "content": "@"
  },
  {
    "path": "fuzz/corpus/qpack_decode/bb5ed80add79653c6661542d671bcce98d3889d4",
    "content": "11"
  },
  {
    "path": "fuzz/corpus/qpack_decode/bd325815b5404905ca0fdd7f47f28c44f0faa0bc",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/bf6d8aa8e500d7a9073f85828c07ba251275916b",
    "content": "1/"
  },
  {
    "path": "fuzz/corpus/qpack_decode/bfdf1777f0f8441d701e7b43393c54a312febf2d",
    "content": "\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/c077e58eecf841678475e04619f6bd11e9c3b68c",
    "content": "*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/c266faa97ba32e1a949c0e34642b693ceb6b0c3e",
    "content": "*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/c358aaee2cbbe11ac133f251d7996cbe4aa0b502",
    "content": ":"
  },
  {
    "path": "fuzz/corpus/qpack_decode/c47497efd7175919cf755e3239683f59af8f014b",
    "content": "\u001b\u001b?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/c7b6a8b7b1f6a2a25674d7055edbde59ae3a04e6",
    "content": "\u000f\u000fw\u00010R\u0001u"
  },
  {
    "path": "fuzz/corpus/qpack_decode/c8232da719422a3a91332975d029bba394c2668e",
    "content": "101"
  },
  {
    "path": "fuzz/corpus/qpack_decode/cad8c3a1bfe13c857a7594cd46590c7bb8b5d761",
    "content": "\u0001\u0004-'"
  },
  {
    "path": "fuzz/corpus/qpack_decode/cb47bf784ed81081edf093a7e6ddd84d9f36ddd1",
    "content": "0"
  },
  {
    "path": "fuzz/corpus/qpack_decode/cc0c53f907a24cb99096052e35ffd2a8ba930550",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/cc15b68f5bb7954e4e1ab8b403759ed76d58a43a",
    "content": "1*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*1*\u0001*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/cc9bbac1d63fe528712481c84712ff067dff7bc6",
    "content": "q"
  },
  {
    "path": "fuzz/corpus/qpack_decode/ce2fd5e03c9528492e2df1b05cd219121049f68f",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/ce7fec2b645d91b42a118c6f3038dec18f5fe2f5",
    "content": "C\u0010\u0010"
  },
  {
    "path": "fuzz/corpus/qpack_decode/cef28e3f8aafca1f3d532519e3280921404e39eb",
    "content": "\n(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((("
  },
  {
    "path": "fuzz/corpus/qpack_decode/cf8f891bbf62c528c48856605cb3ecfcafc8259c",
    "content": "\u0003\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/cfa2cce42084f2ecca8e9e7f7df1e91caff5f549",
    "content": "\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d344cfd756bfadd10263656c4f65e03eb80ce08f",
    "content": "\u0001#\u0001#"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d4163fa91f294e540c438a208fa981ea7c246828",
    "content": "*"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d42428711dc6c18ec63bfa1ba1f7b576895b37cd",
    "content": "#*2"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d469311897f8e44826c7344e8c994b3257a5c25c",
    "content": "*''"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d4ef7999eeb79ec5baa709ccf52830c2cc0d1c40",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/d56de425875a70a8feb53dddde2a0d0017adcb1a",
    "content": "\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?]\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d5eb1d2078f1ffbc37a1841eaf544b32975343c5",
    "content": "++\u001a"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d6583c31e97445fc24728efc159e30805e3db442",
    "content": "(B(\u000f(BBR BKB(BB(E +BB((BBR BKB(B BK1B((\u0012BB BBB(\u000f(R BKB(B( +BBRBK(B RBK( B"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d6639c48db5279063ec045bdbd7a033961f4d3b8",
    "content": " "
  },
  {
    "path": "fuzz/corpus/qpack_decode/d67ef4494e303987b9746154bbf2f9f48c3ab0d9",
    "content": "2"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d74aeb5306c728e421f0667a2cc1eb003279333d",
    "content": "(((("
  },
  {
    "path": "fuzz/corpus/qpack_decode/d765b66b30db695b6362c135a1aa7f0221e39385",
    "content": "STT\u0012\u000f\u0012\u0012\u0012\u0001"
  },
  {
    "path": "fuzz/corpus/qpack_decode/d81a6d8023a09c85e7c55c0668b6651021850f40",
    "content": "(\n(**\b(**\b(**\b(**\b(**\n(**\b(**\b(**\n(**\n(**\b(**\n(**\b(**\b(**\n(**\b(**\b("
  },
  {
    "path": "fuzz/corpus/qpack_decode/d921a2733ab14769d6a730440d766b446338a949",
    "content": "*)ρ\u000f"
  },
  {
    "path": "fuzz/corpus/qpack_decode/dacafe3e43b51aff60900a9e780ee5ad8f6256ea",
    "content": "`\u001a\u001a\u001a\u001a"
  },
  {
    "path": "fuzz/corpus/qpack_decode/db15c6757498972a5095be15a1a67024530fec59",
    "content": "7"
  },
  {
    "path": "fuzz/corpus/qpack_decode/db30e7150bbfdce48a17f330978fa25f5ffc07c9",
    "content": "))/)/)/))/)/)/"
  },
  {
    "path": "fuzz/corpus/qpack_decode/dd4e13456d579bcdc7dbb13c15ab237b37e647fa",
    "content": "(*\n**\n(*\n(**\n(**\n(\u0001*1*\u0001*1*\u0001*1*\u0001*(+"
  },
  {
    "path": "fuzz/corpus/qpack_decode/df1fbec132a37395d23e7022ae97cccccb2d19b8",
    "content": "(*\n(**\n(**\n(**\n(**\n(**\n(+*\n(**\n(+"
  },
  {
    "path": "fuzz/corpus/qpack_decode/dfdd62c4a1a966409489343f008e997eff0d35b7",
    "content": "\u0005\u001f"
  },
  {
    "path": "fuzz/corpus/qpack_decode/e09b387b4d73f3d6210e54177b71b4dd90ac5ff3",
    "content": "\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/e17eab18eb549ebb980d25cc2bf6390c6547bd69",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/e2e0322e22b3e621ed80fd300a33901a86234949",
    "content": "  "
  },
  {
    "path": "fuzz/corpus/qpack_decode/e2f464dc8c5062fc5eb5d68ee6483f54728c06f2",
    "content": "11)/"
  },
  {
    "path": "fuzz/corpus/qpack_decode/e2f67c31fabafb2a8635756c05c2b82ea813ee1d",
    "content": "j"
  },
  {
    "path": "fuzz/corpus/qpack_decode/e4c9196fb5ada9e11480ee92447c2ca0a2e7000a",
    "content": "\b"
  },
  {
    "path": "fuzz/corpus/qpack_decode/e7330e699efa1badbd015f27e5cba40daa3c3957",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/e8072b2c7f808ad4cf8a051fc3923438b5353e28",
    "content": "\n(((((((((((((((((((((((((((((((("
  },
  {
    "path": "fuzz/corpus/qpack_decode/e92cead9c2e0461125338180ad72cb048d92adaa",
    "content": "1"
  },
  {
    "path": "fuzz/corpus/qpack_decode/ea063927210c7b1ac95c4cbdeaec39e905aa5911",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/ea842236c6f3c6aebe7d2df698bed3b0e749c50e",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/eb8898b76241ea1810edde7c476090ecceefc7c0",
    "content": "2((8"
  },
  {
    "path": "fuzz/corpus/qpack_decode/ec4f16c29fbacb3f0fb575b3e87dfbcb3fbde121",
    "content": "))/))/)/)/)/)/"
  },
  {
    "path": "fuzz/corpus/qpack_decode/ec95ac8245955dd02c67ed21c02209a021a4433c",
    "content": ""
  },
  {
    "path": "fuzz/corpus/qpack_decode/efa658c2c4cb09149b1dad8d9f700b845b34e109",
    "content": "\u0001\u001f(\u0001\u001f(\u0001?0\u0001\u001f(\u0001\u001f(\u0001\u001f(\u0001?0\u0001\u001f(\u0001?x\u0001\u001f(\u0001\u001f(\u0001\u001f(\u0001\u001f(\u0001?0\u0001\u001f(\u0001\u001f(\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/f26b2e4af42c6f443221eab1e852d0e357086e16",
    "content": "\t\bz\t\t\t%\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\u0002\t\t\t\t\t\tx\b\t\t\t\t\t\t\tz\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tH\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
  },
  {
    "path": "fuzz/corpus/qpack_decode/f37c87ed1174080a3144f74ea3ba5f51c8a10235",
    "content": "="
  },
  {
    "path": "fuzz/corpus/qpack_decode/f3a557fcc5716ae116b98c50c55b5de5aebc6039",
    "content": "+"
  },
  {
    "path": "fuzz/corpus/qpack_decode/f3f24aee46a0c212e456eff9d62503efbe7b8230",
    "content": "+\u001d"
  },
  {
    "path": "fuzz/corpus/qpack_decode/f46b49159352b4ebff3a4f5fd715c3482485b537",
    "content": " # #\u001d\u001d"
  },
  {
    "path": "fuzz/corpus/qpack_decode/f77786dfa7307e2cbf078f7d26e7f5fc5375f08c",
    "content": "(B(\u000f(BBR BKB(BB(E +BB((BBR BKB(B BK1B((\u0012BB BBB(\u000f(R BKB(B(B +BBRBK(B RBK( B"
  },
  {
    "path": "fuzz/corpus/qpack_decode/f81464810d2cde5ed21e362b3f3944eea8ff95ab",
    "content": "("
  },
  {
    "path": "fuzz/corpus/qpack_decode/f86962aca14a0e7462055e8e62437fc999883dae",
    "content": "\u0002"
  },
  {
    "path": "fuzz/corpus/qpack_decode/fab0345ed27f30cb3010581c8ebd0f25d1bf4bd8",
    "content": "<"
  },
  {
    "path": "fuzz/corpus/qpack_decode/fb44042bceaf617e5d1e0217d838eb9c4863794b",
    "content": "\u0001?1\u0001?1&\u0001>1\u0001?"
  },
  {
    "path": "fuzz/corpus/qpack_decode/fea7e1ab6c991aebb27b0e60dd9f63e372463eeb",
    "content": "10(("
  },
  {
    "path": "fuzz/corpus/qpack_decode/ff6a56fcc80558f1638fd95a97b39e13c9b6d651",
    "content": "1"
  },
  {
    "path": "fuzz/mayhem/packet_recv_client/Mayhemfile",
    "content": "project: protocols/quiche\n\ntarget: packet-recv-client-libfuzzer\n\nimage: ${MAYHEM_DOCKER_REGISTRY}/protocols/quiche-libfuzzer:latest\n\nadvanced_triage: false\n\ncmds:\n  - cmd: /home/mayhem/packet_recv_client\n    libfuzzer: true\n    sanitizer: true\n    timeout: 5\n    env: {}\n"
  },
  {
    "path": "fuzz/mayhem/packet_recv_server/Mayhemfile",
    "content": "project: protocols/quiche\n\ntarget: packet-recv-server-libfuzzer\n\nimage: ${MAYHEM_DOCKER_REGISTRY}/protocols/quiche-libfuzzer:latest\n\nadvanced_triage: false\n\ncmds:\n  - cmd: /home/mayhem/packet_recv_server\n    libfuzzer: true\n    sanitizer: true\n    timeout: 5\n    env: {QUICHE_FUZZ_CRT: /home/mayhem/cert.crt, QUICHE_FUZZ_KEY: /home/mayhem/cert.key}\n"
  },
  {
    "path": "fuzz/mayhem/packets_posths_server/Mayhemfile",
    "content": "project: protocols/quiche\n\ntarget: packets-posths-server-libfuzzer\n\nimage: ${MAYHEM_DOCKER_REGISTRY}/protocols/quiche-libfuzzer:latest\n\nadvanced_triage: false\n\ncmds:\n  - cmd: /home/mayhem/packets_posths_server\n    libfuzzer: true\n    sanitizer: true\n    timeout: 5\n    env: {QUICHE_FUZZ_CRT: /home/mayhem/cert.crt, QUICHE_FUZZ_KEY: /home/mayhem/cert.key}\n"
  },
  {
    "path": "fuzz/mayhem/packets_recv_server/Mayhemfile",
    "content": "project: protocols/quiche\n\ntarget: packets-recv-server-libfuzzer\n\nimage: ${MAYHEM_DOCKER_REGISTRY}/protocols/quiche-libfuzzer:latest\n\nadvanced_triage: false\n\ncmds:\n  - cmd: /home/mayhem/packets_recv_server\n    libfuzzer: true\n    sanitizer: true\n    timeout: 5\n    env: {QUICHE_FUZZ_CRT: /home/mayhem/cert.crt, QUICHE_FUZZ_KEY: /home/mayhem/cert.key}\n"
  },
  {
    "path": "fuzz/mayhem/qpack_decode/Mayhemfile",
    "content": "project: protocols/quiche\n\ntarget: qpack-decode-libfuzzer\n\nimage: ${MAYHEM_DOCKER_REGISTRY}/protocols/quiche-libfuzzer:latest\n\nadvanced_triage: false\n\ncmds:\n  - cmd: /home/mayhem/qpack_decode\n    libfuzzer: true\n    sanitizer: true\n    timeout: 5\n    env: {}\n"
  },
  {
    "path": "fuzz/src/lib.rs",
    "content": "use std::path::Path;\nuse std::path::PathBuf;\n\nuse quiche::h3::NameValue;\n\npub struct PktsData<'a> {\n    pub data: &'a [u8],\n}\n\npub struct PktIterator<'a> {\n    data: &'a [u8],\n    index: usize,\n}\n\nimpl<'a> Iterator for PktIterator<'a> {\n    type Item = &'a [u8];\n\n    fn next(&mut self) -> Option<Self::Item> {\n        if self.index < self.data.len() {\n            let start = self.index;\n            if self.index + 4 <= self.data.len() {\n                for i in self.index..self.data.len() - 4 {\n                    if &self.data[i..i + 4] == b\"fuzz\" {\n                        self.index = i + 4;\n                        return Some(&self.data[start..i]);\n                    }\n                }\n            }\n            self.index = self.data.len();\n            Some(&self.data[start..])\n        } else {\n            None\n        }\n    }\n}\n\nimpl<'a> PktsData<'a> {\n    pub fn iter(&self) -> PktIterator<'_> {\n        PktIterator {\n            data: self.data,\n            index: 0,\n        }\n    }\n}\n\npub fn reset_rand_for_fuzzing() {\n    extern \"C\" {\n        fn RAND_reset_for_fuzzing();\n    }\n\n    unsafe { RAND_reset_for_fuzzing() };\n}\n\n/// Returns the path to the X.509 certificate and key.\n///\n/// If `QUICHE_FUZZ_CRT` and / or `QUICHE_FUZZ_KEY` are set, their value is\n/// used, otherwise in order to accomodate different fuzzing environments, this\n/// either returns relative paths (e.g. \"fuzz/cert.crt\") when running from a\n/// clone of the git repository, or absolute paths based on `argv[0]` when\n/// running bare executable (as used by OSS-Fuzz).\npub fn get_cert_path() -> (String, String) {\n    let fuzz_dir = if Path::new(\"fuzz/\").exists() {\n        PathBuf::from(\"fuzz/\")\n    } else {\n        // Get directory the fuzzer is running from.\n        let mut fuzz_dir = PathBuf::from(std::env::args().next().unwrap());\n        // Remove executable file name.\n        fuzz_dir.pop();\n        // Add \"fuzz\" subdirectory.\n        fuzz_dir.push(\"fuzz\");\n\n        fuzz_dir\n    };\n\n    let crt_path = std::env::var(\"QUICHE_FUZZ_CRT\").unwrap_or_else(|_| {\n        let mut crt_path = fuzz_dir.clone();\n        crt_path.push(\"cert.crt\");\n        crt_path.to_str().unwrap().to_string()\n    });\n\n    let key_path = std::env::var(\"QUICHE_FUZZ_KEY\").unwrap_or_else(|_| {\n        let mut key_path = fuzz_dir.clone();\n        key_path.push(\"cert.key\");\n        key_path.to_str().unwrap().to_string()\n    });\n\n    (crt_path, key_path)\n}\n\npub fn server_process(\n    pkt: &[u8], conn: &mut quiche::Connection,\n    h3_conn: &mut Option<quiche::h3::Connection>, info: quiche::RecvInfo,\n) {\n    let mut buf = pkt.to_vec();\n    conn.recv(&mut buf, info).ok();\n\n    if (conn.is_in_early_data() || conn.is_established()) && h3_conn.is_none() {\n        let h3_config = quiche::h3::Config::new().unwrap();\n\n        if let Ok(c) = quiche::h3::Connection::with_transport(conn, &h3_config) {\n            *h3_conn = Some(c);\n        }\n    }\n\n    if h3_conn.is_some() {\n        let h3c = h3_conn.as_mut().unwrap();\n        loop {\n            let r = h3c.poll(conn);\n\n            match r {\n                Ok((\n                    _stream_id,\n                    quiche::h3::Event::Headers {\n                        list,\n                        more_frames: _,\n                    },\n                )) => {\n                    let mut headers = list.into_iter();\n\n                    // Look for the request's method.\n                    let method = headers.find(|h| h.name() == b\":method\");\n                    if method.is_none() {\n                        break;\n                    }\n                    let method = method.unwrap();\n\n                    // Look for the request's path.\n                    let path = headers.find(|h| h.name() == b\":path\");\n                    if path.is_none() {\n                        break;\n                    }\n                    let path = path.unwrap();\n\n                    if method.value() == b\"GET\" && path.value() == b\"/\" {\n                        let _resp = [\n                            quiche::h3::Header::new(\n                                b\":status\",\n                                200.to_string().as_bytes(),\n                            ),\n                            quiche::h3::Header::new(b\"server\", b\"quiche\"),\n                        ];\n                    }\n                },\n\n                Ok((_stream_id, quiche::h3::Event::Data)) => {},\n\n                Ok((_stream_id, quiche::h3::Event::Finished)) => {},\n\n                Ok((_stream_id, quiche::h3::Event::Reset(_err))) => {},\n\n                Ok((_flow_id, quiche::h3::Event::PriorityUpdate)) => {},\n\n                Ok((_goaway_id, quiche::h3::Event::GoAway)) => {\n                    // Peer signalled it is going away, handle it.\n                },\n\n                Err(quiche::h3::Error::Done) => {\n                    // Done reading.\n                    break;\n                },\n\n                Err(_e) => {\n                    // An error occurred, handle it.\n                    break;\n                },\n            }\n        }\n    }\n\n    let mut out_buf = [0; 1500];\n    while conn.send(&mut out_buf).is_ok() {}\n}\n"
  },
  {
    "path": "fuzz/src/packet_recv_client.rs",
    "content": "#![no_main]\n\n#[macro_use]\nextern crate libfuzzer_sys;\n\nuse std::net::SocketAddr;\n\nuse std::sync::Mutex;\nuse std::sync::Once;\nuse std::sync::OnceLock;\n\nstatic CONFIG: OnceLock<Mutex<quiche::Config>> = OnceLock::new();\n\nstatic SCID: quiche::ConnectionId<'static> =\n    quiche::ConnectionId::from_ref(&[0; quiche::MAX_CONN_ID_LEN]);\n\nstatic LOG_INIT: Once = Once::new();\n\nfuzz_target!(|data: &[u8]| {\n    quiche_fuzz::reset_rand_for_fuzzing();\n\n    let from: SocketAddr = \"127.0.0.1:1234\".parse().unwrap();\n    let to: SocketAddr = \"127.0.0.1:4321\".parse().unwrap();\n\n    LOG_INIT.call_once(|| env_logger::builder().format_timestamp_nanos().init());\n\n    let mut buf = data.to_vec();\n\n    let config = CONFIG.get_or_init(|| {\n        let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n        config\n            .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)\n            .unwrap();\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.verify_peer(false);\n\n        config.discover_pmtu(true);\n        config.enable_early_data();\n        config.enable_hystart(true);\n\n        Mutex::new(config)\n    });\n\n    let mut conn = quiche::connect(\n        Some(\"quic.tech\"),\n        &SCID,\n        to,\n        from,\n        &mut config.lock().unwrap(),\n    )\n    .unwrap();\n\n    let info = quiche::RecvInfo { from, to };\n\n    conn.recv(&mut buf, info).ok();\n\n    let mut out_buf = [0; 1500];\n    while conn.send(&mut out_buf).is_ok() {}\n});\n"
  },
  {
    "path": "fuzz/src/packet_recv_server.rs",
    "content": "#![no_main]\n\n#[macro_use]\nextern crate libfuzzer_sys;\n\nuse std::net::SocketAddr;\n\nuse std::sync::Mutex;\nuse std::sync::Once;\nuse std::sync::OnceLock;\n\nstatic CONFIG: OnceLock<Mutex<quiche::Config>> = OnceLock::new();\n\nstatic SCID: quiche::ConnectionId<'static> =\n    quiche::ConnectionId::from_ref(&[0; quiche::MAX_CONN_ID_LEN]);\n\nstatic LOG_INIT: Once = Once::new();\n\nfuzz_target!(|data: &[u8]| {\n    quiche_fuzz::reset_rand_for_fuzzing();\n\n    let from: SocketAddr = \"127.0.0.1:1234\".parse().unwrap();\n    let to: SocketAddr = \"127.0.0.1:4321\".parse().unwrap();\n\n    LOG_INIT.call_once(|| env_logger::builder().format_timestamp_nanos().init());\n\n    let mut buf = data.to_vec();\n\n    let config = CONFIG.get_or_init(|| {\n        let (crt_path, key_path) = quiche_fuzz::get_cert_path();\n\n        let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n        config.load_cert_chain_from_pem_file(&crt_path).unwrap();\n        config.load_priv_key_from_pem_file(&key_path).unwrap();\n        config\n            .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)\n            .unwrap();\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n\n        config.discover_pmtu(true);\n        config.enable_early_data();\n        config.enable_hystart(true);\n\n        Mutex::new(config)\n    });\n\n    let mut conn =\n        quiche::accept(&SCID, None, to, from, &mut config.lock().unwrap())\n            .unwrap();\n\n    let info = quiche::RecvInfo { from, to };\n\n    conn.recv(&mut buf, info).ok();\n\n    let mut out_buf = [0; 1500];\n    while conn.send(&mut out_buf).is_ok() {}\n});\n"
  },
  {
    "path": "fuzz/src/packets_posths_server.rs",
    "content": "#![no_main]\n\n#[macro_use]\nextern crate libfuzzer_sys;\n\nuse std::net::SocketAddr;\n\nuse std::sync::Mutex;\nuse std::sync::Once;\nuse std::sync::OnceLock;\n\nstatic CONFIG: OnceLock<Mutex<quiche::Config>> = OnceLock::new();\n\nstatic SCID: quiche::ConnectionId<'static> =\n    quiche::ConnectionId::from_ref(&[0; quiche::MAX_CONN_ID_LEN]);\n\nstatic LOG_INIT: Once = Once::new();\n\nfuzz_target!(|data: &[u8]| {\n    quiche_fuzz::reset_rand_for_fuzzing();\n\n    let from: SocketAddr = \"127.0.0.1:1234\".parse().unwrap();\n    let to: SocketAddr = \"127.0.0.1:4321\".parse().unwrap();\n\n    LOG_INIT.call_once(|| env_logger::builder().format_timestamp_nanos().init());\n\n    let packets = quiche_fuzz::PktsData { data };\n\n    let config = CONFIG.get_or_init(|| {\n        let (crt_path, key_path) = quiche_fuzz::get_cert_path();\n\n        let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n        config.load_cert_chain_from_pem_file(&crt_path).unwrap();\n        config.load_priv_key_from_pem_file(&key_path).unwrap();\n        config\n            .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)\n            .unwrap();\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n\n        config.discover_pmtu(true);\n        config.enable_early_data();\n        config.enable_hystart(true);\n\n        Mutex::new(config)\n    });\n\n    let mut conn =\n        quiche::accept(&SCID, None, to, from, &mut config.lock().unwrap())\n            .unwrap();\n\n    let mut connc = quiche::connect(\n        Some(\"quic.tech\"),\n        &SCID,\n        from,\n        to,\n        &mut config.lock().unwrap(),\n    )\n    .unwrap();\n\n    let info = quiche::RecvInfo { from, to };\n\n    while !conn.is_established() || !connc.is_established() {\n        let flight = quiche::test_utils::emit_flight(&mut connc).unwrap();\n        quiche::test_utils::process_flight(&mut conn, flight).unwrap();\n\n        let flight = quiche::test_utils::emit_flight(&mut conn).unwrap();\n        quiche::test_utils::process_flight(&mut connc, flight).unwrap();\n    }\n\n    let mut h3_conn = None;\n    for pkt in packets.iter() {\n        quiche_fuzz::server_process(pkt, &mut conn, &mut h3_conn, info);\n    }\n});\n"
  },
  {
    "path": "fuzz/src/packets_recv_server.rs",
    "content": "#![no_main]\n\n#[macro_use]\nextern crate libfuzzer_sys;\n\nuse std::net::SocketAddr;\n\nuse std::sync::Mutex;\nuse std::sync::Once;\nuse std::sync::OnceLock;\n\nstatic CONFIG: OnceLock<Mutex<quiche::Config>> = OnceLock::new();\n\nstatic SCID: quiche::ConnectionId<'static> =\n    quiche::ConnectionId::from_ref(&[0; quiche::MAX_CONN_ID_LEN]);\n\nstatic LOG_INIT: Once = Once::new();\n\nfuzz_target!(|data: &[u8]| {\n    quiche_fuzz::reset_rand_for_fuzzing();\n\n    let from: SocketAddr = \"127.0.0.1:1234\".parse().unwrap();\n    let to: SocketAddr = \"127.0.0.1:4321\".parse().unwrap();\n\n    LOG_INIT.call_once(|| env_logger::builder().format_timestamp_nanos().init());\n\n    let packets = quiche_fuzz::PktsData { data };\n\n    let config = CONFIG.get_or_init(|| {\n        let (crt_path, key_path) = quiche_fuzz::get_cert_path();\n\n        let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n        config.load_cert_chain_from_pem_file(&crt_path).unwrap();\n        config.load_priv_key_from_pem_file(&key_path).unwrap();\n        config\n            .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)\n            .unwrap();\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n\n        config.discover_pmtu(true);\n        config.enable_early_data();\n        config.enable_hystart(true);\n\n        Mutex::new(config)\n    });\n\n    let mut conn =\n        quiche::accept(&SCID, None, to, from, &mut config.lock().unwrap())\n            .unwrap();\n\n    let info = quiche::RecvInfo { from, to };\n\n    let mut h3_conn = None;\n    for pkt in packets.iter() {\n        quiche_fuzz::server_process(pkt, &mut conn, &mut h3_conn, info);\n    }\n});\n"
  },
  {
    "path": "fuzz/src/qpack_decode.rs",
    "content": "#![no_main]\n\n#[macro_use]\nextern crate libfuzzer_sys;\n\nuse quiche::h3::NameValue;\n\n// Fuzzer for qpack codec. Checks that decode(encode(hdrs)) == hdrs. To get the\n// initial hdrs, the fuzzer deserializes the input, and skips inputs where\n// deserialization fails.\n//\n// The fuzzer could have been written to instead check encode(decode(input)) ==\n// input. However, that transformation is not guaranteed to be the identify\n// function, as there are multiple ways the same hdr list could be encoded.\nfuzz_target!(|data: &[u8]| {\n    let mut decoder = quiche::h3::qpack::Decoder::new();\n    let mut encoder = quiche::h3::qpack::Encoder::new();\n\n    let hdrs = match decoder.decode(data, u64::MAX) {\n        Err(_) => return,\n        Ok(hdrs) => hdrs,\n    };\n\n    let mut encoded_hdrs = vec![0; data.len() * 10 + 1000];\n    let encoded_size = encoder.encode(&hdrs, &mut encoded_hdrs).unwrap();\n\n    let decoded_hdrs = decoder\n        .decode(&encoded_hdrs[..encoded_size], u64::MAX)\n        .unwrap();\n\n    let mut expected_hdrs = Vec::new();\n\n    // Turn original headers into lower-case as the QPACK decode doesn't do\n    // this.\n    for h in &hdrs {\n        let name = h.name().to_ascii_lowercase();\n\n        expected_hdrs.push(quiche::h3::Header::new(&name, h.value()));\n    }\n\n    assert_eq!(expected_hdrs, decoded_hdrs)\n});\n"
  },
  {
    "path": "h3i/AGENTS.md",
    "content": "# h3i\n\n## OVERVIEW\n\nLow-level HTTP/3 testing client. Sends arbitrary/malformed H3 frames to probe server RFC compliance. Both library (`lib.rs`) and CLI binary (`main.rs`). Used programmatically as test driver in tokio-quiche integration tests.\n\n## STRUCTURE\n\n```\nsrc/\n  lib.rs              # Crate root: quiche re-export, QPACK encoding, stream type constants\n  main.rs             # CLI binary: clap arg parsing, qlog I/O, dispatches to sync/async client\n  config.rs           # Config struct (QUIC transport params, TLS, host/port) + builder\n  frame.rs            # H3iFrame enum (Headers/QuicheH3/ResetStream), EnrichedHeaders, CloseTriggerFrame\n  frame_parser.rs     # Per-stream incremental frame parser (FrameParser, FrameParseResult)\n  actions/\n    h3.rs             # Action enum: SendFrame, SendHeadersFrame, StreamBytes, SendDatagram,\n                      #   OpenUniStream, ResetStream, StopSending, ConnectionClose, Wait, FlushPackets\n  client/\n    mod.rs            # Client trait, execute_action(), parse_streams(), shared logic\n    sync_client.rs    # Blocking mio-based client: connect() -> ConnectionSummary\n    async_client.rs   # tokio-quiche-based client (behind `async` feature)\n    connection_summary.rs  # ConnectionSummary: StreamMap + Stats + PathStats + close details\n  prompts/\n    h3/               # Interactive CLI prompts (inquire crate) for building Actions\n  recordreplay/\n    qlog.rs           # Action <-> qlog event conversion; replay from qlog files\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n|------|------|-------|\n| Define new action type | `actions/h3.rs` | Add variant to `Action` enum |\n| Modify frame parsing | `frame_parser.rs` | `FrameParser::try_parse_frame` |\n| Change connection output | `client/connection_summary.rs` | `ConnectionSummary`, `StreamMap` (640 lines) |\n| Add CLI flags | `main.rs` | `config_from_clap()` uses clap v3 |\n| Library config | `config.rs` | `Config` struct + builder pattern |\n| Custom frame types | `frame.rs` | `H3iFrame` enum wraps quiche frames |\n| qlog record/replay | `recordreplay/qlog.rs` | Bidirectional: Action->qlog and qlog->Action |\n| Use as library | `lib.rs` doc example | `sync_client::connect(config, actions, close_triggers)` |\n\n## NOTES\n\n- **Feature gate**: `async` feature swaps sync mio client for tokio-quiche async client. Also changes quiche re-export path (`quiche` vs `tokio_quiche::quiche`).\n- **quiche `internal` feature**: always enabled -- accesses `quiche::h3::frame::Frame` internals for raw frame construction.\n- **Stream type constants**: `HTTP3_CONTROL_STREAM_TYPE_ID` (0x0), `QPACK_ENCODER_STREAM_TYPE_ID` (0x2), `QPACK_DECODER_STREAM_TYPE_ID` (0x3) in `lib.rs`.\n- **Action execution**: `client/mod.rs::execute_action()` matches on `Action` variants, writes directly to `quiche::Connection` streams -- no H3 connection layer.\n- **ConnectionSummary**: returned from both sync/async `connect()`. Contains per-stream frame maps, QUIC stats, path stats, close reason. Custom `Serialize` impl truncates binary at 16KB.\n- **Literal header encoding**: `encode_header_block_literal()` bypasses Huffman + lowercase normalization for testing malformed headers.\n- **Close triggers**: optional `CloseTriggerFrame` list causes automatic connection close when matching frame received.\n"
  },
  {
    "path": "h3i/Cargo.toml",
    "content": "[package]\nname = \"h3i\"\nversion = \"0.6.0\"\ndescription = \"Low-level HTTP/3 debugging and testing\"\nrepository = { workspace = true }\nauthors = [\n  \"Lucas Pardue <lucas@lucaspardue.com>\",\n  \"Evan Rittenhouse <evanrittenhouse@gmail.com>\",\n]\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\nreadme = \"README.md\"\n\n[features]\nasync = [\"dep:buffer-pool\", \"dep:tokio\", \"dep:tokio-quiche\"]\n\n[dependencies]\nclap = \"3\"\nenv_logger = { workspace = true }\ninquire = \"0.9.1\"\nlog = { workspace = true, features = [\"std\"] }\nmio = { workspace = true, features = [\"net\", \"os-poll\"] }\nmultimap = \"0.10\"\noctets = { workspace = true }\nqlog = { workspace = true }\nquiche = { features = [\"internal\", \"qlog\"], workspace = true }\nring = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\nserde_with = { workspace = true, features = [\"macros\", \"std\"] }\nsmallvec = { workspace = true }\nurl = { workspace = true }\n\n# Dependencies for async client\nbuffer-pool = { optional = true, workspace = true }\ntokio = { version = \"1.44\", features = [\"net\", \"sync\", \"time\"], optional = true }\ntokio-quiche = { features = [\"capture_keylogs\", \"quiche_internal\"], optional = true, workspace = true }\n"
  },
  {
    "path": "h3i/README.md",
    "content": "h3i consists of an interactive command-line tool and library for low-level HTTP/3\ndebugging and testing.\n\nHTTP/3 ([RFC 9114]) is the wire format for HTTP semantics ([RFC 9110]). The RFCs\ncontain a range of requirements about how Request or Response messages are\ngenerated, serialized, sent, received, parsed, and consumed. QUIC ([RFC 900])\nstreams are used for these messages along with other control and QPACK ([RFC\n9204]) header compression instructions.\n\nh3i provides a highly configurable HTTP/3 client that can bend RFC rules in\norder to test the behavior of servers. QUIC streams can be opened, fin'd,\nstopped or reset at any point in time. HTTP/3 frames can be sent on any stream,\nin any order, containing user-controlled content (both legal and illegal).\n\n**Note**: Please note that h3i is _not_ intended to be a production HTTP/3\nclient, and we currently have no plans to make it one. Use it in production at\nyour own risk!\n\n# Command-line Tool\n\nThe command-line tool is intended to be used for ad-hoc interactive\nexploration of HTTP/3 server behavior.\n\nFor example, to interactively test https://cloudflare-quic.com:\n\n```\ncargo run cloudflare-quic.com\n```\n\nThis opens an interactive prompt that walks the user through constructing a\nnumber of `Action`s, such as sending an HTTP/3 HEADERS frame, that are queued\nand then sent to the server in sequence. The available options are\n\n- `headers` - an HTTP/3 HEADERS frame, with mandatory pseudo headers\n- `headers_no_pseudo` - an HTTP/3 HEADER frame, with no mandatory pseudo headers\n- `data` - an HTTP/3 DATA frame\n- `settings` - an HTTP/3 SETTINGS frame\n- `goaway` - an HTTP/3 GOAWAY frame\n- `priority_update` - an HTTP/3 PRIORITY_UPDATE frame\n- `push_promise` - an HTTP/3 PUSH_PROMISE frame\n- `cancel_push` - an HTTP/3 CANCEL_PUSH frame\n- `max_push_id` - an HTTP/3 MAX_PUSH_ID frame\n- `grease` - an HTTP/3 GREASE frame\n- `extension_frame` - an HTTP/3 extension frame\n- `open_uni_stream` - opens an HTTP/3 unidirectional stream with a type\n- `stream_bytes` - send arbitrary data on a stream\n- `reset_stream` - resets a uni or bidi stream\n- `stop_sending` - stops a bidi stream\n- `connection_close` - closes the QUIC connection\n- `flush_packets` - force a QUIC packet flush, to emit any buffered actions\n- `commit` - finish action input, open the connection and execute all actions\n- `wait` - specify a client-side wait, in order to provide some delay between action emits\n- `quit` - quit without opening a connection\n\nTo send two HTTP/3 requests, would require the sequence `headers` and `commit`:\n\n![h3i-demo](h3i-demo.gif)\n\nBy default, the client prints some information about the QUIC connection state,\ntransmitted/received frames, and stream lifecycle. Additional information can be\nprinted using the `RUST_LOG=trace` environment variable, which will emit a\nJSON-serialized [ConnectionSummary](#ConnectionSummary). If the `QLOGDIR`\nenvironment variable is provided, then a qlog file containing the full details\nof QUIC and HTTP/3 will be written.\n\nIn some cases, it can be useful to ignore server name resolution and connect to\na server at a specific IP address, using the indicated SNI. The `--connect-to`\noption can be used to specify the desired IP and port.\n\n## Record and Replay\n\nBy default, h3i records all of the actions to a [qlog] file\n`<timestamp>-qlog.sqlog`. This can be replayed against the same server, or a\ndifferent server, using the `--qlog-input` option. For example:\n\n```\ncargo run cloudflare-quic.com --qlog-input <timestamp>-qlog.sqlog\ncargo run blog.cloudflare.com --qlog-input <timestamp>-qlog.sqlog\n```\n\nNote that `:authority` or `host` headers may need to be re-written to match the target server, depending on the use case.\n\nThe file uses a custom qlog schema that augments the [QUIC schema] and [HTTP/3\nschema].\n\n# Library\n\nh3i is also provided as a library, which allows programmatic control over HTTP/3 client behavior. This is useful for writing test cases.\n\nThe key components of the library are actions, client runner, connection summary, and stream map.\n\nThe library provides both synchronous and asynchronous clients. The latter is built on [tokio-quiche] and can be accessed by using the `async` feature.\n\n## Example\nh3i currently has one example, which can be run with:\n\n```shell\ncargo run --example content_length_mismatch\n```\n\nIt can be useful to observe the packets on the wire that are exchanged when h3i\nis run. Since QUIC is an encrypted transport protocol, tools such as Wireshark\nneed access to the session keys in order to dissect the packets. A common\napproach is to log these keys in response to the `SSLKEYLOGFILE` environment\nvariable and configure Wireshark to use them; [see\nmore](https://wiki.wireshark.org/TLS#using-the-pre-master-secret). For example,\nto log to the file `h3i-example.keys`:\n\n```shell\nSSLKEYLOGFILE=\"h3i-example.keys\" cargo run --example content_length_mismatch\n```\n\n## Actions\n\nActions are small operations such as sending HTTP/3 frames or managing QUIC streams. Each independent use case for h3i requires its own collection of Actions, that h3i iterates over in sequence and executes.\n\nTo emulate the CLI example from above, all that is required is a single action:\n\n\n```rust\n// The set of request headers\nlet headers = headers: vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"https\"),\n            Header::new(b\":authority\", b\"cloudflare-quic.com\"),\n            Header::new(b\":path\", b\"/\"),\n            Header::new(b\"user-agent\", b\"h3i\")\n        ];\n\nlet send_headers_action = send_headers_frame(0, true, headers);\n\nlet actions = vec![send_headers_action];\n```\n\n## Client runner\n\nApplications using the library can invoke the client runner via `sync_client::connect()` or `async_client::connect()`. This requires a set of configuration parameters and an actions vector.\n\n```rust\nlet config = pub struct Config {\n    host_port: \"cloudflare-quic.com\",\n    .. // other fields omitted for brevity\n};\n\n#[cfg(not(feature = \"async\"))]\nlet summary = sync_client::connect(&config, actions);\n#[cfg(feature = \"async\")]\nlet summary = async_client::connect(&config, actions);\n```\n\n## ConnectionSummary\n\nThis is the core \"output\" struct. It \"summarizes\" the connection by providing a view into what was received on each stream (see `StreamMap` below). It also includes statistics about the connection and the QUIC paths that comprises the connection. Lastly, it includes details as to _why_ the connection closed: a timeout, a peer or local error, etc.\n\n### StreamMap\n\nThe `StreamMap` is the second core struct in the library. It is a map of received frames keyed on stream ID, together with a variety of helper methods to check or validate them.\n\nThese frames are of type H3iFrame, which abstract or wrap Quiche's own `quiche::h3::Frame` type to make them easier to work with. For example, the `H3iFrame::Headers` variant contains a headers list without QPACK encoding, making it easy to read or validate. Some frames have no additional features; these are simply wrapped in the `H3iFrame::QuicheH3` variant.\n\n# Inspiration\n\nh3i has been inspired by several other tools and techniques used across the HTTP and QUIC ecosystem:\n\n* [h2i](https://pkg.go.dev/golang.org/x/net/http2/h2i) - an interactive console debugger for HTTP/2. Provided as part of the Go HTTP/2 implementation\n\n* [h2spec](https://github.com/summerwind/h2spec) - a conformance testing tool for\nHTTP/2. Written and maintained by Moto Ishizawa.\n\n* [h3spec](https://github.com/kazu-yamamoto/h3spec) - a conformance testing tool\nfor QUIC and HTTP/3. Written and maintained by Kazu Yamamato.\n\n[RFC 9000]: https://www.rfc-editor.org/rfc/rfc9000.html\n[RFC 9110]: https://www.rfc-editor.org/rfc/rfc9110.html\n[RFC 9114]: https://www.rfc-editor.org/rfc/rfc9114.html\n[RFC 9204]: https://www.rfc-editor.org/rfc/rfc9204.html\n[qlog]: https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-main-schema/\n[QUIC schema]: https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-quic-events\n[HTTP/3 schema]: https://datatracker.ietf.org/doc/draft-ietf-quic-qlog-h3-events\n[tokio-quiche]: https://docs.rs/tokio-quiche\n"
  },
  {
    "path": "h3i/examples/content_length_mismatch.rs",
    "content": "use h3i::actions::h3::Action;\nuse h3i::actions::h3::StreamEvent;\nuse h3i::actions::h3::StreamEventType;\nuse h3i::actions::h3::WaitType;\nuse h3i::client::sync_client;\nuse h3i::config::Config;\nuse quiche::h3::frame::Frame;\nuse quiche::h3::Header;\nuse quiche::h3::NameValue;\n\n/// The QUIC stream to send the frames on. See\n/// https://datatracker.ietf.org/doc/html/rfc9000#name-streams and\n/// https://datatracker.ietf.org/doc/html/rfc9114#request-streams for more.\nconst STREAM_ID: u64 = 0;\n\n/// Send a request with a Content-Length header that specifies 5 bytes, but a\n/// body that is only 4 bytes long. This verifies https://datatracker.ietf.org/doc/html/rfc9114#section-4.1.2-3 for\n/// cloudflare-quic.com.\nfn main() {\n    let config = Config::new()\n        .with_host_port(\"cloudflare-quic.com\".to_string())\n        .with_idle_timeout(2000)\n        .build()\n        .unwrap();\n\n    let headers = vec![\n        Header::new(b\":method\", b\"POST\"),\n        Header::new(b\":scheme\", b\"https\"),\n        Header::new(b\":authority\", b\"cloudflare-quic.com\"),\n        Header::new(b\":path\", b\"/\"),\n        // We say that we're going to send a body with 5 bytes...\n        Header::new(b\"content-length\", b\"5\"),\n    ];\n\n    let header_block = encode_header_block(&headers).unwrap();\n\n    let actions = vec![\n        Action::SendHeadersFrame {\n            stream_id: STREAM_ID,\n            fin_stream: false,\n            headers,\n            literal_headers: false,\n            frame: Frame::Headers { header_block },\n            expected_result: Default::default(),\n        },\n        Action::SendFrame {\n            stream_id: STREAM_ID,\n            fin_stream: true,\n            frame: Frame::Data {\n                // ...but, in actuality, we only send 4 bytes. This should yield a\n                // 400 Bad Request response from an RFC-compliant\n                // server: https://datatracker.ietf.org/doc/html/rfc9114#section-4.1.2-3\n                payload: b\"test\".to_vec(),\n            },\n            expected_result: Default::default(),\n        },\n        Action::Wait {\n            wait_type: WaitType::StreamEvent(StreamEvent {\n                stream_id: STREAM_ID,\n                event_type: StreamEventType::Headers,\n            }),\n        },\n        Action::ConnectionClose {\n            error: quiche::ConnectionError {\n                is_app: true,\n                error_code: quiche::h3::WireErrorCode::NoError as u64,\n                reason: vec![],\n            },\n        },\n    ];\n\n    // This example doesn't use close trigger frames, since we manually close the\n    // connection upon receiving a HEADERS frame on stream 0.\n    let close_trigger_frames = None;\n\n    let summary = sync_client::connect(config, actions, close_trigger_frames)\n        .expect(\"connection failed\");\n\n    println!(\n        \"=== received connection summary! ===\\n\\n{}\",\n        serde_json::to_string_pretty(&summary).unwrap_or_else(|e| e.to_string())\n    );\n}\n\n// SendHeadersFrame requires a QPACK-encoded header block. h3i provides a\n// `send_headers_frame` helper function to abstract this, but for clarity, we do\n// it here.\nfn encode_header_block(\n    headers: &[quiche::h3::Header],\n) -> std::result::Result<Vec<u8>, String> {\n    let mut encoder = quiche::h3::qpack::Encoder::new();\n\n    let headers_len = headers\n        .iter()\n        .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);\n\n    let mut header_block = vec![0; headers_len];\n    let len = encoder\n        .encode(headers, &mut header_block)\n        .map_err(|_| \"Internal Error\")?;\n\n    header_block.truncate(len);\n\n    Ok(header_block)\n}\n"
  },
  {
    "path": "h3i/examples/stream_limit.rs",
    "content": "use h3i::actions::h3::Action;\nuse h3i::actions::h3::ExpectedStreamSendResult;\nuse h3i::client::sync_client;\nuse h3i::config::Config;\nuse h3i::HTTP3_CONTROL_STREAM_TYPE_ID;\nuse h3i::QPACK_DECODER_STREAM_TYPE_ID;\nuse h3i::QPACK_ENCODER_STREAM_TYPE_ID;\n\n/// Test that opening too many unidirectional streams correctly triggers a\n/// StreamLimit error. Most servers advertise a max_streams_uni of 3, so\n/// attempting to open a 4th stream should fail.\nfn main() {\n    let config = Config::new()\n        .with_host_port(\"cloudflare-quic.com\".to_string())\n        .with_idle_timeout(2000)\n        .build()\n        .unwrap();\n\n    // Client-initiated unidirectional stream IDs: 2, 6, 10, 14, ...\n    // With max_streams_uni=3 from server, streams 2, 6, 10 should succeed,\n    // but stream 14 should fail with StreamLimit.\n    let actions = vec![\n        Action::OpenUniStream {\n            stream_id: 2,\n            fin_stream: false,\n            stream_type: HTTP3_CONTROL_STREAM_TYPE_ID,\n            expected_result: ExpectedStreamSendResult::OkExact(1),\n        },\n        Action::OpenUniStream {\n            stream_id: 6,\n            fin_stream: false,\n            stream_type: QPACK_ENCODER_STREAM_TYPE_ID,\n            expected_result: ExpectedStreamSendResult::OkExact(1),\n        },\n        Action::OpenUniStream {\n            stream_id: 10,\n            fin_stream: false,\n            stream_type: QPACK_DECODER_STREAM_TYPE_ID,\n            expected_result: ExpectedStreamSendResult::OkExact(1),\n        },\n        // This 4th stream should exceed the server's max_streams_uni limit\n        Action::OpenUniStream {\n            stream_id: 14,\n            fin_stream: false,\n            stream_type: 0x10,\n            expected_result: ExpectedStreamSendResult::Error(\n                h3i::quiche::Error::StreamLimit,\n            ),\n        },\n        Action::ConnectionClose {\n            error: h3i::quiche::ConnectionError {\n                is_app: true,\n                error_code: h3i::quiche::h3::WireErrorCode::NoError as u64,\n                reason: vec![],\n            },\n        },\n    ];\n\n    let close_trigger_frames = None;\n\n    let summary = sync_client::connect(config, actions, close_trigger_frames)\n        .expect(\"connection failed\");\n\n    println!(\n        \"=== Stream limit test completed! ===\\n\\n{}\",\n        serde_json::to_string_pretty(&summary).unwrap_or_else(|e| e.to_string())\n    );\n}\n"
  },
  {
    "path": "h3i/src/actions/h3.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Actions specific to HTTP/3 and QUIC\n//!\n//! Actions are small operations such as sending HTTP/3 frames or managing QUIC\n//! streams. Each independent use case for h3i requires its own collection of\n//! Actions, that h3i iterates over in sequence and executes.\n\nuse std::collections::HashMap;\nuse std::time::Duration;\n\nuse crate::quiche;\nuse quiche::h3::frame::Frame;\nuse quiche::h3::Header;\nuse quiche::ConnectionError;\nuse serde::Deserialize;\nuse serde::Serialize;\nuse serde_with::serde_as;\n\nuse crate::encode_header_block;\nuse crate::encode_header_block_literal;\n\n/// Expected result from a stream_send operation.\n#[derive(Clone, Debug, Default, PartialEq, Eq)]\npub enum ExpectedStreamSendResult {\n    /// Expect success with any number of bytes written.\n    #[default]\n    Ok,\n    /// Expect success with exactly the specified number of bytes written.\n    OkExact(usize),\n    /// Expect the operation to fail with the specified error.\n    Error(quiche::Error),\n}\n\n/// An action which the HTTP/3 client should take.\n///\n/// The client iterates over a vector of said actions, executing each one\n/// sequentially. Note that packets will be flushed when said iteration has\n/// completed, regardless of if an [`Action::FlushPackets`] was the terminal\n/// action.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Action {\n    /// Send a [quiche::h3::frame::Frame] over a stream.\n    SendFrame {\n        stream_id: u64,\n        fin_stream: bool,\n        frame: Frame,\n        expected_result: ExpectedStreamSendResult,\n    },\n\n    /// Send a HEADERS frame over a stream.\n    SendHeadersFrame {\n        stream_id: u64,\n        fin_stream: bool,\n        literal_headers: bool,\n        headers: Vec<Header>,\n        frame: Frame,\n        expected_result: ExpectedStreamSendResult,\n    },\n\n    /// Send arbitrary bytes over a stream.\n    StreamBytes {\n        stream_id: u64,\n        fin_stream: bool,\n        bytes: Vec<u8>,\n        expected_result: ExpectedStreamSendResult,\n    },\n\n    /// Send a DATAGRAM frame.\n    SendDatagram {\n        payload: Vec<u8>,\n    },\n\n    /// Open a new unidirectional stream.\n    OpenUniStream {\n        stream_id: u64,\n        fin_stream: bool,\n        stream_type: u64,\n        expected_result: ExpectedStreamSendResult,\n    },\n\n    /// Send a RESET_STREAM frame with the given error code.\n    ResetStream {\n        stream_id: u64,\n        error_code: u64,\n    },\n\n    /// Send a STOP_SENDING frame with the given error code.\n    StopSending {\n        stream_id: u64,\n        error_code: u64,\n    },\n\n    /// Send a CONNECTION_CLOSE frame with the given [`ConnectionError`].\n    ConnectionClose {\n        error: ConnectionError,\n    },\n\n    FlushPackets,\n\n    /// Wait for an event. See [WaitType] for the events.\n    Wait {\n        wait_type: WaitType,\n    },\n}\n\n/// Configure the wait behavior for a connection.\n#[serde_as]\n#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]\npub enum WaitType {\n    /// Wait for a time before firing the next action\n    #[serde(rename = \"duration\")]\n    WaitDuration(\n        #[serde_as(as = \"serde_with::DurationMilliSecondsWithFrac<f64>\")]\n        Duration,\n    ),\n    /// Wait for some form of a response before firing the next action. This can\n    /// be superseded in several cases:\n    /// 1. The peer resets the specified stream.\n    /// 2. The peer sends a `fin` over the specified stream\n    StreamEvent(StreamEvent),\n\n    /// Wait until the peer has updated MAX_STREAMS to allow creation\n    /// of the required additional streams.\n    ///\n    /// Typically requires processing of a MAX_STREAMS frame sent by\n    /// the peer.  The peer may decide to send MAX_STREAMS updates as\n    /// the number of active streams changes due to stream termination\n    /// or stream creation. Typical goals being:\n    /// - Limit the number of active streams to the configured limit.\n    /// - Ensure that available quota is non-zero when number of active streams\n    ///   is below the configured limit.\n    /// - Minimize the number of MAX_STREAM updates sent.\n    CanOpenNumStreams(RequiredStreamsQuota),\n}\n\nimpl From<WaitType> for Action {\n    fn from(value: WaitType) -> Self {\n        Self::Wait { wait_type: value }\n    }\n}\n\n/// A response event, received over a stream, which will terminate the wait\n/// period.\n///\n/// See [StreamEventType] for the types of events.\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[serde(rename = \"snake_case\")]\npub struct StreamEvent {\n    pub stream_id: u64,\n    #[serde(rename = \"type\")]\n    pub event_type: StreamEventType,\n}\n\n/// A check for stream creation quota which will terminate the wait period.\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[serde(rename = \"snake_case\")]\npub struct RequiredStreamsQuota {\n    /// Required stream quota\n    pub num: u64,\n\n    /// True for bidirectional streams, false for unidirectional streams\n    pub bidi: bool,\n}\n\n/// Response that can terminate a wait period.\n#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum StreamEventType {\n    /// A HEADERS frame was received.\n    Headers,\n    /// A DATA frame was received.\n    Data,\n    /// The stream was somehow finished, either by a RESET_STREAM frame or via\n    /// the `fin` bit being set.\n    Finished,\n}\n\n#[derive(Debug, Default)]\npub(crate) struct WaitingFor {\n    stream_events: HashMap<u64, Vec<StreamEvent>>,\n    required_stream_quota: Option<RequiredStreamsQuota>,\n}\n\nimpl WaitingFor {\n    pub(crate) fn is_empty(&self) -> bool {\n        self.stream_events.values().all(|v| v.is_empty()) &&\n            self.required_stream_quota.is_none()\n    }\n\n    pub(crate) fn add_wait(&mut self, stream_event: &StreamEvent) {\n        self.stream_events\n            .entry(stream_event.stream_id)\n            .or_default()\n            .push(*stream_event);\n    }\n\n    pub(crate) fn set_required_stream_quota(\n        &mut self, required: RequiredStreamsQuota,\n    ) {\n        self.required_stream_quota = Some(required);\n    }\n\n    /// Check and clear the [`WaitType::CanOpenNumStreams`] condition if\n    /// `conn` now reports enough available streams.\n    pub(crate) fn check_can_open_num_streams(\n        &mut self, conn: &quiche::Connection,\n    ) {\n        if let Some(streams_required) = self.required_stream_quota {\n            let available_streams = if streams_required.bidi {\n                conn.peer_streams_left_bidi()\n            } else {\n                conn.peer_streams_left_uni()\n            };\n            if available_streams >= streams_required.num {\n                log::info!(\n                    \"required_stream_quota condition met \\\n                     (needed={}, available={}, bidi={})\",\n                    streams_required.num,\n                    available_streams,\n                    streams_required.bidi,\n                );\n                self.required_stream_quota = None;\n            }\n        }\n    }\n\n    pub(crate) fn remove_wait(&mut self, stream_event: StreamEvent) {\n        if let Some(waits) = self.stream_events.get_mut(&stream_event.stream_id) {\n            let old_len = waits.len();\n            waits.retain(|wait| wait != &stream_event);\n            let new_len = waits.len();\n\n            if old_len != new_len {\n                log::info!(\"No longer waiting for {stream_event:?}\");\n            }\n        }\n    }\n\n    pub(crate) fn clear_waits_on_stream(&mut self, stream_id: u64) {\n        if let Some(waits) = self.stream_events.get_mut(&stream_id) {\n            if !waits.is_empty() {\n                log::info!(\"Clearing all waits for stream {stream_id}\");\n                waits.clear();\n            }\n        }\n    }\n}\n\n/// Convenience to convert between header-related data and a\n/// [Action::SendHeadersFrame].\npub fn send_headers_frame(\n    stream_id: u64, fin_stream: bool, headers: Vec<Header>,\n) -> Action {\n    let header_block = encode_header_block(&headers).unwrap();\n\n    Action::SendHeadersFrame {\n        stream_id,\n        fin_stream,\n        headers,\n        literal_headers: false,\n        frame: Frame::Headers { header_block },\n        expected_result: ExpectedStreamSendResult::Ok,\n    }\n}\n\n/// Convenience to convert between header-related data and a\n/// [Action::SendHeadersFrame].\npub fn send_headers_frame_with_expected_result(\n    stream_id: u64, fin_stream: bool, headers: Vec<Header>,\n    expected_result: ExpectedStreamSendResult,\n) -> Action {\n    let header_block = encode_header_block(&headers).unwrap();\n\n    Action::SendHeadersFrame {\n        stream_id,\n        fin_stream,\n        headers,\n        literal_headers: false,\n        frame: Frame::Headers { header_block },\n        expected_result,\n    }\n}\n\n/// Convenience to convert between header-related data and a\n/// [Action::SendHeadersFrame]. Unlike [`send_headers_frame`],\n/// this version encodes the headers literally as they are provided,\n/// not converting the header names to lower-case.\npub fn send_headers_frame_literal(\n    stream_id: u64, fin_stream: bool, headers: Vec<Header>,\n) -> Action {\n    let header_block = encode_header_block_literal(&headers).unwrap();\n\n    Action::SendHeadersFrame {\n        stream_id,\n        fin_stream,\n        headers,\n        literal_headers: true,\n        frame: Frame::Headers { header_block },\n        expected_result: ExpectedStreamSendResult::Ok,\n    }\n}\n\n/// Convenience to convert between header-related data and a\n/// [Action::SendHeadersFrame]. Unlike [`send_headers_frame`],\n/// this version encodes the headers literally as they are provided,\n/// not converting the header names to lower-case.\npub fn send_headers_frame_literal_with_expected_result(\n    stream_id: u64, fin_stream: bool, headers: Vec<Header>,\n    expected_result: ExpectedStreamSendResult,\n) -> Action {\n    let header_block = encode_header_block_literal(&headers).unwrap();\n\n    Action::SendHeadersFrame {\n        stream_id,\n        fin_stream,\n        headers,\n        literal_headers: true,\n        frame: Frame::Headers { header_block },\n        expected_result,\n    }\n}\n"
  },
  {
    "path": "h3i/src/actions/mod.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Actions are small operations such as sending HTTP/3 frames or managing QUIC\n//! streams. Each independent use case for h3i requires its own collection of\n//! Actions, that h3i iterates over in sequence and executes.\npub mod h3;\n"
  },
  {
    "path": "h3i/src/client/async_client.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Responsible for creating a [tokio_quiche::quic::QuicheConnection] and\n//! yielding I/O to tokio-quiche.\n\nuse buffer_pool::ConsumeBuffer;\nuse buffer_pool::Pooled;\nuse log;\nuse quiche::PathStats;\nuse quiche::Stats;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::task::Context;\nuse std::task::Poll;\nuse std::time::Duration;\nuse tokio::select;\nuse tokio::sync::mpsc;\nuse tokio::sync::oneshot;\nuse tokio::time::sleep;\nuse tokio::time::sleep_until;\nuse tokio::time::Instant;\nuse tokio_quiche::buf_factory::BufFactory;\nuse tokio_quiche::metrics::Metrics;\nuse tokio_quiche::quic::HandshakeInfo;\nuse tokio_quiche::quic::QuicheConnection;\nuse tokio_quiche::settings::Hooks;\nuse tokio_quiche::settings::QuicSettings;\nuse tokio_quiche::socket::Socket;\nuse tokio_quiche::ApplicationOverQuic;\nuse tokio_quiche::ConnectionParams;\nuse tokio_quiche::QuicResult;\n\nuse crate::actions::h3::Action;\nuse crate::actions::h3::WaitType;\nuse crate::actions::h3::WaitingFor;\nuse crate::client::execute_action;\nuse crate::client::parse_args;\nuse crate::client::parse_streams;\nuse crate::client::ClientError;\nuse crate::client::CloseTriggerFrames;\nuse crate::client::ConnectionSummary;\nuse crate::client::ParsedArgs;\nuse crate::client::StreamMap;\nuse crate::client::MAX_DATAGRAM_SIZE;\nuse crate::config::Config as H3iConfig;\nuse crate::frame::H3iFrame;\nuse crate::quiche;\n\nuse super::Client;\nuse super::ConnectionCloseDetails;\nuse super::StreamParserMap;\n\n/// Connect to the socket.\npub async fn connect(\n    args: &H3iConfig, frame_actions: Vec<Action>,\n    close_trigger_frames: Option<CloseTriggerFrames>,\n) -> std::result::Result<BuildingConnectionSummary, ClientError> {\n    let quic_settings = create_config(args);\n    let mut connection_params =\n        ConnectionParams::new_client(quic_settings, None, Hooks::default());\n\n    connection_params.session = args.session.clone();\n\n    let ParsedArgs {\n        connect_url,\n        bind_addr,\n        peer_addr,\n    } = parse_args(args);\n\n    let socket = tokio::net::UdpSocket::bind(bind_addr).await.unwrap();\n    socket.connect(peer_addr).await.unwrap();\n\n    log::info!(\n        \"connecting to {:} from {:}\",\n        peer_addr,\n        socket.local_addr().unwrap()\n    );\n\n    let (h3i, conn_summary_fut) =\n        H3iDriver::new(frame_actions, close_trigger_frames);\n    match tokio_quiche::quic::connect_with_config(\n        Socket::try_from(socket).unwrap(),\n        connect_url,\n        &connection_params,\n        h3i,\n    )\n    .await\n    {\n        Ok(_) => Ok(conn_summary_fut),\n        Err(_) => Err(ClientError::HandshakeFail),\n    }\n}\n\nfn create_config(args: &H3iConfig) -> QuicSettings {\n    let mut quic_settings = QuicSettings::default();\n\n    quic_settings.verify_peer = args.verify_peer;\n    quic_settings.max_idle_timeout =\n        Some(Duration::from_millis(args.idle_timeout));\n    quic_settings.max_recv_udp_payload_size = MAX_DATAGRAM_SIZE;\n    quic_settings.max_send_udp_payload_size = MAX_DATAGRAM_SIZE;\n    quic_settings.initial_max_data = 10_000_000;\n    quic_settings.initial_max_stream_data_bidi_local =\n        args.max_stream_data_bidi_local;\n    quic_settings.initial_max_stream_data_bidi_remote =\n        args.max_stream_data_bidi_remote;\n    quic_settings.initial_max_stream_data_uni = args.max_stream_data_uni;\n    quic_settings.initial_max_streams_bidi = args.max_streams_bidi;\n    quic_settings.initial_max_streams_uni = args.max_streams_uni;\n    quic_settings.disable_active_migration = true;\n    quic_settings.active_connection_id_limit = 0;\n    quic_settings.max_connection_window = args.max_window;\n    quic_settings.max_stream_window = args.max_stream_window;\n    quic_settings.enable_send_streams_blocked = true;\n    quic_settings.grease = false;\n\n    quic_settings.capture_quiche_logs = true;\n    quic_settings.keylog_file = std::env::var_os(\"SSLKEYLOGFILE\")\n        .and_then(|os_str| os_str.into_string().ok());\n\n    quic_settings.enable_dgram = args.enable_dgram;\n    quic_settings.dgram_recv_max_queue_len = args.dgram_recv_queue_len;\n    quic_settings.dgram_send_max_queue_len = args.dgram_send_queue_len;\n\n    quic_settings\n}\n\n/// The [`Future`] used to build a [`ConnectionSummary`].\n///\n/// At a high level, [`H3iDriver`] will interact with the UDP socket directly,\n/// sending and receiving data as necessary. As new data is received, it will\n/// send [`ConnectionRecord`]s to this struct, which uses these records to\n/// construct the [`ConnectionSummary`].\n#[must_use = \"must await to get a ConnectionSummary\"]\npub struct BuildingConnectionSummary {\n    rx: mpsc::UnboundedReceiver<ConnectionRecord>,\n    summary: Option<ConnectionSummary>,\n    seen_all_close_trigger_frames: Option<oneshot::Sender<()>>,\n}\n\nimpl BuildingConnectionSummary {\n    fn new(\n        rx: mpsc::UnboundedReceiver<ConnectionRecord>,\n        close_trigger_frames: Option<CloseTriggerFrames>,\n        trigger_frame_tx: oneshot::Sender<()>,\n    ) -> Self {\n        let summary = ConnectionSummary {\n            stream_map: StreamMap::new(close_trigger_frames),\n            ..Default::default()\n        };\n\n        Self {\n            rx,\n            summary: Some(summary),\n            seen_all_close_trigger_frames: Some(trigger_frame_tx),\n        }\n    }\n}\n\nimpl Future for BuildingConnectionSummary {\n    type Output = ConnectionSummary;\n\n    fn poll(\n        mut self: Pin<&mut Self>, cx: &mut Context<'_>,\n    ) -> Poll<Self::Output> {\n        while let Poll::Ready(Some(record)) = self.rx.poll_recv(cx) {\n            // Grab all records received from the current event loop iteration and\n            // insert them into the in-progress summary\n            let summary = self.summary.as_mut().expect(\"summary already taken\");\n\n            match record {\n                ConnectionRecord::StreamedFrame { stream_id, frame } => {\n                    let stream_map = &mut summary.stream_map;\n                    stream_map.insert(stream_id, frame);\n\n                    if stream_map.all_close_trigger_frames_seen() {\n                        // Signal the H3iDriver task to close the connection.\n                        if let Some(expected_tx) =\n                            self.seen_all_close_trigger_frames.take()\n                        {\n                            let _ = expected_tx.send(());\n                        }\n                    }\n                },\n                ConnectionRecord::ConnectionStats(s) => summary.stats = Some(s),\n                ConnectionRecord::PathStats(ps) => summary.path_stats = ps,\n                ConnectionRecord::Close(d) => summary.conn_close_details = d,\n            };\n        }\n\n        if self.rx.is_closed() {\n            // The sender drops when the Tokio-Quiche IOW finishes, so the\n            // connection is done and we're safe to yield the summary.\n            let summary = self.summary.take().expect(\"summary already taken\");\n            Poll::Ready(summary)\n        } else {\n            Poll::Pending\n        }\n    }\n}\n\npub struct H3iDriver {\n    buffer: Pooled<ConsumeBuffer>,\n    actions: Vec<Action>,\n    actions_executed: usize,\n    next_fire_time: Instant,\n    waiting_for_responses: WaitingFor,\n    record_tx: mpsc::UnboundedSender<ConnectionRecord>,\n    stream_parsers: StreamParserMap,\n    close_trigger_seen_rx: oneshot::Receiver<()>,\n}\n\nimpl H3iDriver {\n    fn new(\n        actions: Vec<Action>, close_trigger_frames: Option<CloseTriggerFrames>,\n    ) -> (Self, BuildingConnectionSummary) {\n        let (record_tx, record_rx) = mpsc::unbounded_channel();\n        let (close_trigger_seen_tx, close_trigger_seen_rx) = oneshot::channel();\n        let fut = BuildingConnectionSummary::new(\n            record_rx,\n            close_trigger_frames,\n            close_trigger_seen_tx,\n        );\n\n        (\n            Self {\n                buffer: BufFactory::get_max_buf(),\n                actions,\n                actions_executed: 0,\n                next_fire_time: Instant::now(),\n                waiting_for_responses: WaitingFor::default(),\n                record_tx,\n                stream_parsers: StreamParserMap::default(),\n                close_trigger_seen_rx,\n            },\n            fut,\n        )\n    }\n\n    /// If the next action should fire.\n    fn should_fire(&self) -> bool {\n        Instant::now() >= self.next_fire_time\n    }\n\n    /// Insert all waits into the waiting set.\n    fn register_waits(&mut self) {\n        while self.actions_executed < self.actions.len() {\n            if let Action::Wait { wait_type } =\n                &self.actions[self.actions_executed]\n            {\n                self.actions_executed += 1;\n\n                match wait_type {\n                    WaitType::WaitDuration(duration) => {\n                        self.next_fire_time = Instant::now() + *duration;\n\n                        log::debug!(\n                            \"h3i: waiting for responses: {:?}\",\n                            self.waiting_for_responses\n                        );\n                    },\n                    WaitType::StreamEvent(event) => {\n                        self.waiting_for_responses.add_wait(event);\n                    },\n                    WaitType::CanOpenNumStreams(required_streams) => {\n                        log::info!(\n                            \"h3i: waiting for peer_streams_left_bidi >= {required_streams:?}\"\n                        );\n                        self.waiting_for_responses\n                            .set_required_stream_quota(*required_streams);\n                    },\n                }\n            } else {\n                break;\n            }\n        }\n    }\n}\n\nimpl Client for H3iDriver {\n    fn stream_parsers_mut(&mut self) -> &mut StreamParserMap {\n        &mut self.stream_parsers\n    }\n\n    fn handle_response_frame(\n        &mut self, stream_id: u64, frame: crate::frame::H3iFrame,\n    ) {\n        self.record_tx\n            .send(ConnectionRecord::StreamedFrame { stream_id, frame })\n            .expect(\"H3iDriver task dropped\")\n    }\n}\n\nimpl ApplicationOverQuic for H3iDriver {\n    fn on_conn_established(\n        &mut self, _qconn: &mut QuicheConnection, _handshake_info: &HandshakeInfo,\n    ) -> QuicResult<()> {\n        log::info!(\"h3i: HTTP/3 connection established\");\n        Ok(())\n    }\n\n    fn should_act(&self) -> bool {\n        // Even if the connection wasn't established, we should still send\n        // terminal records to the summary\n        true\n    }\n\n    fn process_reads(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()> {\n        log::trace!(\"h3i: process_reads\");\n\n        // This is executed in process_reads so that work_loop can clear any waits\n        // on the current event loop iteration - if it was in process_writes, we\n        // could potentially miss waits and hang the client.\n        self.register_waits();\n\n        let stream_events = parse_streams(qconn, self);\n        for event in stream_events {\n            self.waiting_for_responses.remove_wait(event);\n        }\n\n        self.waiting_for_responses.check_can_open_num_streams(qconn);\n\n        Ok(())\n    }\n\n    fn process_writes(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()> {\n        log::trace!(\"h3i: process_writes\");\n\n        if !self.waiting_for_responses.is_empty() {\n            log::debug!(\n                \"awaiting responses on streams {:?}, skipping further action\",\n                self.waiting_for_responses\n            );\n\n            return Ok(());\n        }\n\n        // Re-create the iterator so we can mutably borrow the stream parser map\n        let iter = self.actions.clone().into_iter().skip(self.actions_executed);\n\n        for action in iter {\n            match action {\n                Action::SendFrame { .. } |\n                Action::StreamBytes { .. } |\n                Action::SendDatagram { .. } |\n                Action::ResetStream { .. } |\n                Action::StopSending { .. } |\n                Action::OpenUniStream { .. } |\n                Action::ConnectionClose { .. } |\n                Action::SendHeadersFrame { .. } => {\n                    if self.should_fire() {\n                        // Reset the fire time such that the next action will\n                        // still fire.\n                        self.next_fire_time = Instant::now();\n\n                        execute_action(&action, qconn, self.stream_parsers_mut());\n                        self.actions_executed += 1;\n                    } else {\n                        break;\n                    }\n                },\n                Action::Wait { .. } => {\n                    // Break out of the write phase if we see a wait, since waits\n                    // have to be registered in the read\n                    // phase. The actions_executed pointer will be\n                    // incremented there as well\n                    break;\n                },\n                Action::FlushPackets => {\n                    self.actions_executed += 1;\n                    break;\n                },\n            }\n        }\n\n        Ok(())\n    }\n\n    async fn wait_for_data(\n        &mut self, qconn: &mut QuicheConnection,\n    ) -> QuicResult<()> {\n        log::trace!(\"h3i: wait_for_data\");\n\n        let sleep_fut = if !self.should_fire() {\n            sleep_until(self.next_fire_time)\n        } else {\n            // If we have nothing to send, allow the IOW to resolve wait_for_data\n            // on its own (whether via Quiche timer or incoming data).\n            sleep(Duration::MAX)\n        };\n\n        select! {\n            rx = &mut self.close_trigger_seen_rx, if !self.close_trigger_seen_rx.is_terminated() => {\n                // NOTE: wait_for_data can be called again after all close triggers have been seen,\n                // depending on how long it takes quiche to mark the connection as closed.\n                // Therefore we can't re-poll the receiver or we'd panic.\n                if rx.is_ok() {\n                    // TODO: customizable close trigger frames\n                    let _ = qconn.close(true, quiche::h3::WireErrorCode::NoError as u64, b\"saw all expected frames\");\n                }\n            }\n            _ = sleep_fut => {}\n        }\n\n        Ok(())\n    }\n\n    fn buffer(&mut self) -> &mut [u8] {\n        &mut self.buffer\n    }\n\n    fn on_conn_close<M: Metrics>(\n        &mut self, qconn: &mut QuicheConnection, _metrics: &M,\n        _work_loop_result: &QuicResult<()>,\n    ) {\n        let _ = self\n            .record_tx\n            .send(ConnectionRecord::Close(ConnectionCloseDetails::new(qconn)));\n\n        let _ = self\n            .record_tx\n            .send(ConnectionRecord::ConnectionStats(qconn.stats()));\n\n        let conn_path_stats = qconn.path_stats().collect::<Vec<PathStats>>();\n        let _ = self\n            .record_tx\n            .send(ConnectionRecord::PathStats(conn_path_stats));\n    }\n}\n\npub enum ConnectionRecord {\n    StreamedFrame { stream_id: u64, frame: H3iFrame },\n    Close(ConnectionCloseDetails),\n    PathStats(Vec<PathStats>),\n    ConnectionStats(Stats),\n}\n"
  },
  {
    "path": "h3i/src/client/connection_summary.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Summarizes events that occurred during a connection.\n\nuse quiche::ConnectionError;\nuse quiche::PathStats;\nuse quiche::Stats;\nuse serde::ser::SerializeStruct;\nuse serde::ser::Serializer;\nuse serde::Serialize;\nuse std::cmp;\nuse std::collections::HashMap;\nuse std::iter::FromIterator;\n\nuse crate::frame::CloseTriggerFrame;\nuse crate::frame::EnrichedHeaders;\nuse crate::frame::H3iFrame;\nuse crate::quiche;\n\n/// Maximum length of any serialized element's unstructured data such as reason\n/// phrase.\npub const MAX_SERIALIZED_BUFFER_LEN: usize = 16384;\n\n/// A summary of all frames received on a connection. There are some extra\n/// fields included to provide additional context into the connection's\n/// behavior.\n///\n/// ConnectionSummary implements [Serialize]. HTTP/3 frames that contain binary\n/// payload are serialized using the qlog\n/// [hexstring](https://www.ietf.org/archive/id/draft-ietf-quic-qlog-main-schema-10.html#section-1.2)\n/// format - \"an even-length lowercase string of hexadecimally encoded bytes\n/// examples: 82dc, 027339, 4cdbfd9bf0\"\n#[derive(Default, Debug)]\npub struct ConnectionSummary {\n    pub stream_map: StreamMap,\n    /// L4 statistics received from the connection.\n    pub stats: Option<Stats>,\n    /// Statistics about all paths of the connection.\n    pub path_stats: Vec<PathStats>,\n    /// Details about why the connection closed.\n    pub conn_close_details: ConnectionCloseDetails,\n}\n\nimpl Serialize for ConnectionSummary {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut state = s.serialize_struct(\"path_stats\", 4)?;\n        state.serialize_field(\"stream_map\", &self.stream_map)?;\n        state.serialize_field(\n            \"stats\",\n            &self.stats.as_ref().map(SerializableStats),\n        )?;\n        let p: Vec<SerializablePathStats> =\n            self.path_stats.iter().map(SerializablePathStats).collect();\n        state.serialize_field(\"path_stats\", &p)?;\n        state.serialize_field(\"error\", &self.conn_close_details)?;\n        state.serialize_field(\n            \"missed_close_trigger_frames\",\n            &self.stream_map.missing_close_trigger_frames(),\n        )?;\n        state.end()\n    }\n}\n\n/// A read-only aggregation of frames received over a connection, mapped to the\n/// stream ID over which they were received.\n///\n/// [`StreamMap`] also contains the [`CloseTriggerFrames`] for the connection so\n/// that its state can be updated as new frames are received.\n#[derive(Clone, Debug, Default, Serialize)]\npub struct StreamMap {\n    stream_frame_map: HashMap<u64, Vec<H3iFrame>>,\n    close_trigger_frames: Option<CloseTriggerFrames>,\n}\n\nimpl<T> From<T> for StreamMap\nwhere\n    T: IntoIterator<Item = (u64, Vec<H3iFrame>)>,\n{\n    fn from(value: T) -> Self {\n        let stream_frame_map = HashMap::from_iter(value);\n\n        Self {\n            stream_frame_map,\n            close_trigger_frames: None,\n        }\n    }\n}\n\nimpl StreamMap {\n    /// Flatten all received frames into a single vector. The ordering is\n    /// non-deterministic.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use h3i::client::connection_summary::StreamMap;\n    /// use h3i::frame::EnrichedHeaders;\n    /// use h3i::frame::H3iFrame;\n    /// use quiche::h3::Header;\n    /// use std::iter::FromIterator;\n    ///\n    /// let h = Header::new(b\"hello\", b\"world\");\n    /// let headers = H3iFrame::Headers(EnrichedHeaders::from(vec![h]));\n    ///\n    /// let stream_map: StreamMap = [(0, vec![headers.clone()])].into();\n    /// assert_eq!(stream_map.all_frames(), vec![headers]);\n    /// ```\n    pub fn all_frames(&self) -> Vec<H3iFrame> {\n        self.stream_frame_map\n            .values()\n            .flatten()\n            .map(Clone::clone)\n            .collect::<Vec<H3iFrame>>()\n    }\n\n    /// Get all frames on a given `stream_id`.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use h3i::client::connection_summary::StreamMap;\n    /// use h3i::frame::EnrichedHeaders;\n    /// use h3i::frame::H3iFrame;\n    /// use quiche::h3::Header;\n    /// use std::iter::FromIterator;\n    ///\n    /// let mut stream_map = StreamMap::default();\n    ///\n    /// let h = Header::new(b\"hello\", b\"world\");\n    /// let headers = H3iFrame::Headers(EnrichedHeaders::from(vec![h]));\n    ///\n    /// let stream_map: StreamMap = [(0, vec![headers.clone()])].into();\n    /// assert_eq!(stream_map.stream(0), vec![headers]);\n    /// ```\n    pub fn stream(&self, stream_id: u64) -> Vec<H3iFrame> {\n        self.stream_frame_map\n            .get(&stream_id)\n            .cloned()\n            .unwrap_or_default()\n    }\n\n    /// Check if a provided [`H3iFrame`] was received, regardless of what stream\n    /// it was received on.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use h3i::client::connection_summary::StreamMap;\n    /// use h3i::frame::EnrichedHeaders;\n    /// use h3i::frame::H3iFrame;\n    /// use quiche::h3::Header;\n    /// use std::iter::FromIterator;\n    ///\n    /// let h = Header::new(b\"hello\", b\"world\");\n    /// let headers = H3iFrame::Headers(EnrichedHeaders::from(vec![h]));\n    ///\n    /// let stream_map: StreamMap = [(0, vec![headers.clone()])].into();\n    /// assert!(stream_map.received_frame(&headers));\n    /// ```\n    pub fn received_frame(&self, frame: &H3iFrame) -> bool {\n        self.all_frames().contains(frame)\n    }\n\n    /// Check if a provided [`H3iFrame`] was received over a specified stream.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use h3i::client::connection_summary::StreamMap;\n    /// use h3i::frame::EnrichedHeaders;\n    /// use h3i::frame::H3iFrame;\n    /// use quiche::h3::Header;\n    /// use std::iter::FromIterator;\n    ///\n    /// let h = Header::new(b\"hello\", b\"world\");\n    /// let headers = H3iFrame::Headers(EnrichedHeaders::from(vec![h]));\n    ///\n    /// let stream_map: StreamMap = [(0, vec![headers.clone()])].into();\n    /// assert!(stream_map.received_frame_on_stream(0, &headers));\n    /// ```\n    pub fn received_frame_on_stream(\n        &self, stream: u64, frame: &H3iFrame,\n    ) -> bool {\n        self.stream_frame_map\n            .get(&stream)\n            .map(|v| v.contains(frame))\n            .is_some()\n    }\n\n    /// Check if the stream map is empty, e.g., no frames were received.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use h3i::client::connection_summary::StreamMap;\n    /// use h3i::frame::EnrichedHeaders;\n    /// use h3i::frame::H3iFrame;\n    /// use quiche::h3::Header;\n    /// use std::iter::FromIterator;\n    ///\n    /// let mut stream_map = StreamMap::default();\n    /// assert!(stream_map.is_empty());\n    ///\n    /// let h = Header::new(b\"hello\", b\"world\");\n    /// let headers = H3iFrame::Headers(EnrichedHeaders::from(vec![h]));\n    ///\n    /// let stream_map: StreamMap = [(0, vec![headers.clone()])].into();\n    /// assert!(!stream_map.is_empty());\n    /// ```\n    pub fn is_empty(&self) -> bool {\n        self.stream_frame_map.is_empty()\n    }\n\n    /// See all HEADERS received on a given stream.\n    ///\n    /// # Example\n    ///\n    /// ```\n    /// use h3i::client::connection_summary::StreamMap;\n    /// use h3i::frame::EnrichedHeaders;\n    /// use h3i::frame::H3iFrame;\n    /// use quiche::h3::Header;\n    /// use std::iter::FromIterator;\n    ///\n    /// let h = Header::new(b\"hello\", b\"world\");\n    /// let enriched = EnrichedHeaders::from(vec![h]);\n    /// let headers = H3iFrame::Headers(enriched.clone());\n    /// let data = H3iFrame::QuicheH3(quiche::h3::frame::Frame::Data {\n    ///     payload: b\"hello world\".to_vec(),\n    /// });\n    ///\n    /// let stream_map: StreamMap = [(0, vec![headers.clone(), data.clone()])].into();\n    /// assert_eq!(stream_map.headers_on_stream(0), vec![enriched]);\n    /// ```\n    pub fn headers_on_stream(&self, stream_id: u64) -> Vec<EnrichedHeaders> {\n        self.stream(stream_id)\n            .into_iter()\n            .filter_map(|h3i_frame| h3i_frame.to_enriched_headers())\n            .collect()\n    }\n\n    /// If all [`CloseTriggerFrame`]s were seen. If no triggers were expected,\n    /// this will return `false`.\n    pub fn all_close_trigger_frames_seen(&self) -> bool {\n        if let Some(triggers) = self.close_trigger_frames.as_ref() {\n            triggers.saw_all_trigger_frames()\n        } else {\n            false\n        }\n    }\n\n    /// The set of all [`CloseTriggerFrame`]s that were _not_ seen on the\n    /// connection. Returns `None` if\n    pub fn missing_close_trigger_frames(&self) -> Option<Vec<CloseTriggerFrame>> {\n        self.close_trigger_frames\n            .as_ref()\n            .map(|e| e.missing_triggers())\n    }\n\n    ///  Not `pub` as users aren't expected to build their own [`StreamMap`]s.\n    pub(crate) fn new(close_trigger_frames: Option<CloseTriggerFrames>) -> Self {\n        Self {\n            close_trigger_frames,\n            ..Default::default()\n        }\n    }\n\n    pub(crate) fn insert(&mut self, stream_id: u64, frame: H3iFrame) {\n        if let Some(expected) = self.close_trigger_frames.as_mut() {\n            expected.receive_frame(stream_id, &frame);\n        }\n\n        self.stream_frame_map\n            .entry(stream_id)\n            .or_default()\n            .push(frame);\n    }\n\n    /// Close a [`quiche::Connection`] with the CONNECTION_CLOSE frame specified\n    /// by [`CloseTriggerFrames`]. If no [`CloseTriggerFrames`] exist, this is a\n    /// no-op.\n    pub(crate) fn close_due_to_trigger_frames(\n        &self, qconn: &mut quiche::Connection,\n    ) {\n        if let Some(ConnectionError {\n            is_app,\n            error_code,\n            reason,\n        }) = self.close_trigger_frames.as_ref().map(|tf| &tf.close_with)\n        {\n            let _ = qconn.close(*is_app, *error_code, reason);\n        }\n    }\n}\n\n/// A container for frames that h3i expects to see over a given connection. If\n/// h3i receives all the frames it expects, it will send a CONNECTION_CLOSE\n/// frame to the server. This bypasses the idle timeout and vastly quickens test\n/// suites which depend heavily on h3i.\n///\n/// The specific CONNECTION_CLOSE frame can be customized by passing a\n/// [`ConnectionError`] to [`Self::new_with_connection_close`]. h3i will send an\n/// application CONNECTION_CLOSE frame with error code 0x100 if this struct is\n/// constructed with the [`Self::new`] constructor.\n#[derive(Clone, Serialize, Debug)]\npub struct CloseTriggerFrames {\n    missing: Vec<CloseTriggerFrame>,\n    #[serde(skip)]\n    close_with: ConnectionError,\n}\n\nimpl CloseTriggerFrames {\n    /// Create a new [`CloseTriggerFrames`]. If all expected frames are\n    /// received, h3i will close the connection with an application-level\n    /// CONNECTION_CLOSE frame with error code 0x100.\n    pub fn new(frames: Vec<CloseTriggerFrame>) -> Self {\n        Self::new_with_connection_close(frames, ConnectionError {\n            is_app: true,\n            error_code: quiche::h3::WireErrorCode::NoError as u64,\n            reason: b\"saw all close trigger frames\".to_vec(),\n        })\n    }\n\n    /// Create a new [`CloseTriggerFrames`] with a custom close frame. When all\n    /// close trigger frames are received, h3i will close the connection with\n    /// the level, error code, and reason from `close_with`.\n    pub fn new_with_connection_close(\n        frames: Vec<CloseTriggerFrame>, close_with: ConnectionError,\n    ) -> Self {\n        Self {\n            missing: frames,\n            close_with,\n        }\n    }\n\n    fn receive_frame(&mut self, stream_id: u64, frame: &H3iFrame) {\n        for (i, trigger) in self.missing.iter_mut().enumerate() {\n            if trigger.is_equivalent(frame) && trigger.stream_id() == stream_id {\n                self.missing.remove(i);\n                break;\n            }\n        }\n    }\n\n    fn saw_all_trigger_frames(&self) -> bool {\n        self.missing.is_empty()\n    }\n\n    fn missing_triggers(&self) -> Vec<CloseTriggerFrame> {\n        self.missing.clone()\n    }\n}\n\nimpl From<Vec<CloseTriggerFrame>> for CloseTriggerFrames {\n    fn from(value: Vec<CloseTriggerFrame>) -> Self {\n        Self::new(value)\n    }\n}\n\n/// Denotes why the connection was closed.\n#[derive(Default)]\npub struct ConnectionCloseDetails {\n    peer_error: Option<ConnectionError>,\n    local_error: Option<ConnectionError>,\n    /// If the connection timed out.\n    pub timed_out: bool,\n    /// Return the session from the underlying connection.\n    pub session: Option<Vec<u8>>,\n}\n\nimpl core::fmt::Debug for ConnectionCloseDetails {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        // Avoid printing 'session' since it contains connection secrets.\n        f.debug_struct(\"ConnectionCloseDetails\")\n            .field(\"peer_error\", &self.peer_error)\n            .field(\"local_error\", &self.local_error)\n            .field(\"timed_out\", &self.timed_out)\n            .finish()\n    }\n}\n\nimpl ConnectionCloseDetails {\n    pub fn new<F: quiche::BufFactory>(qconn: &quiche::Connection<F>) -> Self {\n        let session = qconn.session().map(|s| s.to_vec());\n        Self {\n            peer_error: qconn.peer_error().cloned(),\n            local_error: qconn.local_error().cloned(),\n            timed_out: qconn.is_timed_out(),\n            session,\n        }\n    }\n\n    /// The error sent from the peer, if any.\n    pub fn peer_error(&self) -> Option<&ConnectionError> {\n        self.peer_error.as_ref()\n    }\n\n    /// The error generated locally, if any.\n    pub fn local_error(&self) -> Option<&ConnectionError> {\n        self.local_error.as_ref()\n    }\n\n    /// If the connection didn't see an error, either one from the peer or\n    /// generated locally.\n    pub fn no_err(&self) -> bool {\n        self.peer_error.is_none() && self.local_error.is_none()\n    }\n}\n\nimpl Serialize for ConnectionCloseDetails {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut state: <S as Serializer>::SerializeStruct =\n            s.serialize_struct(\"enriched_connection_error\", 3)?;\n        if let Some(pe) = &self.peer_error {\n            state.serialize_field(\n                \"peer_error\",\n                &SerializableConnectionError(pe),\n            )?;\n        }\n\n        if let Some(le) = &self.local_error {\n            state.serialize_field(\n                \"local_error\",\n                &SerializableConnectionError(le),\n            )?;\n        }\n\n        state.serialize_field(\"timed_out\", &self.timed_out)?;\n        state.end()\n    }\n}\n\n/// A wrapper to help serialize [quiche::PathStats]\npub struct SerializablePathStats<'a>(&'a quiche::PathStats);\n\nimpl Serialize for SerializablePathStats<'_> {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut state = s.serialize_struct(\"path_stats\", 17)?;\n        state.serialize_field(\"local_addr\", &self.0.local_addr)?;\n        state.serialize_field(\"peer_addr\", &self.0.peer_addr)?;\n        state.serialize_field(\"active\", &self.0.active)?;\n        state.serialize_field(\"recv\", &self.0.recv)?;\n        state.serialize_field(\"sent\", &self.0.sent)?;\n        state.serialize_field(\"lost\", &self.0.lost)?;\n        state.serialize_field(\"retrans\", &self.0.retrans)?;\n        state.serialize_field(\"rtt\", &self.0.rtt.as_secs_f64())?;\n        state.serialize_field(\n            \"min_rtt\",\n            &self.0.min_rtt.map(|x| x.as_secs_f64()),\n        )?;\n        state.serialize_field(\"rttvar\", &self.0.rttvar.as_secs_f64())?;\n        state.serialize_field(\"cwnd\", &self.0.cwnd)?;\n        state.serialize_field(\"sent_bytes\", &self.0.sent_bytes)?;\n        state.serialize_field(\"recv_bytes\", &self.0.recv_bytes)?;\n        state.serialize_field(\"lost_bytes\", &self.0.lost_bytes)?;\n        state.serialize_field(\n            \"stream_retrans_bytes\",\n            &self.0.stream_retrans_bytes,\n        )?;\n        state.serialize_field(\"pmtu\", &self.0.pmtu)?;\n        state.serialize_field(\"delivery_rate\", &self.0.delivery_rate)?;\n        state.end()\n    }\n}\n\n/// A wrapper to help serialize [quiche::Stats]\npub struct SerializableStats<'a>(&'a quiche::Stats);\n\nimpl Serialize for SerializableStats<'_> {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut state = s.serialize_struct(\"path_stats\", 14)?;\n        state.serialize_field(\"recv\", &self.0.recv)?;\n        state.serialize_field(\"sent\", &self.0.sent)?;\n        state.serialize_field(\"lost\", &self.0.lost)?;\n        state.serialize_field(\"retrans\", &self.0.retrans)?;\n        state.serialize_field(\"sent_bytes\", &self.0.sent_bytes)?;\n        state.serialize_field(\"recv_bytes\", &self.0.recv_bytes)?;\n        state.serialize_field(\"lost_bytes\", &self.0.lost_bytes)?;\n        state.serialize_field(\n            \"stream_retrans_bytes\",\n            &self.0.stream_retrans_bytes,\n        )?;\n        state.serialize_field(\"paths_count\", &self.0.paths_count)?;\n        state.serialize_field(\n            \"reset_stream_count_local\",\n            &self.0.reset_stream_count_local,\n        )?;\n        state.serialize_field(\n            \"stopped_stream_count_local\",\n            &self.0.stopped_stream_count_local,\n        )?;\n        state.serialize_field(\n            \"reset_stream_count_remote\",\n            &self.0.reset_stream_count_remote,\n        )?;\n        state.serialize_field(\n            \"stopped_stream_count_remote\",\n            &self.0.stopped_stream_count_remote,\n        )?;\n        state.serialize_field(\n            \"path_challenge_rx_count\",\n            &self.0.path_challenge_rx_count,\n        )?;\n        state.end()\n    }\n}\n\n/// A wrapper to help serialize a [quiche::ConnectionError]\n#[derive(Clone, Debug)]\npub struct SerializableConnectionError<'a>(&'a quiche::ConnectionError);\n\nimpl Serialize for SerializableConnectionError<'_> {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut state = s.serialize_struct(\"path_stats\", 3)?;\n        state.serialize_field(\"is_app\", &self.0.is_app)?;\n        state.serialize_field(\"error_code\", &self.0.error_code)?;\n        let max = cmp::min(self.0.reason.len(), MAX_SERIALIZED_BUFFER_LEN);\n        state.serialize_field(\n            \"reason\",\n            &String::from_utf8_lossy(&self.0.reason[..max]),\n        )?;\n        state.end()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::frame::EnrichedHeaders;\n    use quiche::h3::Header;\n\n    fn h3i_frame() -> H3iFrame {\n        vec![Header::new(b\"hello\", b\"world\")].into()\n    }\n\n    #[test]\n    fn close_trigger_frame() {\n        let frame = h3i_frame();\n        let mut triggers = CloseTriggerFrames::new(vec![CloseTriggerFrame::new(\n            0,\n            frame.clone(),\n        )]);\n\n        triggers.receive_frame(0, &frame);\n\n        assert!(triggers.saw_all_trigger_frames());\n    }\n\n    #[test]\n    fn trigger_frame_missing() {\n        let frame = h3i_frame();\n        let expected_frames = vec![\n            CloseTriggerFrame::new(0, frame.clone()),\n            CloseTriggerFrame::new(4, frame.clone()),\n            CloseTriggerFrame::new(8, vec![Header::new(b\"go\", b\"jets\")]),\n        ];\n        let mut expected = CloseTriggerFrames::new(expected_frames.clone());\n\n        expected.receive_frame(0, &frame);\n\n        assert!(!expected.saw_all_trigger_frames());\n        assert_eq!(expected.missing_triggers(), expected_frames[1..].to_vec());\n    }\n\n    fn stream_map_data() -> Vec<H3iFrame> {\n        let headers =\n            H3iFrame::Headers(EnrichedHeaders::from(vec![Header::new(\n                b\"hello\", b\"world\",\n            )]));\n        let data = H3iFrame::QuicheH3(quiche::h3::frame::Frame::Data {\n            payload: b\"hello world\".to_vec(),\n        });\n\n        vec![headers, data]\n    }\n\n    #[test]\n    fn test_stream_map_trigger_frames_with_none() {\n        let stream_map: StreamMap = vec![(0, stream_map_data())].into();\n        assert!(!stream_map.all_close_trigger_frames_seen());\n    }\n\n    #[test]\n    fn test_stream_map_trigger_frames() {\n        let data = stream_map_data();\n        let mut stream_map = StreamMap::new(Some(\n            vec![\n                CloseTriggerFrame::new(0, data[0].clone()),\n                CloseTriggerFrame::new(0, data[1].clone()),\n            ]\n            .into(),\n        ));\n\n        stream_map.insert(0, data[0].clone());\n        assert!(!stream_map.all_close_trigger_frames_seen());\n        assert_eq!(stream_map.missing_close_trigger_frames().unwrap(), vec![\n            CloseTriggerFrame::new(0, data[1].clone())\n        ]);\n    }\n}\n"
  },
  {
    "path": "h3i/src/client/mod.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! The main h3i client runner.\n//!\n//! The client is responsible for connecting to an indicated server, executing\n//! as series of [Action]s, and capturing the results in a\n//! [ConnectionSummary].\n\n#[cfg(feature = \"async\")]\npub mod async_client;\npub mod connection_summary;\npub mod sync_client;\n\nuse connection_summary::*;\nuse qlog::events::http3::FrameParsed;\nuse qlog::events::http3::HttpHeader;\nuse quiche::ConnectionError;\n\nuse std::collections::HashMap;\nuse std::net::SocketAddr;\nuse std::time::Instant;\n\nuse crate::actions::h3::Action;\nuse crate::actions::h3::ExpectedStreamSendResult;\nuse crate::actions::h3::StreamEvent;\nuse crate::actions::h3::StreamEventType;\nuse crate::config::Config;\nuse crate::frame::H3iFrame;\nuse crate::frame::ResetStream;\nuse crate::frame_parser::FrameParseResult;\nuse crate::frame_parser::FrameParser;\nuse crate::frame_parser::InterruptCause;\nuse crate::recordreplay::qlog::QlogEvent;\nuse crate::recordreplay::qlog::*;\nuse qlog::events::http3::Http3Frame;\nuse qlog::events::EventData;\nuse qlog::streamer::QlogStreamer;\nuse serde::Serialize;\n\nuse crate::quiche;\nuse quiche::h3::frame::Frame as QFrame;\nuse quiche::h3::Error;\nuse quiche::h3::NameValue;\n\nconst MAX_DATAGRAM_SIZE: usize = 1350;\nconst QUIC_VERSION: u32 = 1;\n\nfn handle_qlog(\n    qlog_streamer: Option<&mut QlogStreamer>, qlog_frame: Http3Frame,\n    stream_id: u64,\n) {\n    if let Some(s) = qlog_streamer {\n        let ev_data = EventData::Http3FrameParsed(FrameParsed {\n            stream_id,\n            frame: qlog_frame,\n            ..Default::default()\n        });\n\n        s.add_event_data_now(ev_data).ok();\n    }\n}\n\n#[derive(Debug, Serialize)]\n/// Represents different errors that can occur when the h3i client runs.\npub enum ClientError {\n    /// An error during the QUIC handshake.\n    HandshakeFail,\n    /// An error during HTTP/3 exchanges.\n    HttpFail,\n    /// Some other type of error.\n    Other(String),\n}\n\npub(crate) trait Client {\n    /// Gives mutable access to the stream parsers to update their state.\n    fn stream_parsers_mut(&mut self) -> &mut StreamParserMap;\n\n    /// Handles a response frame. This allows [`Client`]s to customize how they\n    /// construct a [`StreamMap`] from a list of frames.\n    fn handle_response_frame(&mut self, stream_id: u64, frame: H3iFrame);\n}\n\npub(crate) type StreamParserMap = HashMap<u64, FrameParser>;\n\nfn validate_stream_send_result(\n    result: quiche::Result<usize>, expected: &ExpectedStreamSendResult,\n    stream_id: u64,\n) {\n    match expected {\n        ExpectedStreamSendResult::Ok => {\n            result.unwrap_or_else(|err| {\n                panic!(\n                    \"Expected stream_send on stream {} to succeed, but got: {:?}\",\n                    stream_id, err\n                )\n            });\n        },\n        ExpectedStreamSendResult::OkExact(expected_bytes) => {\n            match result {\n                Ok(actual_bytes) if actual_bytes == *expected_bytes => {},\n                Ok(actual_bytes) => panic!(\n                    \"Expected stream_send on stream {} to write {} bytes, got {}\",\n                    stream_id, expected_bytes, actual_bytes\n                ),\n                Err(err) => panic!(\n                    \"Expected stream_send on stream {} to write {} bytes, got error: {:?}\",\n                    stream_id, expected_bytes, err\n                ),\n            }\n        },\n        ExpectedStreamSendResult::Error(expected_err) => {\n            match result {\n                Ok(written) => panic!(\n                    \"Expected stream_send on stream {} to fail with {:?}, but wrote {} bytes\",\n                    stream_id, expected_err, written\n                ),\n                Err(actual_err) if &actual_err == expected_err => {},\n                Err(actual_err) => panic!(\n                    \"Expected stream_send on stream {} to fail with {:?}, got {:?}\",\n                    stream_id, expected_err, actual_err\n                ),\n            }\n        },\n    }\n}\n\npub(crate) fn execute_action<F: quiche::BufFactory>(\n    action: &Action, conn: &mut quiche::Connection<F>,\n    stream_parsers: &mut StreamParserMap,\n) {\n    match action {\n        Action::SendFrame {\n            stream_id,\n            fin_stream,\n            frame,\n            expected_result,\n        } => {\n            log::info!(\"frame tx id={stream_id} frame={frame:?}\");\n\n            // TODO: make serialization smarter\n            let mut d = [42; 9999];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            if let Some(s) = conn.qlog_streamer() {\n                let events: QlogEvents = action.into();\n                for event in events {\n                    match event {\n                        QlogEvent::Event { data, ex_data } => {\n                            // skip dummy packet\n                            if matches!(\n                                data.as_ref(),\n                                EventData::QuicPacketSent(..)\n                            ) {\n                                continue;\n                            }\n\n                            s.add_event_data_ex_now(*data, ex_data).ok();\n                        },\n\n                        QlogEvent::JsonEvent(mut ev) => {\n                            // need to rewrite the event time\n                            ev.time = Instant::now()\n                                .duration_since(s.start_time())\n                                .as_secs_f64() *\n                                1000.0;\n                            s.add_event(ev).ok();\n                        },\n                    }\n                }\n            }\n            let len = frame.to_bytes(&mut b).unwrap();\n\n            // TODO - consider storying result in ConnectionSummary too.\n            let result = conn.stream_send(*stream_id, &d[..len], *fin_stream);\n            validate_stream_send_result(result, expected_result, *stream_id);\n\n            stream_parsers\n                .entry(*stream_id)\n                .or_insert_with(|| FrameParser::new(*stream_id));\n        },\n\n        Action::SendHeadersFrame {\n            stream_id,\n            fin_stream,\n            headers,\n            frame,\n            expected_result,\n            ..\n        } => {\n            log::info!(\"headers frame tx stream={stream_id} hdrs={headers:?}\");\n\n            // TODO: make serialization smarter\n            let mut d = [42; 9999];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            if let Some(s) = conn.qlog_streamer() {\n                let events: QlogEvents = action.into();\n                for event in events {\n                    match event {\n                        QlogEvent::Event { data, ex_data } => {\n                            // skip dummy packet\n                            if matches!(\n                                data.as_ref(),\n                                EventData::QuicPacketSent(..)\n                            ) {\n                                continue;\n                            }\n\n                            s.add_event_data_ex_now(*data, ex_data).ok();\n                        },\n\n                        QlogEvent::JsonEvent(mut ev) => {\n                            // need to rewrite the event time\n                            ev.time = Instant::now()\n                                .duration_since(s.start_time())\n                                .as_secs_f64() *\n                                1000.0;\n                            s.add_event(ev).ok();\n                        },\n                    }\n                }\n            }\n            let len = frame.to_bytes(&mut b).unwrap();\n            let result = conn.stream_send(*stream_id, &d[..len], *fin_stream);\n            validate_stream_send_result(result, expected_result, *stream_id);\n\n            stream_parsers\n                .entry(*stream_id)\n                .or_insert_with(|| FrameParser::new(*stream_id));\n        },\n\n        Action::OpenUniStream {\n            stream_id,\n            fin_stream,\n            stream_type,\n            expected_result,\n        } => {\n            log::info!(\n                \"open uni stream_id={stream_id} ty={stream_type} fin={fin_stream}\"\n            );\n\n            let mut d = [42; 8];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            b.put_varint(*stream_type).unwrap();\n            let off = b.off();\n\n            let result = conn.stream_send(*stream_id, &d[..off], *fin_stream);\n            validate_stream_send_result(result, expected_result, *stream_id);\n\n            stream_parsers\n                .entry(*stream_id)\n                .or_insert_with(|| FrameParser::new(*stream_id));\n        },\n\n        Action::StreamBytes {\n            stream_id,\n            bytes,\n            fin_stream,\n            expected_result,\n        } => {\n            log::info!(\n                \"stream bytes tx id={} len={} fin={}\",\n                stream_id,\n                bytes.len(),\n                fin_stream\n            );\n            let result = conn.stream_send(*stream_id, bytes, *fin_stream);\n            validate_stream_send_result(result, expected_result, *stream_id);\n\n            stream_parsers\n                .entry(*stream_id)\n                .or_insert_with(|| FrameParser::new(*stream_id));\n        },\n\n        Action::SendDatagram { payload } => {\n            log::info!(\"dgram tx len={}\", payload.len(),);\n\n            conn.dgram_send(payload)\n                .expect(\"datagram extension not enabled by peer\");\n        },\n\n        Action::ResetStream {\n            stream_id,\n            error_code,\n        } => {\n            log::info!(\n                \"reset_stream stream_id={stream_id} error_code={error_code}\"\n            );\n            if let Err(e) = conn.stream_shutdown(\n                *stream_id,\n                quiche::Shutdown::Write,\n                *error_code,\n            ) {\n                log::error!(\"can't send reset_stream: {e}\");\n                // Clients can't reset streams they don't own. If we attempt to do\n                // this, stream_shutdown would fail, and we\n                // shouldn't create a parser.\n                return;\n            }\n\n            stream_parsers\n                .entry(*stream_id)\n                .or_insert_with(|| FrameParser::new(*stream_id));\n        },\n\n        Action::StopSending {\n            stream_id,\n            error_code,\n        } => {\n            log::info!(\n                \"stop_sending stream id={stream_id} error_code={error_code}\"\n            );\n\n            if let Err(e) = conn.stream_shutdown(\n                *stream_id,\n                quiche::Shutdown::Read,\n                *error_code,\n            ) {\n                log::error!(\"can't send stop_sending: {e}\");\n            }\n\n            // A `STOP_SENDING` should elicit a `RESET_STREAM` in response, which\n            // the frame parser can automatically handle.\n            stream_parsers\n                .entry(*stream_id)\n                .or_insert_with(|| FrameParser::new(*stream_id));\n        },\n\n        Action::ConnectionClose { error } => {\n            let ConnectionError {\n                is_app,\n                error_code,\n                reason,\n            } = error;\n\n            log::info!(\"connection_close={error:?}\");\n            let _ = conn.close(*is_app, *error_code, reason);\n        },\n\n        // Neither of these actions will manipulate the Quiche connection\n        Action::FlushPackets | Action::Wait { .. } => unreachable!(),\n    }\n}\n\npub(crate) fn parse_streams<F: quiche::BufFactory, C: Client>(\n    conn: &mut quiche::Connection<F>, client: &mut C,\n) -> Vec<StreamEvent> {\n    let mut responded_streams: Vec<StreamEvent> =\n        Vec::with_capacity(conn.readable().len());\n\n    for stream in conn.readable() {\n        // TODO: ignoring control streams\n        if stream % 4 != 0 {\n            continue;\n        }\n\n        loop {\n            let stream_parse_result = client\n                .stream_parsers_mut()\n                .get_mut(&stream)\n                .expect(\"stream readable with no parser\")\n                .try_parse_frame(conn);\n\n            match stream_parse_result {\n                Ok(FrameParseResult::FrameParsed { h3i_frame, fin }) => {\n                    if let H3iFrame::Headers(ref headers) = h3i_frame {\n                        log::info!(\"hdrs={headers:?}\");\n                    }\n\n                    handle_response_frame(\n                        client,\n                        conn.qlog_streamer(),\n                        &mut responded_streams,\n                        stream,\n                        h3i_frame,\n                    );\n\n                    if fin {\n                        handle_fin(\n                            &mut responded_streams,\n                            client.stream_parsers_mut(),\n                            stream,\n                        );\n                        break;\n                    }\n                },\n                Ok(FrameParseResult::Retry) => {},\n                Ok(FrameParseResult::Interrupted(cause)) => {\n                    if let InterruptCause::ResetStream(error_code) = cause {\n                        let frame = H3iFrame::ResetStream(ResetStream {\n                            stream_id: stream,\n                            error_code,\n                        });\n\n                        log::info!(\"received reset stream: {frame:?}\");\n                        handle_response_frame(\n                            client,\n                            None,\n                            &mut responded_streams,\n                            stream,\n                            frame,\n                        );\n                    }\n\n                    handle_fin(\n                        &mut responded_streams,\n                        client.stream_parsers_mut(),\n                        stream,\n                    );\n                    break;\n                },\n                Err(e) => {\n                    match e {\n                        Error::TransportError(quiche::Error::Done) => {\n                            log::debug!(\"stream {stream} exhausted\");\n                        },\n                        Error::TransportError(quiche::Error::StreamReset(\n                            error_code,\n                        )) => {\n                            let frame = H3iFrame::ResetStream(ResetStream {\n                                stream_id: stream,\n                                error_code,\n                            });\n\n                            log::info!(\"received reset stream: {frame:?}\");\n\n                            handle_response_frame(\n                                client,\n                                None,\n                                &mut responded_streams,\n                                stream,\n                                frame,\n                            );\n\n                            client.stream_parsers_mut().remove(&stream);\n                        },\n                        _ => {\n                            log::warn!(\"stream read error: {e}\");\n                        },\n                    };\n\n                    break;\n                },\n            }\n        }\n    }\n\n    responded_streams\n}\n\nfn handle_fin(\n    responded_streams: &mut Vec<StreamEvent>,\n    stream_parsers: &mut StreamParserMap, stream_id: u64,\n) {\n    responded_streams.push(StreamEvent {\n        stream_id,\n        event_type: StreamEventType::Finished,\n    });\n\n    stream_parsers.remove(&stream_id);\n}\n\n/// Push any responses to the [StreamMap] as well as store them in the\n/// `responded` vector\nfn handle_response_frame<C: Client>(\n    client: &mut C, qlog_streamer: Option<&mut QlogStreamer>,\n    responded_streams: &mut Vec<StreamEvent>, stream_id: u64, frame: H3iFrame,\n) {\n    let cloned = frame.clone();\n    client.handle_response_frame(stream_id, cloned);\n\n    let mut to_qlog: Option<Http3Frame> = None;\n    let mut push_to_responses: Option<StreamEvent> = None;\n\n    match frame {\n        H3iFrame::Headers(enriched_headers) => {\n            push_to_responses = Some(StreamEvent {\n                stream_id,\n                event_type: StreamEventType::Headers,\n            });\n\n            let qlog_headers: Vec<HttpHeader> = enriched_headers\n                .headers()\n                .iter()\n                .map(|h| qlog::events::http3::HttpHeader {\n                    name: Some(String::from_utf8_lossy(h.name()).into_owned()),\n                    name_bytes: None,\n                    value: Some(String::from_utf8_lossy(h.value()).into_owned()),\n                    value_bytes: None,\n                })\n                .collect();\n\n            to_qlog = Some(Http3Frame::Headers {\n                headers: qlog_headers,\n            });\n        },\n        H3iFrame::QuicheH3(quiche_frame) => {\n            if let QFrame::Data { .. } = quiche_frame {\n                push_to_responses = Some(StreamEvent {\n                    stream_id,\n                    event_type: StreamEventType::Data,\n                });\n            }\n\n            to_qlog = Some(quiche_frame.to_qlog());\n        },\n        H3iFrame::ResetStream(_) => {\n            push_to_responses = Some(StreamEvent {\n                stream_id,\n                event_type: StreamEventType::Finished,\n            });\n        },\n    }\n\n    if let Some(to_qlog) = to_qlog {\n        handle_qlog(qlog_streamer, to_qlog, stream_id);\n    }\n\n    if let Some(to_push) = push_to_responses {\n        responded_streams.push(to_push);\n    }\n}\n\npub(crate) struct ParsedArgs<'a> {\n    pub(crate) bind_addr: SocketAddr,\n    pub(crate) peer_addr: SocketAddr,\n    pub(crate) connect_url: Option<&'a str>,\n}\n\npub(crate) fn parse_args(args: &Config) -> ParsedArgs<'_> {\n    // We'll only connect to one server.\n    let connect_url = if !args.omit_sni {\n        args.host_port.split(':').next()\n    } else {\n        None\n    };\n\n    let (peer_addr, bind_addr) = resolve_socket_addrs(args);\n\n    ParsedArgs {\n        peer_addr,\n        bind_addr,\n        connect_url,\n    }\n}\n\nfn resolve_socket_addrs(args: &Config) -> (SocketAddr, SocketAddr) {\n    // Resolve server address.\n    let peer_addr = if let Some(addr) = &args.connect_to {\n        addr.parse().expect(\"--connect-to is expected to be a string containing an IPv4 or IPv6 address with a port. E.g. 192.0.2.0:443\")\n    } else {\n        let x = format!(\"https://{}\", args.host_port);\n        *url::Url::parse(&x)\n            .unwrap()\n            .socket_addrs(|| None)\n            .unwrap()\n            .first()\n            .unwrap()\n    };\n\n    // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the\n    // server address. This is needed on macOS and BSD variants that don't\n    // support binding to IN6ADDR_ANY for both v4 and v6.\n    let bind_addr = match peer_addr {\n        std::net::SocketAddr::V4(_) => format!(\"0.0.0.0:{}\", args.source_port),\n        std::net::SocketAddr::V6(_) => format!(\"[::]:{}\", args.source_port),\n    };\n\n    (\n        peer_addr,\n        bind_addr.parse().expect(\"unable to parse bind address\"),\n    )\n}\n"
  },
  {
    "path": "h3i/src/client/sync_client.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Responsible for creating a [quiche::Connection] and managing I/O.\n\nuse std::slice::Iter;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse ring::rand::*;\n\nuse crate::client::QUIC_VERSION;\nuse crate::frame::H3iFrame;\nuse crate::quiche;\n\nuse crate::actions::h3::Action;\nuse crate::actions::h3::StreamEventType;\nuse crate::actions::h3::WaitType;\nuse crate::actions::h3::WaitingFor;\nuse crate::client::execute_action;\nuse crate::client::parse_streams;\nuse crate::client::ClientError;\nuse crate::client::ConnectionCloseDetails;\nuse crate::client::MAX_DATAGRAM_SIZE;\nuse crate::config::Config;\n\nuse super::parse_args;\nuse super::Client;\nuse super::CloseTriggerFrames;\nuse super::ConnectionSummary;\nuse super::ParsedArgs;\nuse super::StreamMap;\nuse super::StreamParserMap;\n\n#[derive(Default)]\nstruct SyncClient {\n    streams: StreamMap,\n    stream_parsers: StreamParserMap,\n}\n\nimpl SyncClient {\n    fn new(close_trigger_frames: Option<CloseTriggerFrames>) -> Self {\n        Self {\n            streams: StreamMap::new(close_trigger_frames),\n            ..Default::default()\n        }\n    }\n}\n\nimpl Client for SyncClient {\n    fn stream_parsers_mut(&mut self) -> &mut StreamParserMap {\n        &mut self.stream_parsers\n    }\n\n    fn handle_response_frame(&mut self, stream_id: u64, frame: H3iFrame) {\n        self.streams.insert(stream_id, frame);\n    }\n}\n\nfn create_config(args: &Config, should_log_keys: bool) -> quiche::Config {\n    // Create the configuration for the QUIC connection.\n    let mut config = quiche::Config::new(QUIC_VERSION).unwrap();\n\n    config.verify_peer(args.verify_peer);\n    config.set_application_protos(&[b\"h3\"]).unwrap();\n    config.set_max_idle_timeout(args.idle_timeout);\n    config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_initial_max_data(10_000_000);\n    config\n        .set_initial_max_stream_data_bidi_local(args.max_stream_data_bidi_local);\n    config.set_initial_max_stream_data_bidi_remote(\n        args.max_stream_data_bidi_remote,\n    );\n    config.set_initial_max_stream_data_uni(args.max_stream_data_uni);\n    config.set_initial_max_streams_bidi(args.max_streams_bidi);\n    config.set_initial_max_streams_uni(args.max_streams_uni);\n    config.set_disable_active_migration(true);\n    config.set_active_connection_id_limit(0);\n\n    config.set_max_connection_window(args.max_window);\n    config.set_max_stream_window(args.max_stream_window);\n    config.set_enable_send_streams_blocked(true);\n    config.grease(false);\n\n    if args.enable_early_data {\n        config.enable_early_data();\n    }\n\n    if args.enable_dgram {\n        config.enable_dgram(\n            true,\n            args.dgram_recv_queue_len,\n            args.dgram_send_queue_len,\n        );\n    }\n    if should_log_keys {\n        config.log_keys()\n    }\n\n    config\n}\n\n/// Connect to a server and execute provided actions.\n///\n/// Constructs a socket and [quiche::Connection] based on the provided `args`,\n/// then iterates over `actions`.\n///\n/// If `close_trigger_frames` is specified, h3i will close the connection\n/// immediately upon receiving all of the supplied frames rather than waiting\n/// for the idle timeout. See [`CloseTriggerFrames`] for details.\n///\n/// Returns a [ConnectionSummary] on success, [ClientError] on failure.\npub fn connect(\n    args: Config, actions: Vec<Action>,\n    close_trigger_frames: Option<CloseTriggerFrames>,\n) -> std::result::Result<ConnectionSummary, ClientError> {\n    connect_with_early_data(args, None, actions, close_trigger_frames)\n}\n\n/// Connect to a server and execute provided early_action and actions.\n///\n/// See `connect` for additional documentation.\npub fn connect_with_early_data(\n    args: Config, early_actions: Option<Vec<Action>>, actions: Vec<Action>,\n    close_trigger_frames: Option<CloseTriggerFrames>,\n) -> std::result::Result<ConnectionSummary, ClientError> {\n    let mut buf = [0; 65535];\n    let mut out = [0; MAX_DATAGRAM_SIZE];\n\n    let ParsedArgs {\n        connect_url,\n        bind_addr,\n        peer_addr,\n    } = parse_args(&args);\n\n    // Setup the event loop.\n    let mut poll = mio::Poll::new().unwrap();\n    let mut events = mio::Events::with_capacity(1024);\n\n    // Create the UDP socket backing the QUIC connection, and register it with\n    // the event loop.\n    let mut socket = mio::net::UdpSocket::bind(bind_addr).unwrap();\n    poll.registry()\n        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)\n        .unwrap();\n\n    let mut keylog = None;\n    if let Some(keylog_path) = std::env::var_os(\"SSLKEYLOGFILE\") {\n        let file = std::fs::OpenOptions::new()\n            .create(true)\n            .append(true)\n            .open(keylog_path)\n            .unwrap();\n\n        keylog = Some(file);\n    }\n\n    let mut config = create_config(&args, keylog.is_some());\n\n    // Generate a random source connection ID for the connection.\n    let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n\n    let rng = SystemRandom::new();\n    rng.fill(&mut scid[..]).unwrap();\n\n    let scid = quiche::ConnectionId::from_ref(&scid);\n\n    let Ok(local_addr) = socket.local_addr() else {\n        return Err(ClientError::Other(\"invalid socket\".to_string()));\n    };\n\n    // Create a new client-side QUIC connection.\n    let mut conn =\n        quiche::connect(connect_url, &scid, local_addr, peer_addr, &mut config)\n            .map_err(|e| ClientError::Other(e.to_string()))?;\n\n    if let Some(session) = &args.session {\n        conn.set_session(session)\n            .map_err(|error| ClientError::Other(error.to_string()))?;\n    }\n\n    if let Some(keylog) = &mut keylog {\n        if let Ok(keylog) = keylog.try_clone() {\n            conn.set_keylog(Box::new(keylog));\n        }\n    }\n\n    log::info!(\n        \"connecting to {peer_addr:} from {local_addr:} with scid {scid:?}\",\n    );\n\n    let mut app_proto_selected = false;\n\n    // Send ClientHello and initiate the handshake.\n    let (write, send_info) = conn.send(&mut out).expect(\"initial send failed\");\n\n    let mut client = SyncClient::new(close_trigger_frames);\n    // Send early data if connection is_in_early_data (resumption with 0-RTT was\n    // successful) and if we have early_actions.\n    if conn.is_in_early_data() {\n        if let Some(early_actions) = early_actions {\n            let mut early_action_iter = early_actions.iter();\n            let mut wait_duration = None;\n            let mut wait_instant = None;\n            let mut waiting_for = WaitingFor::default();\n\n            check_duration_and_do_actions(\n                &mut wait_duration,\n                &mut wait_instant,\n                &mut early_action_iter,\n                &mut conn,\n                &mut waiting_for,\n                client.stream_parsers_mut(),\n            );\n        }\n    }\n\n    while let Err(e) = socket.send_to(&out[..write], send_info.to) {\n        if e.kind() == std::io::ErrorKind::WouldBlock {\n            log::debug!(\n                \"{} -> {}: send() would block\",\n                socket.local_addr().unwrap(),\n                send_info.to\n            );\n            continue;\n        }\n\n        return Err(ClientError::Other(format!(\"send() failed: {e:?}\")));\n    }\n\n    let app_data_start = std::time::Instant::now();\n\n    let mut action_iter = actions.iter();\n    let mut wait_duration = None;\n    let mut wait_instant = None;\n\n    let mut waiting_for = WaitingFor::default();\n\n    loop {\n        let actual_sleep = match (wait_duration, conn.timeout()) {\n            (Some(wait), Some(timeout)) => {\n                #[allow(clippy::comparison_chain)]\n                if timeout < wait {\n                    // shave some off the wait time so it doesn't go longer\n                    // than user really wanted.\n                    let new = wait - timeout;\n                    wait_duration = Some(new);\n                    Some(timeout)\n                } else if wait < timeout {\n                    Some(wait)\n                } else {\n                    // same, so picking either doesn't matter\n                    Some(timeout)\n                }\n            },\n            (None, Some(timeout)) => Some(timeout),\n            (Some(wait), None) => Some(wait),\n            _ => None,\n        };\n\n        log::debug!(\"actual sleep is {actual_sleep:?}\");\n        poll.poll(&mut events, actual_sleep).unwrap();\n\n        // If the event loop reported no events, run a belt and braces check on\n        // the quiche connection's timeouts.\n        if events.is_empty() {\n            log::debug!(\"timed out\");\n\n            conn.on_timeout();\n        }\n\n        // Read incoming UDP packets from the socket and feed them to quiche,\n        // until there are no more packets to read.\n        for event in &events {\n            let socket = match event.token() {\n                mio::Token(0) => &socket,\n\n                _ => unreachable!(),\n            };\n\n            let local_addr = socket.local_addr().unwrap();\n            'read: loop {\n                let (len, from) = match socket.recv_from(&mut buf) {\n                    Ok(v) => v,\n\n                    Err(e) => {\n                        // There are no more UDP packets to read on this socket.\n                        // Process subsequent events.\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            break 'read;\n                        }\n\n                        return Err(ClientError::Other(format!(\n                            \"{local_addr}: recv() failed: {e:?}\"\n                        )));\n                    },\n                };\n\n                let recv_info = quiche::RecvInfo {\n                    to: local_addr,\n                    from,\n                };\n\n                // Process potentially coalesced packets.\n                let _read = match conn.recv(&mut buf[..len], recv_info) {\n                    Ok(v) => v,\n\n                    Err(e) => {\n                        log::debug!(\"{local_addr}: recv failed: {e:?}\");\n                        continue 'read;\n                    },\n                };\n            }\n        }\n\n        log::debug!(\"done reading\");\n\n        if conn.is_closed() {\n            log::info!(\n                \"connection closed with error={:?} did_idle_timeout={}, stats={:?} path_stats={:?}\",\n                conn.peer_error(),\n                conn.is_timed_out(),\n                conn.stats(),\n                conn.path_stats().collect::<Vec<quiche::PathStats>>(),\n            );\n\n            if !conn.is_established() {\n                log::info!(\n                    \"connection timed out after {:?}\",\n                    app_data_start.elapsed(),\n                );\n\n                return Err(ClientError::HandshakeFail);\n            }\n\n            break;\n        }\n\n        // Create a new application protocol session once the QUIC connection is\n        // established.\n        if (conn.is_established() || conn.is_in_early_data()) &&\n            !app_proto_selected\n        {\n            app_proto_selected = true;\n        }\n\n        if app_proto_selected {\n            check_duration_and_do_actions(\n                &mut wait_duration,\n                &mut wait_instant,\n                &mut action_iter,\n                &mut conn,\n                &mut waiting_for,\n                client.stream_parsers_mut(),\n            );\n\n            let mut wait_cleared = false;\n            for response in parse_streams(&mut conn, &mut client) {\n                let stream_id = response.stream_id;\n\n                if let StreamEventType::Finished = response.event_type {\n                    waiting_for.clear_waits_on_stream(stream_id);\n                } else {\n                    waiting_for.remove_wait(response);\n                }\n\n                wait_cleared = true;\n            }\n\n            // Check if a CanOpenNumStreams wait is satisfied.\n            let before = waiting_for.is_empty();\n            waiting_for.check_can_open_num_streams(&conn);\n            if !before && waiting_for.is_empty() {\n                wait_cleared = true;\n            }\n\n            if client.streams.all_close_trigger_frames_seen() {\n                client.streams.close_due_to_trigger_frames(&mut conn);\n            }\n\n            if wait_cleared {\n                check_duration_and_do_actions(\n                    &mut wait_duration,\n                    &mut wait_instant,\n                    &mut action_iter,\n                    &mut conn,\n                    &mut waiting_for,\n                    client.stream_parsers_mut(),\n                );\n            }\n        }\n\n        // Provides as many CIDs as possible.\n        while conn.scids_left() > 0 {\n            let (scid, reset_token) = generate_cid_and_reset_token();\n\n            if conn.new_scid(&scid, reset_token, false).is_err() {\n                break;\n            }\n        }\n\n        // Generate outgoing QUIC packets and send them on the UDP socket, until\n        // quiche reports that there are no more packets to be sent.\n        let sockets = vec![&socket];\n\n        for socket in sockets {\n            let local_addr = socket.local_addr().unwrap();\n\n            for peer_addr in conn.paths_iter(local_addr) {\n                loop {\n                    let (write, send_info) = match conn.send_on_path(\n                        &mut out,\n                        Some(local_addr),\n                        Some(peer_addr),\n                    ) {\n                        Ok(v) => v,\n\n                        Err(quiche::Error::Done) => {\n                            break;\n                        },\n\n                        Err(e) => {\n                            log::error!(\n                                \"{local_addr} -> {peer_addr}: send failed: {e:?}\"\n                            );\n\n                            conn.close(false, 0x1, b\"fail\").ok();\n                            break;\n                        },\n                    };\n\n                    if let Err(e) = socket.send_to(&out[..write], send_info.to) {\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            log::debug!(\n                                \"{} -> {}: send() would block\",\n                                local_addr,\n                                send_info.to\n                            );\n                            break;\n                        }\n\n                        return Err(ClientError::Other(format!(\n                            \"{} -> {}: send() failed: {:?}\",\n                            local_addr, send_info.to, e\n                        )));\n                    }\n                }\n            }\n        }\n\n        if conn.is_closed() {\n            log::info!(\n                \"connection closed, {:?} {:?}\",\n                conn.stats(),\n                conn.path_stats().collect::<Vec<quiche::PathStats>>()\n            );\n\n            if !conn.is_established() {\n                log::info!(\n                    \"connection timed out after {:?}\",\n                    app_data_start.elapsed(),\n                );\n\n                return Err(ClientError::HandshakeFail);\n            }\n\n            break;\n        }\n    }\n\n    Ok(ConnectionSummary {\n        stream_map: client.streams,\n        stats: Some(conn.stats()),\n        path_stats: conn.path_stats().collect(),\n        conn_close_details: ConnectionCloseDetails::new(&conn),\n    })\n}\n\nfn check_duration_and_do_actions(\n    wait_duration: &mut Option<Duration>, wait_instant: &mut Option<Instant>,\n    action_iter: &mut Iter<Action>, conn: &mut quiche::Connection,\n    waiting_for: &mut WaitingFor, stream_parsers: &mut StreamParserMap,\n) {\n    match wait_duration.as_ref() {\n        None => {\n            if let Some(idle_wait) =\n                handle_actions(action_iter, conn, waiting_for, stream_parsers)\n            {\n                *wait_duration = Some(idle_wait);\n                *wait_instant = Some(Instant::now());\n\n                // TODO: the wait period could still be larger than the\n                // negotiated idle timeout.\n                // We could in theory check quiche's idle_timeout value if\n                // it was public.\n                log::info!(\n                    \"waiting for {idle_wait:?} before executing more actions\"\n                );\n            }\n        },\n\n        Some(period) => {\n            let now = Instant::now();\n            let then = wait_instant.unwrap();\n            log::debug!(\n                \"checking if actions wait period elapsed {:?} > {:?}\",\n                now.duration_since(then),\n                wait_duration\n            );\n            if now.duration_since(then) >= *period {\n                log::debug!(\"yup!\");\n                *wait_duration = None;\n\n                if let Some(idle_wait) =\n                    handle_actions(action_iter, conn, waiting_for, stream_parsers)\n                {\n                    *wait_duration = Some(idle_wait);\n                }\n            }\n        },\n    }\n}\n\n/// Generate a new pair of Source Connection ID and reset token.\npub fn generate_cid_and_reset_token() -> (quiche::ConnectionId<'static>, u128) {\n    let rng = SystemRandom::new();\n\n    let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n    rng.fill(&mut scid[..]).unwrap();\n    let scid = scid.to_vec().into();\n\n    let mut reset_token = [0; 16];\n    rng.fill(&mut reset_token[..]).unwrap();\n\n    let reset_token = u128::from_be_bytes(reset_token);\n    (scid, reset_token)\n}\n\nfn handle_actions<'a, I>(\n    iter: &mut I, conn: &mut quiche::Connection, waiting_for: &mut WaitingFor,\n    stream_parsers: &mut StreamParserMap,\n) -> Option<Duration>\nwhere\n    I: Iterator<Item = &'a Action>,\n{\n    if !waiting_for.is_empty() {\n        log::debug!(\n            \"won't fire an action due to waiting for responses: {waiting_for:?}\"\n        );\n        return None;\n    }\n\n    // Send actions\n    for action in iter {\n        match action {\n            Action::FlushPackets => return None,\n            Action::Wait { wait_type } => match wait_type {\n                WaitType::WaitDuration(period) => return Some(*period),\n                WaitType::StreamEvent(response) => {\n                    log::info!(\n                        \"waiting for {response:?} before executing more actions\"\n                    );\n                    waiting_for.add_wait(response);\n                    return None;\n                },\n                WaitType::CanOpenNumStreams(required_streams) => {\n                    log::info!(\n                        \"h3i: waiting for peer_streams_left_bidi >= {required_streams:?}\"\n                    );\n                    waiting_for.set_required_stream_quota(*required_streams);\n                    return None;\n                },\n            },\n            action => execute_action(action, conn, stream_parsers),\n        }\n    }\n\n    None\n}\n"
  },
  {
    "path": "h3i/src/config.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Configuration for the h3i client.\nuse std::io;\n\n/// Server details and QUIC connection properties.\n#[derive(Clone)]\npub struct Config {\n    /// A string representing the host and port to connect to using the format\n    /// `<host>:<port>`.\n    pub host_port: String,\n    /// If the SNI should be omitted during the TLS handshake.\n    pub omit_sni: bool,\n    /// Set a specific IP address to connect to, rather than use DNS resolution.\n    pub connect_to: Option<String>,\n    /// The source port to use when connecting to a server.\n    pub source_port: u32,\n    /// Whether to verify the server certificate.\n    pub verify_peer: bool,\n    /// The QUIC idle timeout value in milliseconds.\n    pub idle_timeout: u64,\n    /// Flow control limit for the connection in bytes\n    pub max_data: u64,\n    /// Flow control limit for locally-initiated bidirectional streams in bytes.\n    pub max_stream_data_bidi_local: u64,\n    /// Flow control limit for remotely-initiated bidirectional streams in\n    /// bytes.\n    pub max_stream_data_bidi_remote: u64,\n    /// Flow control limit for unidirectional streams in bytes.\n    pub max_stream_data_uni: u64,\n    /// Maximum count for concurrent remotely-initiated bidirectional streams.\n    pub max_streams_bidi: u64,\n    /// \"Maximum count for concurrent remotely-initiated unidirectional\n    /// streams\".\n    pub max_streams_uni: u64,\n    /// Receiver window limit for the connection in bytes.\n    pub max_window: u64,\n    /// Receiver window limit for a stream in bytes.\n    pub max_stream_window: u64,\n    /// Set the session to attempt resumption.\n    pub session: Option<Vec<u8>>,\n    /// Enables sending or receiving early data.\n    pub enable_early_data: bool,\n    /// Whether to enable datagram sending.\n    pub enable_dgram: bool,\n    /// Datagram receive queue length.\n    pub dgram_recv_queue_len: usize,\n    /// Datagram send queue length.\n    pub dgram_send_queue_len: usize,\n}\n\nimpl Config {\n    /// Construct a new config object with default values.\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn with_host_port(mut self, host_port: String) -> Self {\n        self.host_port = host_port;\n        self\n    }\n\n    pub fn omit_sni(mut self) -> Self {\n        self.omit_sni = true;\n        self\n    }\n\n    pub fn with_connect_to(mut self, connect_to: String) -> Self {\n        self.connect_to = Some(connect_to);\n        self\n    }\n\n    pub fn with_source_port(mut self, port: u32) -> Self {\n        self.source_port = port;\n        self\n    }\n\n    pub fn verify_peer(mut self, verify_peer: bool) -> Self {\n        self.verify_peer = verify_peer;\n        self\n    }\n\n    pub fn with_idle_timeout(mut self, idle_timeout: u64) -> Self {\n        self.idle_timeout = idle_timeout;\n        self\n    }\n\n    pub fn with_max_data(mut self, max_data: u64) -> Self {\n        self.max_data = max_data;\n        self\n    }\n\n    pub fn with_max_stream_data_bidi_local(\n        mut self, max_stream_data_bidi_local: u64,\n    ) -> Self {\n        self.max_stream_data_bidi_local = max_stream_data_bidi_local;\n        self\n    }\n\n    pub fn with_max_stream_data_bidi_remote(\n        mut self, max_stream_data_bidi_remote: u64,\n    ) -> Self {\n        self.max_stream_data_bidi_remote = max_stream_data_bidi_remote;\n        self\n    }\n\n    pub fn with_max_stream_data_uni(mut self, max_stream_data_uni: u64) -> Self {\n        self.max_stream_data_uni = max_stream_data_uni;\n        self\n    }\n\n    pub fn with_max_streams_bidi(mut self, max_streams_bidi: u64) -> Self {\n        self.max_streams_bidi = max_streams_bidi;\n        self\n    }\n\n    pub fn with_max_streams_uni(mut self, max_streams_uni: u64) -> Self {\n        self.max_streams_uni = max_streams_uni;\n        self\n    }\n\n    pub fn with_max_window(mut self, max_window: u64) -> Self {\n        self.max_window = max_window;\n        self\n    }\n\n    pub fn with_max_stream_window(mut self, max_stream_window: u64) -> Self {\n        self.max_stream_window = max_stream_window;\n        self\n    }\n\n    pub fn enable_dgram(mut self, enable_dgram: bool) -> Self {\n        self.enable_dgram = enable_dgram;\n        self\n    }\n\n    pub fn with_dgram_recv_queue_len(\n        mut self, dgram_recv_queue_len: usize,\n    ) -> Self {\n        self.dgram_recv_queue_len = dgram_recv_queue_len;\n        self\n    }\n\n    pub fn with_dgram_send_queue_len(\n        mut self, dgram_send_queue_len: usize,\n    ) -> Self {\n        self.dgram_send_queue_len = dgram_send_queue_len;\n        self\n    }\n\n    pub fn build(self) -> Result<Self, io::Error> {\n        if self.host_port.is_empty() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"Must provide a <host:port> to connect\".to_string(),\n            ));\n        }\n\n        Ok(Config {\n            host_port: self.host_port,\n            omit_sni: self.omit_sni,\n            connect_to: self.connect_to,\n            source_port: self.source_port,\n            verify_peer: self.verify_peer,\n            idle_timeout: self.idle_timeout,\n            max_data: self.max_data,\n            max_stream_data_bidi_local: self.max_stream_data_bidi_local,\n            max_stream_data_bidi_remote: self.max_stream_data_bidi_remote,\n            max_stream_data_uni: self.max_stream_data_uni,\n            max_streams_bidi: self.max_streams_bidi,\n            max_streams_uni: self.max_streams_uni,\n            max_window: self.max_window,\n            max_stream_window: self.max_stream_window,\n            session: None,\n            enable_early_data: self.enable_early_data,\n            enable_dgram: self.enable_dgram,\n            dgram_recv_queue_len: self.dgram_recv_queue_len,\n            dgram_send_queue_len: self.dgram_send_queue_len,\n        })\n    }\n}\n\nimpl Default for Config {\n    fn default() -> Self {\n        // Values mirror config_from_clap()\n        Self {\n            host_port: \"\".to_string(),\n            omit_sni: false,\n            connect_to: None,\n            source_port: 0,\n            verify_peer: true,\n            idle_timeout: 5000,\n            max_data: 10000000,\n            max_stream_data_bidi_local: 10000000,\n            max_stream_data_bidi_remote: 10000000,\n            max_stream_data_uni: 10000000,\n            max_streams_bidi: 100,\n            max_streams_uni: 100,\n            max_window: 25165824,\n            max_stream_window: 16777216,\n            session: None,\n            enable_early_data: false,\n            enable_dgram: true,\n            dgram_recv_queue_len: 65536,\n            dgram_send_queue_len: 65536,\n        }\n    }\n}\n"
  },
  {
    "path": "h3i/src/frame.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Helpers for dealing with quiche stream events and HTTP/3 frames.\n\nuse std::cmp;\nuse std::convert::TryFrom;\nuse std::error::Error;\nuse std::fmt::Debug;\nuse std::sync::Arc;\n\nuse crate::quiche;\nuse multimap::MultiMap;\n\nuse quiche::h3::frame::Frame as QFrame;\nuse quiche::h3::Header;\nuse quiche::h3::NameValue;\nuse serde::ser::SerializeStruct;\nuse serde::ser::Serializer;\nuse serde::Serialize;\n\nuse crate::client::connection_summary::MAX_SERIALIZED_BUFFER_LEN;\nuse crate::encode_header_block;\n\npub type BoxError = Box<dyn Error + Send + Sync + 'static>;\n\n/// An internal representation of a QUIC or HTTP/3 frame. This type exists so\n/// that we can extend types defined in Quiche.\n#[derive(Debug, Eq, PartialEq, Clone)]\npub enum H3iFrame {\n    /// A wrapper around a quiche HTTP/3 frame.\n    QuicheH3(QFrame),\n    /// A wrapper around an [EnrichedHeaders] struct.\n    Headers(EnrichedHeaders),\n    /// A wrapper around a [ResetStream] struct\n    ResetStream(ResetStream),\n}\n\nimpl H3iFrame {\n    /// Try to convert this `H3iFrame` to an [EnrichedHeaders].\n    ///\n    /// Returns `Some` if the operation succeeded.\n    pub fn to_enriched_headers(&self) -> Option<EnrichedHeaders> {\n        if let H3iFrame::Headers(header) = self {\n            Some(header.clone())\n        } else {\n            None\n        }\n    }\n}\n\nimpl Serialize for H3iFrame {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        match self {\n            H3iFrame::QuicheH3(frame) => {\n                let mut state = s.serialize_struct(\"frame\", 1)?;\n                let name = frame_name(frame);\n                state.serialize_field(name, &SerializableQFrame(frame))?;\n                state.end()\n            },\n            H3iFrame::Headers(headers) => {\n                let mut state = s.serialize_struct(\"enriched_headers\", 1)?;\n                state.serialize_field(\"enriched_headers\", headers)?;\n                state.end()\n            },\n            H3iFrame::ResetStream(reset) => {\n                let mut state = s.serialize_struct(\"reset_stream\", 1)?;\n                state.serialize_field(\"reset_stream\", reset)?;\n                state.end()\n            },\n        }\n    }\n}\n\nimpl From<QFrame> for H3iFrame {\n    fn from(value: QFrame) -> Self {\n        Self::QuicheH3(value)\n    }\n}\n\nimpl From<Vec<Header>> for H3iFrame {\n    fn from(value: Vec<Header>) -> Self {\n        Self::Headers(EnrichedHeaders::from(value))\n    }\n}\n\npub type HeaderMap = MultiMap<Vec<u8>, Vec<u8>>;\n\n/// An HTTP/3 HEADERS frame with decoded headers and a [HeaderMap].\n#[derive(Clone, PartialEq, Eq)]\npub struct EnrichedHeaders {\n    header_block: Vec<u8>,\n    headers: Vec<Header>,\n    /// A multi-map of raw header names to values, similar to http's HeaderMap.\n    header_map: HeaderMap,\n}\n\n/// A wrapper to help serialize an quiche HTTP header.\npub struct SerializableHeader<'a>(&'a Header);\n\nimpl Serialize for SerializableHeader<'_> {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut state = s.serialize_struct(\"header\", 2)?;\n        state.serialize_field(\"name\", &String::from_utf8_lossy(self.0.name()))?;\n        state\n            .serialize_field(\"value\", &String::from_utf8_lossy(self.0.value()))?;\n        state.end()\n    }\n}\n\nimpl EnrichedHeaders {\n    /// Return the array of headers in this frame.\n    ///\n    /// # Examples\n    /// ```\n    /// use h3i::frame::EnrichedHeaders;\n    /// use quiche::h3::Header;\n    ///\n    /// let raw = vec![\n    ///     Header::new(b\"new jersey\", b\"devils\"),\n    ///     Header::new(b\"new york\", b\"jets\"),\n    /// ];\n    /// let headers = EnrichedHeaders::from(raw.clone());\n    /// assert_eq!(headers.headers(), raw);\n    /// ```\n    pub fn headers(&self) -> &[Header] {\n        &self.headers\n    }\n\n    /// Returns a multi-map of header keys to values.\n    ///\n    /// If a single key contains multiple values, the values in the entry will\n    /// be returned in the same order as they appear in the array of headers\n    /// which backs the [`EnrichedHeaders`].\n    ///\n    /// # Examples\n    /// ```\n    /// use h3i::frame::EnrichedHeaders;\n    /// use h3i::frame::H3iFrame;\n    /// use multimap::MultiMap;\n    /// use quiche::h3::Header;\n    /// use std::iter::FromIterator;\n    ///\n    /// let header_frame = vec![\n    ///     Header::new(b\":status\", b\"200\"),\n    ///     Header::new(b\"hello\", b\"world\"),\n    ///     Header::new(b\"hello\", b\"super-earth\"),\n    /// ];\n    ///\n    /// let enriched = H3iFrame::Headers(header_frame.into())\n    ///     .to_enriched_headers()\n    ///     .unwrap();\n    ///\n    /// let expected = MultiMap::from_iter([\n    ///     (b\":status\".to_vec(), vec![b\"200\".to_vec()]),\n    ///     (b\"hello\".to_vec(), vec![\n    ///         b\"world\".to_vec(),\n    ///         b\"super-earth\".to_vec(),\n    ///     ]),\n    /// ]);\n    ///\n    /// assert_eq!(*enriched.header_map(), expected);\n    /// ```\n    pub fn header_map(&self) -> &HeaderMap {\n        &self.header_map\n    }\n\n    /// Fetches the value of the `:status` pseudo-header.\n    ///\n    /// # Examples\n    /// ```\n    /// use h3i::frame::EnrichedHeaders;\n    /// use quiche::h3::Header;\n    ///\n    /// let headers = EnrichedHeaders::from(vec![Header::new(b\"hello\", b\"world\")]);\n    /// assert!(headers.status_code().is_none());\n    ///\n    /// let headers = EnrichedHeaders::from(vec![Header::new(b\":status\", b\"200\")]);\n    /// assert_eq!(headers.status_code().expect(\"status code is Some\"), b\"200\");\n    /// ```\n    pub fn status_code(&self) -> Option<&Vec<u8>> {\n        self.header_map.get(b\":status\".as_slice())\n    }\n}\n\nimpl Serialize for EnrichedHeaders {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut state = s.serialize_struct(\"enriched_headers\", 2)?;\n        state.serialize_field(\"header_block_len\", &self.header_block.len())?;\n        let x: Vec<SerializableHeader> =\n            self.headers.iter().map(SerializableHeader).collect();\n        state.serialize_field(\"headers\", &x)?;\n        state.end()\n    }\n}\n\nimpl From<Vec<Header>> for EnrichedHeaders {\n    fn from(headers: Vec<Header>) -> Self {\n        let header_block = encode_header_block(&headers).unwrap();\n\n        let mut header_map: HeaderMap = MultiMap::with_capacity(headers.len());\n        for header in headers.iter() {\n            header_map.insert(header.name().to_vec(), header.value().to_vec());\n        }\n\n        Self {\n            header_block,\n            headers,\n            header_map,\n        }\n    }\n}\n\nimpl TryFrom<QFrame> for EnrichedHeaders {\n    type Error = BoxError;\n\n    fn try_from(value: QFrame) -> Result<Self, Self::Error> {\n        match value {\n            QFrame::Headers { header_block } => {\n                let mut qpack_decoder = quiche::h3::qpack::Decoder::new();\n                let headers =\n                    qpack_decoder.decode(&header_block, u64::MAX).unwrap();\n\n                Ok(EnrichedHeaders::from(headers))\n            },\n            _ => Err(\"Cannot convert non-Headers frame into HeadersFrame\".into()),\n        }\n    }\n}\n\nimpl Debug for EnrichedHeaders {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{:?}\", self.headers)\n    }\n}\n\n/// A `RESET_STREAM` frame.\n///\n/// See [RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000#name-reset_stream-frames) for\n/// more.\n#[derive(Debug, Clone, Eq, PartialEq, Serialize)]\npub struct ResetStream {\n    /// The stream ID over which the RESET_STREAM frame was sent.\n    pub stream_id: u64,\n    /// The error code sent from the peer.\n    pub error_code: u64,\n}\n\nfn frame_name(frame: &QFrame) -> &'static str {\n    match frame {\n        QFrame::Data { .. } => \"DATA\",\n        QFrame::Headers { .. } => \"HEADERS\",\n        QFrame::CancelPush { .. } => \"CANCEL_PUSH\",\n        QFrame::Settings { .. } => \"SETTINGS\",\n        QFrame::PushPromise { .. } => \"PUSH_PROMISE\",\n        QFrame::GoAway { .. } => \"GO_AWAY\",\n        QFrame::MaxPushId { .. } => \"MAX_PUSH_ID\",\n        QFrame::PriorityUpdateRequest { .. } => \"PRIORITY_UPDATE(REQUEST)\",\n        QFrame::PriorityUpdatePush { .. } => \"PRIORITY_UPDATE(PUSH)\",\n        QFrame::Unknown { .. } => \"UNKNOWN\",\n    }\n}\n\n/// A wrapper to help serialize a quiche HTTP/3 frame.\npub struct SerializableQFrame<'a>(&'a QFrame);\n\nimpl Serialize for SerializableQFrame<'_> {\n    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let name = frame_name(self.0);\n        match self.0 {\n            QFrame::Data { payload } => {\n                let mut state = s.serialize_struct(name, 2)?;\n                let max = cmp::min(payload.len(), MAX_SERIALIZED_BUFFER_LEN);\n                state.serialize_field(\"payload_len\", &payload.len())?;\n                state.serialize_field(\n                    \"payload\",\n                    &qlog::HexSlice::maybe_string(Some(&payload[..max])),\n                )?;\n                state.end()\n            },\n\n            QFrame::Headers { header_block } => {\n                let mut state = s.serialize_struct(name, 1)?;\n                state.serialize_field(\"header_block_len\", &header_block.len())?;\n                state.end()\n            },\n\n            QFrame::CancelPush { push_id } => {\n                let mut state = s.serialize_struct(name, 1)?;\n                state.serialize_field(\"push_id\", &push_id)?;\n                state.end()\n            },\n\n            QFrame::Settings {\n                max_field_section_size,\n                qpack_max_table_capacity,\n                qpack_blocked_streams,\n                connect_protocol_enabled,\n                h3_datagram,\n                grease: _,\n                additional_settings,\n                raw: _,\n            } => {\n                let mut state = s.serialize_struct(name, 6)?;\n                state.serialize_field(\n                    \"max_field_section_size\",\n                    &max_field_section_size,\n                )?;\n                state.serialize_field(\n                    \"qpack_max_table_capacity\",\n                    &qpack_max_table_capacity,\n                )?;\n                state.serialize_field(\n                    \"qpack_blocked_streams\",\n                    &qpack_blocked_streams,\n                )?;\n                state.serialize_field(\n                    \"connect_protocol_enabled\",\n                    &connect_protocol_enabled,\n                )?;\n                state.serialize_field(\"h3_datagram\", &h3_datagram)?;\n                state.serialize_field(\n                    \"additional_settings\",\n                    &additional_settings,\n                )?;\n                state.end()\n            },\n\n            QFrame::PushPromise {\n                push_id,\n                header_block,\n            } => {\n                let mut state = s.serialize_struct(name, 2)?;\n                state.serialize_field(\"push_id\", &push_id)?;\n                state.serialize_field(\"header_block_len\", &header_block.len())?;\n                state.end()\n            },\n\n            QFrame::GoAway { id } => {\n                let mut state = s.serialize_struct(name, 1)?;\n                state.serialize_field(\"id\", &id)?;\n                state.end()\n            },\n\n            QFrame::MaxPushId { push_id } => {\n                let mut state = s.serialize_struct(name, 1)?;\n                state.serialize_field(\"push_id\", &push_id)?;\n                state.end()\n            },\n\n            QFrame::PriorityUpdateRequest {\n                prioritized_element_id,\n                priority_field_value,\n            } => {\n                let mut state = s.serialize_struct(name, 3)?;\n                state.serialize_field(\n                    \"prioritized_element_id\",\n                    &prioritized_element_id,\n                )?;\n\n                let max = cmp::min(\n                    priority_field_value.len(),\n                    MAX_SERIALIZED_BUFFER_LEN,\n                );\n                state.serialize_field(\n                    \"priority_field_value_len\",\n                    &priority_field_value.len(),\n                )?;\n                state.serialize_field(\n                    \"priority_field_value\",\n                    &String::from_utf8_lossy(&priority_field_value[..max]),\n                )?;\n                state.end()\n            },\n\n            QFrame::PriorityUpdatePush {\n                prioritized_element_id,\n                priority_field_value,\n            } => {\n                let mut state = s.serialize_struct(name, 3)?;\n                state.serialize_field(\n                    \"prioritized_element_id\",\n                    &prioritized_element_id,\n                )?;\n                let max = cmp::min(\n                    priority_field_value.len(),\n                    MAX_SERIALIZED_BUFFER_LEN,\n                );\n                state.serialize_field(\n                    \"priority_field_value_len\",\n                    &priority_field_value.len(),\n                )?;\n                state.serialize_field(\n                    \"priority_field_value\",\n                    &String::from_utf8_lossy(&priority_field_value[..max]),\n                )?;\n                state.end()\n            },\n\n            QFrame::Unknown { raw_type, payload } => {\n                let mut state = s.serialize_struct(name, 3)?;\n                state.serialize_field(\"raw_type\", &raw_type)?;\n                let max = cmp::min(payload.len(), MAX_SERIALIZED_BUFFER_LEN);\n                state.serialize_field(\"payload_len\", &payload.len())?;\n                state.serialize_field(\n                    \"payload\",\n                    &qlog::HexSlice::maybe_string(Some(&payload[..max])),\n                )?;\n                state.end()\n            },\n        }\n    }\n}\n\ntype CustomEquivalenceHandler =\n    Box<dyn for<'f> Fn(&'f H3iFrame) -> bool + Send + Sync + 'static>;\n\n#[derive(Clone)]\nenum Comparator {\n    Frame(H3iFrame),\n    /// Specifies how to compare an incoming [`H3iFrame`] with this\n    /// [`CloseTriggerFrame`]. Typically, the validation attempts to fuzzy-match\n    /// the [`CloseTriggerFrame`] against the incoming [`H3iFrame`], but there\n    /// are times where other behavior is desired (for example, checking\n    /// deserialized JSON payloads in a headers frame, or ensuring a random\n    /// value matches a regex).\n    ///\n    /// See [`CloseTriggerFrame::is_equivalent`] for more on how frames are\n    /// compared.\n    Fn(Arc<CustomEquivalenceHandler>),\n}\n\nimpl Serialize for Comparator {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        match self {\n            Self::Fn(_) => serializer.serialize_str(\"<comparator_fn>\"),\n            Self::Frame(f) => {\n                let mut frame_ser = serializer.serialize_struct(\"frame\", 1)?;\n                frame_ser.serialize_field(\"frame\", f)?;\n                frame_ser.end()\n            },\n        }\n    }\n}\n\n/// Instructs h3i to watch for certain incoming [`H3iFrame`]s. The incoming\n/// frames can either be supplied directly via [`CloseTriggerFrame::new`], or\n/// via a verification callback  passed to\n/// [`CloseTriggerFrame::new_with_comparator`].\n#[derive(Serialize, Clone)]\npub struct CloseTriggerFrame {\n    stream_id: u64,\n    comparator: Comparator,\n}\n\nimpl CloseTriggerFrame {\n    /// Create a new [`CloseTriggerFrame`] which should watch for the provided\n    /// [`H3iFrame`].\n    ///\n    /// # Note\n    ///\n    /// For [QuicheH3] and [ResetStream] variants, equivalence is the same as\n    /// equality.\n    ///\n    /// For Headers variants, this [`CloseTriggerFrame`] is equivalent to the\n    /// incoming [`H3iFrame`] if the [`H3iFrame`] contains all [`Header`]s\n    /// in _this_ frame. In other words, `this` can be considered equivalent\n    /// to `other` if `other` contains a superset of `this`'s [`Header`]s.\n    ///\n    /// This allows users for fuzzy-matching on header frames without needing to\n    /// supply every individual header on the frame.\n    ///\n    /// [ResetStream]: H3iFrame::ResetStream\n    /// [QuicheH3]: H3iFrame::QuicheH3\n    pub fn new(stream_id: u64, frame: impl Into<H3iFrame>) -> Self {\n        Self {\n            stream_id,\n            comparator: Comparator::Frame(frame.into()),\n        }\n    }\n\n    /// Create a new [`CloseTriggerFrame`] which will match incoming\n    /// [`H3iFrame`]s according to the passed `comparator_fn`.\n    ///\n    /// The `comparator_fn` will be called with every incoming [`H3iFrame`]. It\n    /// should return `true` if the incoming frame is expected, and `false`\n    /// if it is not.\n    pub fn new_with_comparator<F>(stream_id: u64, comparator_fn: F) -> Self\n    where\n        F: Fn(&H3iFrame) -> bool + Send + Sync + 'static,\n    {\n        Self {\n            stream_id,\n            comparator: Comparator::Fn(Arc::new(Box::new(comparator_fn))),\n        }\n    }\n\n    pub(crate) fn stream_id(&self) -> u64 {\n        self.stream_id\n    }\n\n    pub(crate) fn is_equivalent(&self, other: &H3iFrame) -> bool {\n        let frame = match &self.comparator {\n            Comparator::Fn(compare) => return compare(other),\n            Comparator::Frame(frame) => frame,\n        };\n\n        match frame {\n            H3iFrame::Headers(me) => {\n                let H3iFrame::Headers(other) = other else {\n                    return false;\n                };\n\n                // TODO(evanrittenhouse): we could theoretically hand-roll a\n                // MultiMap which uses a HashSet as the\n                // multi-value collection, but in practice we don't expect very\n                // many headers on an CloseTriggerFrame\n                //\n                // ref: https://docs.rs/multimap/latest/src/multimap/lib.rs.html#89\n                me.headers().iter().all(|m| other.headers().contains(m))\n            },\n            H3iFrame::QuicheH3(me) => match other {\n                H3iFrame::QuicheH3(other) => me == other,\n                _ => false,\n            },\n            H3iFrame::ResetStream(me) => match other {\n                H3iFrame::ResetStream(rs) => me == rs,\n                _ => false,\n            },\n        }\n    }\n}\n\nimpl Debug for CloseTriggerFrame {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let repr = match &self.comparator {\n            Comparator::Frame(frame) => format!(\"{frame:?}\"),\n            Comparator::Fn(_) => \"closure\".to_string(),\n        };\n\n        write!(\n            f,\n            \"CloseTriggerFrame {{ stream_id: {}, comparator: {repr} }}\",\n            self.stream_id\n        )\n    }\n}\n\nimpl PartialEq for CloseTriggerFrame {\n    fn eq(&self, other: &Self) -> bool {\n        match (&self.comparator, &other.comparator) {\n            (Comparator::Frame(this_frame), Comparator::Frame(other_frame)) =>\n                self.stream_id == other.stream_id && this_frame == other_frame,\n            _ => false,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quiche::h3::frame::Frame;\n\n    #[test]\n    fn test_header_equivalence() {\n        let this = CloseTriggerFrame::new(0, vec![\n            Header::new(b\"hello\", b\"world\"),\n            Header::new(b\"go\", b\"jets\"),\n        ]);\n        let other: H3iFrame = vec![\n            Header::new(b\"hello\", b\"world\"),\n            Header::new(b\"go\", b\"jets\"),\n            Header::new(b\"go\", b\"devils\"),\n        ]\n        .into();\n\n        assert!(this.is_equivalent(&other));\n    }\n\n    #[test]\n    fn test_header_non_equivalence() {\n        let this = CloseTriggerFrame::new(0, vec![\n            Header::new(b\"hello\", b\"world\"),\n            Header::new(b\"go\", b\"jets\"),\n            Header::new(b\"go\", b\"devils\"),\n        ]);\n        let other: H3iFrame =\n            vec![Header::new(b\"hello\", b\"world\"), Header::new(b\"go\", b\"jets\")]\n                .into();\n\n        // `other` does not contain the `go: devils` header, so it's not\n        // equivalent to `this.\n        assert!(!this.is_equivalent(&other));\n    }\n\n    #[test]\n    fn test_rst_stream_equivalence() {\n        let mut rs = ResetStream {\n            stream_id: 0,\n            error_code: 57,\n        };\n\n        let this = CloseTriggerFrame::new(0, H3iFrame::ResetStream(rs.clone()));\n        let incoming = H3iFrame::ResetStream(rs.clone());\n        assert!(this.is_equivalent(&incoming));\n\n        rs.stream_id = 57;\n        let incoming = H3iFrame::ResetStream(rs);\n        assert!(!this.is_equivalent(&incoming));\n    }\n\n    #[test]\n    fn test_frame_equivalence() {\n        let mut d = Frame::Data {\n            payload: b\"57\".to_vec(),\n        };\n\n        let this = CloseTriggerFrame::new(0, H3iFrame::QuicheH3(d.clone()));\n        let incoming = H3iFrame::QuicheH3(d.clone());\n        assert!(this.is_equivalent(&incoming));\n\n        d = Frame::Data {\n            payload: b\"go jets\".to_vec(),\n        };\n        let incoming = H3iFrame::QuicheH3(d.clone());\n        assert!(!this.is_equivalent(&incoming));\n    }\n\n    #[test]\n    fn test_comparator() {\n        let this = CloseTriggerFrame::new_with_comparator(0, |frame| {\n            if let H3iFrame::Headers(..) = frame {\n                frame\n                    .to_enriched_headers()\n                    .unwrap()\n                    .header_map()\n                    .get(&b\"cookie\".to_vec())\n                    .is_some_and(|v| {\n                        std::str::from_utf8(v)\n                            .map(|s| s.to_lowercase())\n                            .unwrap()\n                            .contains(\"cookie\")\n                    })\n            } else {\n                false\n            }\n        });\n\n        let incoming: H3iFrame =\n            vec![Header::new(b\"cookie\", b\"SomeRandomCookie1234\")].into();\n\n        assert!(this.is_equivalent(&incoming));\n    }\n}\n"
  },
  {
    "path": "h3i/src/frame_parser.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Stateful parsing of QUIC streams into HTTP/3 frames.\n\nuse crate::quiche;\nuse quiche::h3::frame::Frame as QFrame;\nuse quiche::h3::Error as H3Error;\nuse quiche::h3::Result;\n\nuse crate::frame::H3iFrame;\n\n/// Max stream state size in bytes (2MB).\nconst MAX_STREAM_STATE_SIZE: usize = 2_000_000;\n\n#[derive(Debug, Default, PartialEq, Eq)]\nenum FrameState {\n    #[default]\n    Type,\n    Len,\n    Val,\n}\n\n#[derive(Debug, Eq, PartialEq)]\n/// The reason that frame parsing was interrupted.\npub enum InterruptCause {\n    FinBit,\n    ResetStream(u64),\n}\n\n#[derive(Debug, Eq, PartialEq)]\n/// Represents different frame parsing outcomes.\npub enum FrameParseResult {\n    /// The frame was unable to be parsed at the current moment. This signifies\n    /// that the stream is retryable without another I/O cycle. If another\n    /// I/O cycle is needed, a [`quiche::h3::Error::TransportError`]\n    /// containing [`quiche::Error::Done`] will be returned.\n    Retry,\n    /// A frame has been successfully parsed. `fin` denotes if the FIN bit was\n    /// set.\n    FrameParsed { h3i_frame: H3iFrame, fin: bool },\n    /// A frame is in the middle of being parsed, but either a FIN bit or a\n    /// RESET_STREAM was received.\n    Interrupted(InterruptCause),\n}\n\n/// Parses [`H3iFrame`]s from a QUIC stream.\n///\n/// Each `FrameParser` instance is bound to a single stream when created.\n/// [`FrameParser::try_parse_frame()`] will attempt to pull stream data from a\n/// [`quiche::Connection`] and build a complete frame.\n///\n/// There are various success and failure criteria, see `try_parse_frame()` for\n/// specific guidance.\npub(crate) struct FrameParser {\n    ty: Option<u64>,\n    len: Option<u64>,\n\n    stream_id: u64,\n\n    curr_state: FrameState,\n    state_buf: Vec<u8>,\n    state_offset: usize,\n    state_len: usize,\n}\n\nimpl FrameParser {\n    pub(crate) fn new(stream_id: u64) -> Self {\n        Self {\n            stream_id,\n            ..Default::default()\n        }\n    }\n\n    /// Attempt to pull stream data from a [`quiche::Connection`] and build a\n    /// complete frame.\n    ///\n    /// On success, [FrameParseResult::FrameParsed] is returned. The caller\n    /// should keep calling try_parse_frame() to read a series of frames\n    /// from the stream.\n    ///\n    /// [FrameParseResult::Retry] signifies that the parser's internal state\n    /// requires another attempt to read stream data, but the stream is\n    /// still readable. `try_parse_frame()` should be retried without\n    /// executing another I/O cycle.\n    ///\n    /// If the available stream data does not provide a complete frame, a\n    /// [`quiche::h3::Error::TransportError`] containing [`quiche::Error::Done`]\n    /// is returned. Callers should execute an I/O cycle before calling\n    /// try_parse_frame() again.\n    ///\n    /// If the stream is terminated, either by FIN or reset,\n    /// [FrameParseResult::Interrupted] is returned. The caller should cease\n    /// calling methods on the stream since the stream is closed.\n    pub(crate) fn try_parse_frame<F: quiche::BufFactory>(\n        &mut self, qconn: &mut quiche::Connection<F>,\n    ) -> Result<FrameParseResult> {\n        loop {\n            let (len, fin) = match self.try_fill_buffer(qconn, self.stream_id) {\n                Ok((l, f)) => (l, f),\n                Err(H3Error::TransportError(quiche::Error::StreamReset(err))) =>\n                    return Ok(FrameParseResult::Interrupted(\n                        InterruptCause::ResetStream(err),\n                    )),\n                Err(e) => return Err(e),\n            };\n\n            log::trace!(\n                \"{} stream={} read bytes={len:?}\",\n                qconn.trace_id(),\n                self.stream_id\n            );\n\n            if fin && self.frame_incomplete() {\n                return Ok(FrameParseResult::Interrupted(InterruptCause::FinBit));\n            };\n\n            match self.curr_state {\n                FrameState::Type => {\n                    let Ok(varint) = self.try_consume_varint() else {\n                        // Map Error::Done's to Retry's because state_buf must be\n                        // resized to fit a larger varint\n                        return Ok(FrameParseResult::Retry);\n                    };\n\n                    self.set_frame_type(varint)?;\n                    self.state_transition(FrameState::Len, 1)?;\n                },\n                FrameState::Len => {\n                    let Ok(varint) = self.try_consume_varint() else {\n                        // Map Error::Done's to Retry's because state_buf must be\n                        // resized to fit a larger varint\n                        return Ok(FrameParseResult::Retry);\n                    };\n\n                    self.set_frame_len(varint)?;\n                    self.state_transition(\n                        FrameState::Val,\n                        self.len.expect(\"frame len is not set\") as usize,\n                    )?;\n                },\n                FrameState::Val => {\n                    if self.state_buffer_complete() {\n                        let h3i_frame = self.build_h3i_frame()?;\n                        // unwraps are safe now\n                        let ty = match self.ty.unwrap() {\n                            0x0 => \"DATA\".to_string(),\n                            0x1 => \"HEADERS\".to_string(),\n                            0x3 => \"CANCEL_PUSH\".to_string(),\n                            0x4 => \"SETTINGS\".to_string(),\n                            0x5 => \"PUSH_PROMISE\".to_string(),\n                            0x7 => \"GOAWAY\".to_string(),\n                            0xd => \"MAX_PUSH_AWAY\".to_string(),\n                            _ => format!(\"UNKNOWN val={}\", self.ty.unwrap()),\n                        };\n                        log::info!(\n                            \"{} stream={} frame rx ty={} len={}\",\n                            qconn.trace_id(),\n                            self.stream_id,\n                            ty,\n                            self.len.unwrap()\n                        );\n\n                        // Reset the states for the next frame\n                        *self = Self::new(self.stream_id);\n                        return Ok(FrameParseResult::FrameParsed {\n                            h3i_frame,\n                            fin,\n                        });\n                    };\n\n                    // No need to map to Retry here since we've exhausted the\n                    // received bytes and must try another I/O\n                    // cycle\n                    return Err(H3Error::TransportError(quiche::Error::Done));\n                },\n            }\n        }\n    }\n\n    fn frame_incomplete(&self) -> bool {\n        !self.state_buf.is_empty() && !self.state_buffer_complete()\n    }\n\n    fn try_fill_buffer<F: quiche::BufFactory>(\n        &mut self, qconn: &mut quiche::Connection<F>, stream_id: u64,\n    ) -> Result<(usize, bool)> {\n        if self.state_buffer_complete() {\n            return Ok((0, qconn.stream_finished(stream_id)));\n        }\n\n        let buf = &mut self.state_buf[self.state_offset..self.state_len];\n        match qconn.stream_recv(stream_id, buf) {\n            Ok((len, fin)) => {\n                self.state_offset += len;\n                Ok((len, fin))\n            },\n            Err(e) => Err(H3Error::TransportError(e)),\n        }\n    }\n\n    fn try_consume_varint(&mut self) -> Result<u64> {\n        if self.state_offset == 1 {\n            self.state_len = octets::varint_parse_len(self.state_buf[0]);\n            self.state_buf.resize(self.state_len, 0);\n        }\n\n        if !self.state_buffer_complete() {\n            return Err(H3Error::TransportError(quiche::Error::Done));\n        }\n\n        let varint = octets::Octets::with_slice(&self.state_buf).get_varint()?;\n        Ok(varint)\n    }\n\n    fn state_buffer_complete(&self) -> bool {\n        self.state_offset == self.state_len\n    }\n\n    fn state_transition(\n        &mut self, new_state: FrameState, expected_len: usize,\n    ) -> Result<()> {\n        // A peer can influence the size of the state buffer (e.g. with the\n        // payload size of a GREASE frame), so we need to limit the maximum\n        // size to avoid DoS.\n        if expected_len > MAX_STREAM_STATE_SIZE {\n            return Err(quiche::h3::Error::ExcessiveLoad);\n        }\n\n        self.state_buf.resize(expected_len, 0);\n        self.curr_state = new_state;\n        self.state_offset = 0;\n        self.state_len = expected_len;\n\n        Ok(())\n    }\n\n    fn set_frame_type(&mut self, ty: u64) -> Result<()> {\n        self.ty = Some(ty);\n        self.state_transition(FrameState::Len, 1)?;\n\n        Ok(())\n    }\n\n    fn set_frame_len(&mut self, len: u64) -> Result<()> {\n        self.len = Some(len);\n        self.state_transition(FrameState::Val, len as usize)?;\n\n        Ok(())\n    }\n\n    fn build_h3i_frame(&mut self) -> Result<H3iFrame> {\n        let qframe = QFrame::from_bytes(\n            self.ty.expect(\"frame ty not set\"),\n            self.len.expect(\"frame len not set\"),\n            &self.state_buf,\n        )?;\n\n        match qframe {\n            QFrame::Headers { ref header_block } => {\n                let mut qpack_decoder = quiche::h3::qpack::Decoder::new();\n                let headers =\n                    qpack_decoder.decode(header_block, u64::MAX).unwrap();\n\n                Ok(H3iFrame::Headers(headers.into()))\n            },\n            _ => Ok(qframe.into()),\n        }\n    }\n}\n\nimpl std::fmt::Debug for FrameParser {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let s = format!(\n            \"FrameParser {{ stream: {}, type: {:?}, length: {:?} }}\",\n            self.stream_id, self.ty, self.len,\n        );\n\n        write!(f, \"{s}\")\n    }\n}\n\nimpl Default for FrameParser {\n    fn default() -> Self {\n        Self {\n            ty: None,\n            len: None,\n            curr_state: FrameState::default(),\n            stream_id: 0,\n            state_buf: vec![0],\n            state_offset: 0,\n            state_len: 1,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quiche::h3::frame::Frame;\n    use quiche::h3::testing::*;\n\n    fn session() -> Result<Session> {\n        Session::new()\n    }\n\n    // See https://datatracker.ietf.org/doc/html/rfc9000#name-variable-length-integer-enc for\n    // encoding scheme. 64 is the lowest number that can be parsed with a 2-byte\n    // varint, so it's used in all tests so they're easier to reason about\n    #[test]\n    fn simple_case() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::new(0);\n        let expected = Frame::Data {\n            payload: vec![1, 2, 3, 4, 5],\n        };\n        s.send_frame_client(expected.clone(), 0, true)\n            .expect(\"first\");\n\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn type_precedes_split() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::new(0);\n        let expected = Frame::Data {\n            payload: vec![10; 10],\n        };\n        s.send_arbitrary_stream_data_client(&[0], 0, false)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n\n        s.send_arbitrary_stream_data_client(&[10; 11], 0, true)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn type_multiple_bytes() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::new(0);\n        let expected = Frame::Unknown {\n            raw_type: 64,\n            payload: vec![1, 2, 3, 4, 5],\n        };\n        s.send_arbitrary_stream_data_client(&[64, 64, 5, 1, 2, 3, 4, 5], 0, true)\n            .expect(\"first\");\n\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::Retry);\n\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn type_multiple_buffers() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::new(0);\n        let expected = Frame::Unknown {\n            raw_type: 64,\n            payload: vec![1, 2, 3, 4, 5],\n        };\n\n        s.send_arbitrary_stream_data_client(&[64], 0, false)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::Retry);\n\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n\n        s.send_arbitrary_stream_data_client(&[64, 5, 1, 2, 3, 4, 5], 0, true)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn type_multiple_buffers_precedes_split() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let expected = Frame::Unknown {\n            raw_type: 64,\n            payload: vec![1, 2, 3, 4, 5],\n        };\n\n        s.send_arbitrary_stream_data_client(&[64, 64], 0, false)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::Retry);\n\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n\n        s.send_arbitrary_stream_data_client(&[5, 1, 2, 3, 4, 5], 0, false)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: false\n        });\n    }\n\n    #[test]\n    fn len_precedes_split() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let expected = Frame::Data {\n            payload: vec![57; 10],\n        };\n\n        s.send_arbitrary_stream_data_client(&[0, 10], 0, false)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n\n        s.send_arbitrary_stream_data_client(&[57; 10], 0, true)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn len_multiple_bytes() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut b = vec![0, 64, 64];\n        let mut payload = vec![0; 64];\n        let mut parser = FrameParser::default();\n        let expected = Frame::Data {\n            payload: payload.clone(),\n        };\n        b.append(&mut payload);\n\n        s.send_arbitrary_stream_data_client(&b, 0, true)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::Retry);\n\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn len_multiple_buffers() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let expected = Frame::Data {\n            payload: vec![64; 64],\n        };\n\n        s.send_arbitrary_stream_data_client(&[0, 64], 0, false)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::Retry);\n\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n\n        s.send_arbitrary_stream_data_client(&[64; 65], 0, true)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn len_multiple_buffers_precedes_split() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let expected = Frame::Data {\n            payload: vec![0; 64],\n        };\n\n        s.send_arbitrary_stream_data_client(&[0, 64, 64], 0, false)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::Retry);\n\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n\n        s.send_arbitrary_stream_data_client(&[0; 64], 0, true)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn no_val() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let first = Frame::Unknown {\n            raw_type: 64,\n            payload: vec![],\n        };\n        let second = Frame::Data {\n            payload: vec![1, 2, 3],\n        };\n\n        s.send_arbitrary_stream_data_client(&[64, 64, 0, 0, 3, 1, 2, 3], 0, true)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::Retry);\n\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(first),\n            fin: false\n        });\n\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(second),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn val_multiple_buffers() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let expected = Frame::Data {\n            payload: vec![1, 2, 3, 4, 5],\n        };\n\n        s.send_arbitrary_stream_data_client(&[0, 5, 1], 0, false)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n\n        s.send_arbitrary_stream_data_client(&[2, 3, 4, 5], 0, false)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: false\n        });\n    }\n\n    #[test]\n    fn val_doesnt_extend_to_buffer_end() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let expected = Frame::Data {\n            payload: vec![1, 2, 3, 4, 5],\n        };\n\n        s.send_arbitrary_stream_data_client(&[0, 5, 1, 2, 3, 4, 5, 0], 0, true)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: false\n        });\n\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n        assert_eq!(parser.ty, Some(0));\n        assert_eq!(parser.curr_state, FrameState::Len);\n    }\n\n    #[test]\n    fn multiple_frames_in_buffer() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let first = Frame::Data {\n            payload: vec![1, 2, 3],\n        };\n        let second = Frame::Data {\n            payload: vec![1, 2, 3, 4],\n        };\n\n        s.send_arbitrary_stream_data_client(\n            &[0, 3, 1, 2, 3, 0, 4, 1, 2, 3, 4],\n            0,\n            true,\n        )\n        .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(first),\n            fin: false\n        });\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(second),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn multiple_frames_multiple_buffers() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let first = Frame::Data {\n            payload: vec![1, 2, 3],\n        };\n        let second = Frame::Data {\n            payload: vec![1, 2, 3, 4],\n        };\n        let third = Frame::Data {\n            payload: vec![1, 2, 3],\n        };\n\n        s.send_arbitrary_stream_data_client(\n            &[0, 3, 1, 2, 3, 0, 4, 1, 2, 3, 4, 0, 3, 1],\n            0,\n            false,\n        )\n        .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(first),\n            fin: false\n        });\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(second),\n            fin: false\n        });\n\n        let res = parser.try_parse_frame(&mut s.pipe.server);\n        assert_eq!(res, Err(H3Error::TransportError(quiche::Error::Done)));\n        assert_eq!(parser.ty, Some(0));\n        assert_eq!(parser.len, Some(3));\n        assert_eq!(parser.state_buf, vec![1, 0, 0]);\n\n        s.send_arbitrary_stream_data_client(&[2, 3], 0, true)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(third),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn multiple_frames_nonzero_stream() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let first = Frame::Data {\n            payload: vec![1, 2, 3],\n        };\n        let second = Frame::Data {\n            payload: vec![1, 2, 3, 4, 5],\n        };\n\n        s.send_arbitrary_stream_data_client(&[0, 3, 1, 2, 3], 0, true)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(first.clone()),\n            fin: true\n        });\n\n        parser = FrameParser::new(4);\n        s.send_arbitrary_stream_data_client(&[0, 5, 1, 2, 3, 4, 5], 4, false)\n            .expect(\"second\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(second),\n            fin: false\n        });\n\n        s.send_arbitrary_stream_data_client(&[0, 3, 1, 2, 3], 4, true)\n            .expect(\"third\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(first),\n            fin: true\n        });\n    }\n\n    #[test]\n    fn interrupted() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        s.send_arbitrary_stream_data_client(&[0, 3, 1, 2], 0, true)\n            .expect(\"send\");\n\n        assert_eq!(\n            parser.try_parse_frame(&mut s.pipe.server),\n            Ok(FrameParseResult::Interrupted(InterruptCause::FinBit))\n        );\n    }\n\n    #[test]\n    fn stream_reset() {\n        let mut s = session().unwrap();\n        s.handshake().unwrap();\n\n        let mut parser = FrameParser::default();\n        let expected = Frame::Data {\n            payload: vec![1, 2, 3, 4, 5],\n        };\n\n        s.send_arbitrary_stream_data_client(&[0, 5, 1, 2, 3, 4, 5], 0, false)\n            .expect(\"first\");\n        let res = parser.try_parse_frame(&mut s.pipe.server).unwrap();\n        assert_eq!(res, FrameParseResult::FrameParsed {\n            h3i_frame: H3iFrame::QuicheH3(expected),\n            fin: false\n        });\n\n        s.pipe\n            .client\n            .stream_shutdown(0, quiche::Shutdown::Write, 0)\n            .expect(\"shutdown\");\n        s.pipe.advance().expect(\"advance\");\n        assert_eq!(\n            parser.try_parse_frame(&mut s.pipe.server),\n            Ok(FrameParseResult::Interrupted(InterruptCause::ResetStream(\n                0\n            )))\n        );\n    }\n}\n"
  },
  {
    "path": "h3i/src/lib.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! h3i - low-level HTTP/3 debug and testing\n//!\n//! HTTP/3 ([RFC 9114]) is the wire format for HTTP semantics ([RFC 9110]). The\n//! RFCs contain a range of requirements about how Request or Response messages\n//! are generated, serialized, sent, received, parsed, and consumed. QUIC ([RFC\n//! 9000]) streams are used for these messages along with other control and\n//! QPACK ([RFC 9204]) header compression instructions.\n//!\n//! h3i provides a highly configurable HTTP/3 client that can bend RFC rules in\n//! order to test the behavior of servers. QUIC streams can be opened, fin'd,\n//! stopped or reset at any point in time. HTTP/3 frames can be sent on any\n//! stream, in any order, containing user-controlled content (both legal and\n//! illegal).\n//!\n//! # Example\n//!\n//! The following example sends a request with its Content-Length header set to\n//! 5, but with its body only consisting of 4 bytes. This is classified as a\n//! [malformed request], and the server should respond with a 400 Bad Request\n//! response. Once h3i receives the response, it will close the connection.\n//!\n//! ```no_run\n//! use h3i::actions::h3::Action;\n//! use h3i::actions::h3::StreamEvent;\n//! use h3i::actions::h3::StreamEventType;\n//! use h3i::actions::h3::WaitType;\n//! use h3i::client::sync_client;\n//! use h3i::config::Config;\n//! use quiche::h3::frame::Frame;\n//! use quiche::h3::Header;\n//! use quiche::h3::NameValue;\n//!\n//! fn main() {\n//!    /// The QUIC stream to send the frames on. See\n//!    /// https://datatracker.ietf.org/doc/html/rfc9000#name-streams and\n//!    /// https://datatracker.ietf.org/doc/html/rfc9114#request-streams for more.\n//!    const STREAM_ID: u64 = 0;\n//!\n//!    let config = Config::new()\n//!        .with_host_port(\"blog.cloudflare.com\".to_string())\n//!        .with_idle_timeout(2000)\n//!        .build()\n//!        .unwrap();\n//!\n//!    let headers = vec![\n//!        Header::new(b\":method\", b\"POST\"),\n//!        Header::new(b\":scheme\", b\"https\"),\n//!        Header::new(b\":authority\", b\"blog.cloudflare.com\"),\n//!        Header::new(b\":path\", b\"/\"),\n//!        // We say that we're going to send a body with 5 bytes...\n//!        Header::new(b\"content-length\", b\"5\"),\n//!    ];\n//!\n//!    let header_block = encode_header_block(&headers).unwrap();\n//!\n//!    let actions = vec![\n//!        Action::SendHeadersFrame {\n//!            stream_id: STREAM_ID,\n//!            fin_stream: false,\n//!            headers,\n//!            frame: Frame::Headers { header_block },\n//!            literal_headers: false,\n//!            expected_result: Default::default(),\n//!        },\n//!        Action::SendFrame {\n//!            stream_id: STREAM_ID,\n//!            fin_stream: true,\n//!            frame: Frame::Data {\n//!                // ...but, in actuality, we only send 4 bytes. This should yield a\n//!                // 400 Bad Request response from an RFC-compliant\n//!                // server: https://datatracker.ietf.org/doc/html/rfc9114#section-4.1.2-3\n//!                payload: b\"test\".to_vec(),\n//!            },\n//!            expected_result: Default::default(),\n//!        },\n//!        Action::Wait {\n//!            wait_type: WaitType::StreamEvent(StreamEvent {\n//!                stream_id: STREAM_ID,\n//!                event_type: StreamEventType::Headers,\n//!            }),\n//!        },\n//!        Action::ConnectionClose {\n//!            error: quiche::ConnectionError {\n//!                is_app: true,\n//!                error_code: quiche::h3::WireErrorCode::NoError as u64,\n//!                reason: vec![],\n//!            },\n//!        },\n//!    ];\n//!\n//!    // This example doesn't use close trigger frames, since we manually close the connection upon\n//!    // receiving a HEADERS frame on stream 0.\n//!    let close_trigger_frames = None;\n//!    let summary = sync_client::connect(config, actions, close_trigger_frames);\n//!\n//!    println!(\n//!        \"=== received connection summary! ===\\n\\n{}\",\n//!        serde_json::to_string_pretty(&summary).unwrap_or_else(|e| e.to_string())\n//!    );\n//! }\n//!\n//! // SendHeadersFrame requires a QPACK-encoded header block. h3i provides a\n//! // `send_headers_frame` helper function to abstract this, but for clarity, we do\n//! // it here.\n//! fn encode_header_block(\n//!     headers: &[quiche::h3::Header],\n//! ) -> std::result::Result<Vec<u8>, String> {\n//!     let mut encoder = quiche::h3::qpack::Encoder::new();\n//!\n//!     let headers_len = headers\n//!         .iter()\n//!         .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);\n//!\n//!     let mut header_block = vec![0; headers_len];\n//!     let len = encoder\n//!         .encode(headers, &mut header_block)\n//!         .map_err(|_| \"Internal Error\")?;\n//!\n//!     header_block.truncate(len);\n//!\n//!     Ok(header_block)\n//! }\n//! ```\n\n//! [RFC 9000]: https://www.rfc-editor.org/rfc/rfc9000.html\n//! [RFC 9110]: https://www.rfc-editor.org/rfc/rfc9110.html\n//! [RFC 9114]: https://www.rfc-editor.org/rfc/rfc9114.html\n//! [RFC 9204]: https://www.rfc-editor.org/rfc/rfc9204.html\n//! [malformed request]: https://datatracker.ietf.org/doc/html/rfc9114#section-4.1.2-3\n\nuse qlog::events::quic::PacketHeader;\nuse qlog::events::quic::PacketSent;\nuse qlog::events::quic::PacketType;\nuse qlog::events::quic::QuicFrame;\nuse qlog::events::EventData;\nuse quiche::h3::qpack::encode_int;\nuse quiche::h3::qpack::encode_str;\nuse quiche::h3::qpack::LITERAL;\nuse quiche::h3::NameValue;\nuse smallvec::SmallVec;\n\n#[cfg(not(feature = \"async\"))]\npub use quiche;\n#[cfg(feature = \"async\")]\npub use tokio_quiche::quiche;\n\n/// The ID for an HTTP/3 control stream type.\n///\n/// See <https://datatracker.ietf.org/doc/html/rfc9114#name-control-streams>.\npub const HTTP3_CONTROL_STREAM_TYPE_ID: u64 = 0x0;\n\n/// The ID for an HTTP/3 push stream type.\n///\n/// See <https://datatracker.ietf.org/doc/html/rfc9114#name-push-streams>.\npub const HTTP3_PUSH_STREAM_TYPE_ID: u64 = 0x1;\n\n/// The ID for a QPACK encoder stream type.\n///\n/// See <https://datatracker.ietf.org/doc/html/rfc9204#section-4.2-2.1>.\npub const QPACK_ENCODER_STREAM_TYPE_ID: u64 = 0x2;\n\n/// The ID for a QPACK decoder stream type.\n///\n/// See <https://datatracker.ietf.org/doc/html/rfc9204#section-4.2-2.2>.\npub const QPACK_DECODER_STREAM_TYPE_ID: u64 = 0x3;\n\n#[derive(Default)]\nstruct StreamIdAllocator {\n    id: u64,\n}\n\nimpl StreamIdAllocator {\n    pub fn take_next_id(&mut self) -> u64 {\n        let old = self.id;\n        self.id += 4;\n\n        old\n    }\n\n    pub fn peek_next_id(&mut self) -> u64 {\n        self.id\n    }\n}\n\n/// Encodes a header block literally. Unlike [`encode_header_block`],\n/// this function encodes all the headers exactly as provided. This\n/// means it does not use the huffman lookup table, nor does it convert\n/// the header names to lowercase before encoding.\nfn encode_header_block_literal(\n    headers: &[quiche::h3::Header],\n) -> std::result::Result<Vec<u8>, String> {\n    // This is a combination of a modified `quiche::h3::qpack::Encoder::encode`\n    // and the [`encode_header_block`] function.\n    let headers_len = headers\n        .iter()\n        .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);\n\n    let mut header_block = vec![0; headers_len];\n\n    let mut b = octets::OctetsMut::with_slice(&mut header_block);\n\n    // Required Insert Count.\n    encode_int(0, 0, 8, &mut b).map_err(|e| format!(\"{e:?}\"))?;\n\n    // Base.\n    encode_int(0, 0, 7, &mut b).map_err(|e| format!(\"{e:?}\"))?;\n\n    for h in headers {\n        encode_str::<false>(h.name(), LITERAL, 3, &mut b)\n            .map_err(|e| format!(\"{e:?}\"))?;\n        encode_str::<false>(h.value(), 0, 7, &mut b)\n            .map_err(|e| format!(\"{e:?}\"))?;\n    }\n\n    let len = b.off();\n\n    header_block.truncate(len);\n    Ok(header_block)\n}\n\nfn encode_header_block(\n    headers: &[quiche::h3::Header],\n) -> std::result::Result<Vec<u8>, String> {\n    let mut encoder = quiche::h3::qpack::Encoder::new();\n\n    let headers_len = headers\n        .iter()\n        .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);\n\n    let mut header_block = vec![0; headers_len];\n    let len = encoder\n        .encode(headers, &mut header_block)\n        .map_err(|_| \"Internal Error\")?;\n\n    header_block.truncate(len);\n\n    Ok(header_block)\n}\n\nfn fake_packet_header() -> PacketHeader {\n    PacketHeader {\n        packet_type: PacketType::OneRtt,\n        ..Default::default()\n    }\n}\n\nfn fake_packet_sent(frames: Option<SmallVec<[QuicFrame; 1]>>) -> EventData {\n    EventData::QuicPacketSent(PacketSent {\n        header: fake_packet_header(),\n        frames,\n        ..Default::default()\n    })\n}\n\npub mod actions;\npub mod client;\npub mod config;\npub mod frame;\npub mod frame_parser;\npub mod prompts;\npub mod recordreplay;\n"
  },
  {
    "path": "h3i/src/main.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::env;\nuse std::io::BufReader;\nuse std::result::Result;\nuse std::time;\nuse std::time::Instant;\n\nuse h3i::actions::h3::Action;\nuse h3i::client::connection_summary::ConnectionSummary;\nuse h3i::client::ClientError;\nuse h3i::prompts::h3::Prompter;\nuse h3i::recordreplay::qlog::QlogEvent;\nuse h3i::recordreplay::qlog::*;\nuse qlog::events::HTTP3_URI;\nuse qlog::events::QUIC_URI;\nuse qlog::reader::QlogSeqReader;\n\nuse clap::App;\nuse clap::Arg;\n#[cfg(feature = \"async\")]\nuse tokio_quiche::BoxError;\n\nfn main() -> Result<(), ClientError> {\n    let mut log_builder = env_logger::builder();\n    if env::var_os(\"RUST_LOG\").is_none() {\n        log_builder.filter_level(log::LevelFilter::Info);\n    }\n\n    log_builder.format_timestamp_nanos().init();\n\n    let config = match config_from_clap() {\n        Ok(v) => v,\n\n        Err(e) => {\n            log::error!(\"Error loading configuration, exiting: {e}\");\n            return Err(ClientError::Other(\"Invalid configuration\".into()));\n        },\n    };\n\n    let actions = match &config.qlog_input {\n        Some(v) => read_qlog(v, config.host_override.as_deref()),\n        None => prompt_frames(&config),\n    };\n\n    #[cfg(not(feature = \"async\"))]\n    let summary = sync_client(config, actions);\n    #[cfg(feature = \"async\")]\n    let summary = async_client(config, actions);\n\n    match summary {\n        Ok(s) => {\n            log::debug!(\n                \"received connection_summary: {}\",\n                serde_json::to_string_pretty(&s)\n                    .unwrap_or_else(|e| e.to_string())\n            );\n        },\n        Err(e) => {\n            log::error!(\"{e:?}\");\n        },\n    }\n\n    Ok(())\n}\n\nstruct Config {\n    library_config: h3i::config::Config,\n    pub qlog_input: Option<String>,\n    pub qlog_actions_output: bool,\n    pub host_override: Option<String>,\n}\n\nfn config_from_clap() -> std::result::Result<Config, String> {\n    let matches = App::new(\"h3i\")\n        .version(\"v0.1.0\")\n        .about(\"Interactive HTTP/3 console debugger\")\n        .arg(\n            Arg::with_name(\"host:port\")\n                .help(\"Hostname and port of the HTTP/3 server\")\n                .required(true)\n                .index(1),\n        )\n        .arg(\n            Arg::with_name(\"omit-sni\")\n                .long(\"omit-sni\")\n                .help(\"Omit the SNI from the TLS handshake\")\n                // Requires an OsStr, so we can parse to empty later on\n                .takes_value(false)\n        )\n        .arg(\n            Arg::with_name(\"connect-to\")\n                .long(\"connect-to\")\n                .help(\"Set a specific IP address to connect to, rather than use DNS resolution\")\n                .takes_value(true),\n        )\n        .arg(\n            Arg::with_name(\"no-verify\")\n                .long(\"no-verify\")\n                .help(\"Don't verify server's certificate.\"),\n        )\n        .arg(\n            Arg::with_name(\"no-qlog-actions-output\")\n                .long(\"no-qlog-actions-output\")\n                .help(\"Don't output action sequence as qlog.\"),\n        )\n        .arg(\n            Arg::with_name(\"qlog-input\")\n                .long(\"qlog-input\")\n                .help(\"Drive connection via qlog rather than cli.\")\n                .takes_value(true),\n        )\n        .arg(\n            Arg::with_name(\"idle-timeout\")\n                .long(\"idle-timeout\")\n                .help(\"The QUIC idle timeout value in milliseconds.\")\n                .takes_value(true)\n                .default_value(\"5000\"),\n        )\n        .arg(\n            Arg::with_name(\"max-data\")\n                .long(\"max-data\")\n                .help(\"Flow control limit for the connection in bytes\")\n                .takes_value(true)\n                .default_value(\"10000000\"),\n        )\n        .arg(\n            Arg::with_name(\"max-stream-data-bidi-local\")\n                .long(\"max-stream-data-bidi-local\")\n                .help(\"Flow control limit for locally-initiated bidirectional streams in bytes.\")\n                .takes_value(true)\n                .default_value(\"1000000\"),\n        )\n        .arg(\n            Arg::with_name(\"max-stream-data-bidi-remote\")\n                .long(\"max-stream-data-bidi-remote\")\n                .help(\"Flow control limit for remotely-initiated bidirectional streams in bytes.\")\n                .takes_value(true)\n                .default_value(\"1000000\"),\n        )\n        .arg(\n            Arg::with_name(\"max-stream-data-uni\")\n                .long(\"max-stream-data-uni\")\n                .help(\"Flow control limit for unidirectional streams in bytes.\")\n                .takes_value(true)\n                .default_value(\"1000000\"),\n        )\n        .arg(\n            Arg::with_name(\"max-streams-bidi\")\n                .long(\"max-streams-bidi\")\n                .help(\"Maximum count for concurrent remotely-initiated bidirectional streams.\")\n                .takes_value(true)\n                .default_value(\"100\"),\n        )\n        .arg(\n            Arg::with_name(\"max-streams-uni\")\n                .long(\"max-streams-uni\")\n                .help(\"Maximum count for concurrent remotely-initiated unidirectional streams.\")\n                .takes_value(true)\n                .default_value(\"100\"),\n        )\n        .arg(\n            Arg::with_name(\"max-window\")\n                .long(\"max-window\")\n                .help(\"Receiver window limit for the connection in bytes.\")\n                .takes_value(true)\n                .default_value(\"25165824\"),\n        )\n        .arg(\n            Arg::with_name(\"max-stream-window\")\n                .long(\"max-stream-window\")\n                .help(\"Receiver window limit for a stream in bytes.\")\n                .takes_value(true)\n                .default_value(\"16777216\"),\n        )\n        .arg(\n            Arg::with_name(\"replay-host-override\")\n                .long(\"replay-host-override\")\n                .help(\"Override the host or authority field in any replayed request headers.\")\n                .requires(\"qlog-input\")\n                .takes_value(true),\n        )\n        .arg(\n            Arg::with_name(\"enable-dgram\")\n                .long(\"enable-dgram\")\n                .help(\"Enable datagram reception\")\n                .takes_value(false),\n        )\n        .arg(\n            Arg::with_name(\"dgram-recv-queue-len\")\n                .long(\"dgram-recv-queue-len\")\n                .help(\"Datagram receive queue length\")\n                .default_value(\"65536\")\n                .takes_value(true),\n        )\n        .arg(\n            Arg::with_name(\"dgram-send-queue-len\")\n                .long(\"dgram-send-queue-len\")\n                .help(\"Datagram send queue length\")\n                .default_value(\"65536\")\n                .takes_value(true),\n        )\n        .get_matches();\n\n    let host_port = matches.value_of(\"host:port\").unwrap().to_string();\n    let omit_sni = matches.is_present(\"omit-sni\");\n    let connect_to: Option<String> =\n        matches.value_of(\"connect-to\").map(|s| s.to_string());\n    let verify_peer = !matches.is_present(\"no-verify\");\n    let idle_timeout = matches\n        .value_of(\"idle-timeout\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"idle-timeout input error {e}\"))?;\n\n    let max_data = matches\n        .value_of(\"max-data\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"max-data input error {e}\"))?;\n\n    let max_stream_data_bidi_local = matches\n        .value_of(\"max-stream-data-bidi-local\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"max-stream-data-bidi-local input error {e}\"))?;\n\n    let max_stream_data_bidi_remote = matches\n        .value_of(\"max-stream-data-bidi-remote\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"max-stream-data-bidi-remote input error {e}\"))?;\n\n    let max_stream_data_uni = matches\n        .value_of(\"max-stream-data-uni\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"max-stream-data-uni input error {e}\"))?;\n\n    let max_streams_bidi = matches\n        .value_of(\"max-streams-bidi\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"max-streams-bidi input error {e}\"))?;\n\n    let max_streams_uni = matches\n        .value_of(\"max-streams-uni\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"max-streams-uni input error {e}\"))?;\n\n    let max_window = matches\n        .value_of(\"max-window\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"max-window input error {e}\"))?;\n\n    let max_stream_window = matches\n        .value_of(\"max-stream-window\")\n        .unwrap()\n        .parse::<u64>()\n        .map_err(|e| format!(\"max-stream-window input error {e}\"))?;\n\n    let enable_dgram = matches.is_present(\"enable-dgram\");\n\n    let dgram_recv_queue_len = matches\n        .value_of(\"dgram-recv-queue-len\")\n        .unwrap()\n        .parse::<usize>()\n        .map_err(|e| format!(\"dgram-recv-queue-len input error {e}\"))?;\n\n    let dgram_send_queue_len = matches\n        .value_of(\"dgram-send-queue-len\")\n        .unwrap()\n        .parse::<usize>()\n        .map_err(|e| format!(\"dgram-send-queue-len input error {e}\"))?;\n\n    let qlog_actions_output = !matches.is_present(\"no-qlog-actions-output\");\n    let qlog_input = matches.value_of(\"qlog-input\").and_then(|q| {\n        std::path::Path::new(q)\n            .file_name()\n            .unwrap()\n            .to_str()\n            .map(|s| s.to_string())\n    });\n\n    let host_override = matches\n        .value_of(\"replay-host-override\")\n        .map(|s| s.to_string());\n\n    let library_config = h3i::config::Config {\n        host_port,\n        omit_sni,\n        connect_to,\n        source_port: 0,\n        verify_peer,\n        idle_timeout,\n        max_data,\n        max_stream_data_bidi_local,\n        max_stream_data_bidi_remote,\n        max_stream_data_uni,\n        max_streams_bidi,\n        max_streams_uni,\n        max_window,\n        max_stream_window,\n        session: None,\n        enable_early_data: false,\n        enable_dgram,\n        dgram_recv_queue_len,\n        dgram_send_queue_len,\n    };\n\n    Ok(Config {\n        qlog_input,\n        qlog_actions_output,\n        library_config,\n        host_override,\n    })\n}\n\n#[cfg(feature = \"async\")]\nfn async_client(\n    config: Config, frame_actions: Vec<Action>,\n) -> Result<ConnectionSummary, BoxError> {\n    let rt = tokio::runtime::Builder::new_current_thread()\n        .enable_all()\n        .build()\n        .map_err(Box::new)?;\n\n    let fut = async {\n        h3i::client::async_client::connect(\n            &config.library_config,\n            frame_actions,\n            None,\n        )\n        .await\n        .unwrap()\n        .await\n    };\n\n    Ok(rt.block_on(fut))\n}\n\n#[cfg(not(feature = \"async\"))]\nfn sync_client(\n    config: Config, actions: Vec<Action>,\n) -> Result<ConnectionSummary, ClientError> {\n    // TODO: CLI/qlog don't support passing close trigger frames at the moment\n    h3i::client::sync_client::connect(config.library_config, actions, None)\n}\n\nfn read_qlog(filename: &str, host_override: Option<&str>) -> Vec<Action> {\n    let file = std::fs::File::open(filename).expect(\"failed to open file\");\n    let reader = BufReader::new(file);\n\n    let qlog_reader = QlogSeqReader::new(Box::new(reader)).unwrap();\n    let mut actions = vec![];\n\n    for event in qlog_reader {\n        match event {\n            qlog::reader::Event::Qlog(ev) => {\n                let ac: H3Actions = actions_from_qlog(ev, host_override);\n                actions.extend(ac.0);\n            },\n\n            qlog::reader::Event::Json(ev) => {\n                let ac: H3Actions = (ev).into();\n                actions.extend(ac.0);\n            },\n        }\n    }\n\n    actions\n}\n\nfn prompt_frames(config: &Config) -> Vec<Action> {\n    let mut prompter = Prompter::with_config(&config.library_config);\n    let actions = prompter.prompt();\n\n    if !actions.is_empty() && config.qlog_actions_output {\n        let writer = make_qlog_writer();\n        let mut streamer = make_streamer(std::boxed::Box::new(writer));\n\n        for action in &actions {\n            let events: QlogEvents = action.into();\n            for event in events {\n                match event {\n                    QlogEvent::Event { data, ex_data } => {\n                        streamer.add_event_data_ex_now(*data, ex_data).ok();\n                    },\n\n                    QlogEvent::JsonEvent(mut ev) => {\n                        // need to rewrite the event time\n                        ev.time = Instant::now()\n                            .duration_since(streamer.start_time())\n                            .as_secs_f64() *\n                            1000.0;\n                        streamer.add_event(ev).ok();\n                    },\n                }\n            }\n        }\n    }\n\n    actions\n}\n\n/// Makes a buffered writer for a qlog.\npub fn make_qlog_writer() -> std::io::BufWriter<std::fs::File> {\n    let mut path = std::env::current_dir().unwrap();\n    let now = time::SystemTime::now();\n    let filename = format!(\n        \"{}-qlog.sqlog\",\n        now.duration_since(time::UNIX_EPOCH).unwrap().as_millis()\n    );\n    path.push(filename.clone());\n\n    log::info!(\"Session will be recorded to {filename}\");\n\n    match std::fs::File::create(&path) {\n        Ok(f) => std::io::BufWriter::new(f),\n\n        Err(e) =>\n            panic!(\"Error creating qlog file attempted path was {path:?}: {e}\"),\n    }\n}\n\npub fn make_streamer(\n    writer: Box<dyn std::io::Write + Send + Sync>,\n) -> qlog::streamer::QlogStreamer {\n    let vp = qlog::VantagePointType::Client;\n\n    let trace = qlog::TraceSeq::new(\n        Some(\"h3i\".into()),\n        Some(\"h3i\".into()),\n        None,\n        Some(qlog::VantagePoint {\n            name: None,\n            ty: vp,\n            flow: None,\n        }),\n        vec![QUIC_URI.to_string(), HTTP3_URI.to_string()],\n    );\n\n    let mut streamer = qlog::streamer::QlogStreamer::new(\n        Some(\"h3i\".into()),\n        Some(\"h3i\".into()),\n        time::Instant::now(),\n        trace,\n        qlog::events::EventImportance::Extra,\n        writer,\n    );\n\n    streamer.start_log().ok();\n\n    streamer\n}\n"
  },
  {
    "path": "h3i/src/prompts/h3/errors.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! QUIC and HTTP/3 errors for the h3i client.\nuse inquire::error::InquireResult;\nuse inquire::validator::Validation;\nuse inquire::CustomUserError;\nuse inquire::Select;\nuse inquire::Text;\nuse qlog::events::quic::ErrorSpace;\n\nuse crate::prompts::h3;\n\nuse super::SuggestionResult;\n\npub const NO_ERROR: &str = \"NO_ERROR\";\npub const INTERNAL_ERROR: &str = \"INTERNAL_ERROR\";\npub const CONNECTION_REFUSED: &str = \"CONNECTION_REFUSED\";\npub const FLOW_CONTROL_ERROR: &str = \"FLOW_CONTROL_ERROR\";\npub const STREAM_LIMIT_ERROR: &str = \"STREAM_LIMIT_ERROR\";\npub const STREAM_STATE_ERROR: &str = \"STREAM_STATE_ERROR\";\npub const FINAL_SIZE_ERROR: &str = \"FINAL_SIZE_ERROR\";\npub const FRAME_ENCODING_ERROR: &str = \"FRAME_ENCODING_ERROR\";\npub const TRANSPORT_PARAMETER_ERROR: &str = \"TRANSPORT_PARAMETER_ERROR\";\npub const CONNECTION_ID_LIMIT_ERROR: &str = \"CONNECTION_ID_LIMIT_ERROR\";\npub const PROTOCOL_VIOLATION: &str = \"PROTOCOL_VIOLATION\";\npub const INVALID_TOKEN: &str = \"INVALID_TOKEN\";\npub const APPLICATION_ERROR: &str = \"APPLICATION_ERROR\";\npub const CRYPTO_BUFFER_EXCEEDED: &str = \"CRYPTO_BUFFER_EXCEEDED\";\npub const KEY_UPDATE_ERROR: &str = \"KEY_UPDATE_ERROR\";\npub const AEAD_LIMIT_REACHED: &str = \"AEAD_LIMIT_REACHED\";\npub const NO_VIABLE_PATH: &str = \"NO_VIABLE_PATH\";\npub const VERSION_NEGOTIATION_ERROR: &str = \"VERSION_NEGOTIATION_ERROR\";\n\npub const H3_DATAGRAM_ERROR: &str = \"H3_DATAGRAM_ERROR\";\npub const H3_NO_ERROR: &str = \"H3_NO_ERROR\";\npub const H3_GENERAL_PROTOCOL_ERROR: &str = \"H3_GENERAL_PROTOCOL_ERROR\";\npub const H3_INTERNAL_ERROR: &str = \"H3_INTERNAL_ERROR\";\npub const H3_STREAM_CREATION_ERROR: &str = \"H3_STREAM_CREATION_ERROR\";\npub const H3_CLOSED_CRITICAL_STREAM: &str = \"H3_CLOSED_CRITICAL_STREAM\";\npub const H3_FRAME_UNEXPECTED: &str = \"H3_FRAME_UNEXPECTED\";\npub const H3_FRAME_ERROR: &str = \"H3_FRAME_ERROR\";\npub const H3_EXCESSIVE_LOAD: &str = \"H3_EXCESSIVE_LOAD\";\npub const H3_ID_ERROR: &str = \"H3_ID_ERROR\";\npub const H3_SETTINGS_ERROR: &str = \"H3_SETTINGS_ERROR\";\npub const H3_MISSING_SETTINGS: &str = \"H3_MISSING_SETTINGS\";\npub const H3_REQUEST_REJECTED: &str = \"H3_REQUEST_REJECTED\";\npub const H3_REQUEST_CANCELLED: &str = \"H3_REQUEST_CANCELLED\";\npub const H3_REQUEST_INCOMPLETE: &str = \"H3_REQUEST_INCOMPLETE\";\npub const H3_MESSAGE_ERROR: &str = \"H3_MESSAGE_ERROR\";\npub const H3_CONNECT_ERROR: &str = \"H3_CONNECT_ERROR\";\npub const H3_VERSION_FALLBACK: &str = \"H3_VERSION_FALLBACK\";\npub const QPACK_DECOMPRESSION_FAILED: &str = \"QPACK_DECOMPRESSION_FAILED\";\npub const QPACK_ENCODER_STREAM_ERROR: &str = \"QPACK_ENCODER_STREAM_ERROR\";\npub const QPACK_DECODER_STREAM_ERROR: &str = \"QPACK_DECODER_STREAM_ERROR\";\n\npub const TRANSPORT: &str = \"transport\";\npub const APPLICATION: &str = \"application\";\n\n// TODO: do we want to rely on the qlog enum here?\npub fn prompt_transport_or_app_error() -> InquireResult<(ErrorSpace, u64)> {\n    let trans_or_app = prompt_transport_or_app()?;\n    let space = if trans_or_app == TRANSPORT {\n        ErrorSpace::Transport\n    } else {\n        ErrorSpace::Application\n    };\n\n    let error_code = if matches!(space, ErrorSpace::Transport) {\n        let error_code = Text::new(\"error code:\")\n            .with_validator(validate_transport_error_code)\n            .with_autocomplete(&transport_error_code_suggestor)\n            .with_page_size(18)\n            .prompt()?;\n\n        match error_code.as_str() {\n            NO_ERROR => 0x0,\n            INTERNAL_ERROR => 0x1,\n            CONNECTION_REFUSED => 0x2,\n            FLOW_CONTROL_ERROR => 0x3,\n            STREAM_LIMIT_ERROR => 0x4,\n            STREAM_STATE_ERROR => 0x5,\n            FINAL_SIZE_ERROR => 0x6,\n            FRAME_ENCODING_ERROR => 0x7,\n            TRANSPORT_PARAMETER_ERROR => 0x8,\n            CONNECTION_ID_LIMIT_ERROR => 0x9,\n            PROTOCOL_VIOLATION => 0x0a,\n            INVALID_TOKEN => 0x0b,\n            APPLICATION_ERROR => 0x0c,\n            CRYPTO_BUFFER_EXCEEDED => 0x0d,\n            KEY_UPDATE_ERROR => 0x0e,\n            AEAD_LIMIT_REACHED => 0x0f,\n            NO_VIABLE_PATH => 0x10,\n            VERSION_NEGOTIATION_ERROR => 0x11,\n\n            v => v.parse::<u64>().unwrap(),\n        }\n    } else {\n        let error_code = Text::new(\"error code:\")\n            .with_validator(validate_h3_error_code)\n            .with_autocomplete(&h3_error_code_suggestor)\n            .with_page_size(22)\n            .prompt()?;\n\n        match error_code.as_str() {\n            H3_DATAGRAM_ERROR => 0x33,\n            H3_NO_ERROR => 0x100,\n            H3_GENERAL_PROTOCOL_ERROR => 0x101,\n            H3_INTERNAL_ERROR => 0x102,\n            H3_STREAM_CREATION_ERROR => 0x103,\n            H3_CLOSED_CRITICAL_STREAM => 0x104,\n            H3_FRAME_UNEXPECTED => 0x105,\n            H3_FRAME_ERROR => 0x106,\n            H3_EXCESSIVE_LOAD => 0x107,\n            H3_ID_ERROR => 0x108,\n            H3_SETTINGS_ERROR => 0x109,\n            H3_MISSING_SETTINGS => 0x10a,\n            H3_REQUEST_REJECTED => 0x10b,\n            H3_REQUEST_CANCELLED => 0x10c,\n            H3_REQUEST_INCOMPLETE => 0x10d,\n            H3_MESSAGE_ERROR => 0x10e,\n            H3_CONNECT_ERROR => 0x10f,\n            H3_VERSION_FALLBACK => 0x110,\n            QPACK_DECOMPRESSION_FAILED => 0x200,\n            QPACK_ENCODER_STREAM_ERROR => 0x201,\n            QPACK_DECODER_STREAM_ERROR => 0x202,\n\n            v => v.parse::<u64>().unwrap(),\n        }\n    };\n\n    Ok((space, error_code))\n}\n\nfn prompt_transport_or_app() -> InquireResult<String> {\n    Ok(\n        Select::new(\"transport or application:\", vec![TRANSPORT, APPLICATION])\n            .prompt()?\n            .to_string(),\n    )\n}\n\nfn validate_transport_error_code(\n    id: &str,\n) -> Result<Validation, CustomUserError> {\n    if matches!(\n        id,\n        NO_ERROR |\n            INTERNAL_ERROR |\n            CONNECTION_REFUSED |\n            FLOW_CONTROL_ERROR |\n            STREAM_LIMIT_ERROR |\n            STREAM_STATE_ERROR |\n            FINAL_SIZE_ERROR |\n            FRAME_ENCODING_ERROR |\n            TRANSPORT_PARAMETER_ERROR |\n            CONNECTION_ID_LIMIT_ERROR |\n            PROTOCOL_VIOLATION |\n            INVALID_TOKEN |\n            APPLICATION_ERROR |\n            CRYPTO_BUFFER_EXCEEDED |\n            KEY_UPDATE_ERROR |\n            AEAD_LIMIT_REACHED |\n            NO_VIABLE_PATH |\n            VERSION_NEGOTIATION_ERROR\n    ) {\n        return Ok(Validation::Valid);\n    }\n\n    h3::validate_varint(id)\n}\n\nfn transport_error_code_suggestor(\n    val: &str,\n) -> Result<Vec<String>, CustomUserError> {\n    let suggestions = [\n        NO_ERROR,\n        INTERNAL_ERROR,\n        CONNECTION_REFUSED,\n        FLOW_CONTROL_ERROR,\n        STREAM_LIMIT_ERROR,\n        STREAM_STATE_ERROR,\n        FINAL_SIZE_ERROR,\n        FRAME_ENCODING_ERROR,\n        TRANSPORT_PARAMETER_ERROR,\n        CONNECTION_ID_LIMIT_ERROR,\n        PROTOCOL_VIOLATION,\n        INVALID_TOKEN,\n        APPLICATION_ERROR,\n        CRYPTO_BUFFER_EXCEEDED,\n        KEY_UPDATE_ERROR,\n        AEAD_LIMIT_REACHED,\n        NO_VIABLE_PATH,\n        VERSION_NEGOTIATION_ERROR,\n    ];\n\n    super::squish_suggester(&suggestions, val)\n}\n\nfn validate_h3_error_code(id: &str) -> SuggestionResult<Validation> {\n    if matches!(\n        id,\n        H3_NO_ERROR |\n            H3_GENERAL_PROTOCOL_ERROR |\n            H3_INTERNAL_ERROR |\n            H3_STREAM_CREATION_ERROR |\n            H3_CLOSED_CRITICAL_STREAM |\n            H3_FRAME_UNEXPECTED |\n            H3_FRAME_ERROR |\n            H3_EXCESSIVE_LOAD |\n            H3_ID_ERROR |\n            H3_SETTINGS_ERROR |\n            H3_MISSING_SETTINGS |\n            H3_REQUEST_REJECTED |\n            H3_REQUEST_CANCELLED |\n            H3_REQUEST_INCOMPLETE |\n            H3_MESSAGE_ERROR |\n            H3_CONNECT_ERROR |\n            H3_VERSION_FALLBACK |\n            QPACK_DECOMPRESSION_FAILED |\n            QPACK_ENCODER_STREAM_ERROR |\n            QPACK_DECODER_STREAM_ERROR |\n            H3_DATAGRAM_ERROR\n    ) {\n        return Ok(Validation::Valid);\n    }\n\n    h3::validate_varint(id)\n}\n\nfn h3_error_code_suggestor(val: &str) -> SuggestionResult<Vec<String>> {\n    let suggestions = [\n        H3_NO_ERROR,\n        H3_GENERAL_PROTOCOL_ERROR,\n        H3_INTERNAL_ERROR,\n        H3_STREAM_CREATION_ERROR,\n        H3_CLOSED_CRITICAL_STREAM,\n        H3_FRAME_UNEXPECTED,\n        H3_FRAME_ERROR,\n        H3_EXCESSIVE_LOAD,\n        H3_ID_ERROR,\n        H3_SETTINGS_ERROR,\n        H3_MISSING_SETTINGS,\n        H3_REQUEST_REJECTED,\n        H3_REQUEST_CANCELLED,\n        H3_REQUEST_INCOMPLETE,\n        H3_MESSAGE_ERROR,\n        H3_CONNECT_ERROR,\n        H3_VERSION_FALLBACK,\n        QPACK_DECOMPRESSION_FAILED,\n        QPACK_ENCODER_STREAM_ERROR,\n        QPACK_DECODER_STREAM_ERROR,\n        H3_DATAGRAM_ERROR,\n    ];\n\n    super::squish_suggester(&suggestions, val)\n}\n"
  },
  {
    "path": "h3i/src/prompts/h3/headers.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Prompts for HTTP/3 header fields.\n\nuse crate::quiche;\nuse inquire::error::InquireResult;\nuse inquire::validator::Validation;\nuse inquire::Text;\nuse quiche::h3::frame::Frame;\n\nuse crate::encode_header_block;\nuse crate::encode_header_block_literal;\nuse crate::prompts::h3;\nuse crate::StreamIdAllocator;\n\nuse super::squish_suggester;\nuse super::stream::prompt_fin_stream;\nuse super::SuggestionResult;\nuse super::AUTO_PICK;\nuse super::EMPTY_PICKS;\nuse super::ESC_TO_RET;\nuse super::PUSH_ID_PROMPT;\nuse super::STREAM_ID_PROMPT;\nuse crate::actions::h3::Action;\n\npub fn prompt_headers(\n    sid_alloc: &mut StreamIdAllocator, host_port: &str, raw: bool, literal: bool,\n) -> InquireResult<Action> {\n    let stream_id = Text::new(STREAM_ID_PROMPT)\n        .with_placeholder(EMPTY_PICKS)\n        .with_help_message(ESC_TO_RET)\n        .with_validator(validate_stream_id)\n        .prompt()?;\n\n    let stream_id = match stream_id.as_str() {\n        \"\" => {\n            let id = sid_alloc.peek_next_id();\n            println!(\"{AUTO_PICK}={id}\");\n            id\n        },\n\n        _ => stream_id.parse::<u64>().unwrap(),\n    };\n\n    let mut headers = vec![];\n\n    if !raw {\n        headers.extend_from_slice(&pseudo_headers(host_port)?);\n    }\n\n    headers.extend_from_slice(&headers_read_loop()?);\n\n    sid_alloc.take_next_id();\n\n    let header_block = if literal {\n        encode_header_block_literal(&headers).unwrap_or_default()\n    } else {\n        encode_header_block(&headers).unwrap_or_default()\n    };\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendHeadersFrame {\n        stream_id,\n        fin_stream,\n        headers,\n        literal_headers: literal,\n        frame: Frame::Headers { header_block },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\npub fn prompt_push_promise() -> InquireResult<Action> {\n    let stream_id = h3::prompt_stream_id()?;\n    let push_id = h3::prompt_varint(PUSH_ID_PROMPT)?;\n\n    let headers = headers_read_loop()?;\n    let header_block = if headers.is_empty() {\n        vec![]\n    } else {\n        encode_header_block(&headers).unwrap()\n    };\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame: Frame::PushPromise {\n            push_id,\n            header_block,\n        },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\nfn pseudo_headers(host_port: &str) -> InquireResult<Vec<quiche::h3::Header>> {\n    let method = Text::new(\"method:\")\n        .with_autocomplete(&method_suggester)\n        .with_default(\"GET\")\n        .with_help_message(ESC_TO_RET)\n        .prompt()?;\n\n    let help = format!(\"Press enter/return for default ({host_port}\");\n    let authority = Text::new(\"authority:\")\n        .with_default(host_port)\n        .with_help_message(&help)\n        .prompt()?;\n\n    let path = Text::new(\"path:\").with_default(\"/\").prompt()?;\n\n    let scheme = Text::new(\"scheme:\")\n        .with_default(\"https\")\n        .with_help_message(ESC_TO_RET)\n        .prompt()?;\n\n    Ok(vec![\n        quiche::h3::Header::new(b\":method\", method.as_bytes()),\n        quiche::h3::Header::new(b\":authority\", authority.as_bytes()),\n        quiche::h3::Header::new(b\":path\", path.as_bytes()),\n        quiche::h3::Header::new(b\":scheme\", scheme.as_bytes()),\n    ])\n}\n\nfn headers_read_loop() -> InquireResult<Vec<quiche::h3::Header>> {\n    let mut headers = vec![];\n    loop {\n        let name = Text::new(\"field name:\")\n            .with_help_message(\n                \"type 'q!' to complete headers, or ESC to return to actions\",\n            )\n            .prompt()?;\n\n        if name == \"q!\" {\n            break;\n        }\n\n        let value = Text::new(\"field value:\")\n            .with_help_message(ESC_TO_RET)\n            .prompt()?;\n\n        headers.push(quiche::h3::Header::new(name.as_bytes(), value.as_bytes()));\n    }\n\n    Ok(headers)\n}\n\nfn method_suggester(val: &str) -> SuggestionResult<Vec<String>> {\n    let suggestions = [\"GET\", \"POST\", \"PUT\", \"DELETE\"];\n\n    squish_suggester(&suggestions, val)\n}\n\nfn validate_stream_id(id: &str) -> SuggestionResult<Validation> {\n    if id.is_empty() {\n        return Ok(Validation::Valid);\n    }\n\n    h3::validate_varint(id)\n}\n"
  },
  {
    "path": "h3i/src/prompts/h3/mod.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! A collection of interactive CLI prompts for HTTP/3 based on [inquire].\n\nuse inquire::error::CustomUserError;\nuse inquire::error::InquireResult;\nuse inquire::validator::ErrorMessage;\nuse inquire::validator::Validation;\nuse inquire::InquireError;\nuse inquire::Select;\nuse inquire::Text;\nuse qlog::events::quic::ErrorSpace;\nuse quiche::ConnectionError;\n\nuse crate::actions::h3::Action;\nuse crate::config::Config;\nuse crate::prompts::h3;\nuse crate::prompts::h3::headers::prompt_push_promise;\nuse crate::StreamIdAllocator;\n\nuse std::cell::RefCell;\n\nuse crate::quiche;\n\nuse self::stream::prompt_fin_stream;\nuse self::wait::prompt_wait;\n\n/// An error indicating that the provided buffer is not big enough.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum Error {\n    InternalError,\n    BufferTooShort,\n}\n\nimpl std::convert::From<octets::BufferTooShortError> for Error {\n    fn from(_err: octets::BufferTooShortError) -> Self {\n        Error::BufferTooShort\n    }\n}\n\n/// A specialized [`Result`] type for prompt operations.\n///\n/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html\npub type Result<T> = std::result::Result<T, Error>;\n\n/// A specialized [`Result`] type for internal prompt suggestion.\n///\n/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html\ntype SuggestionResult<T> = std::result::Result<T, CustomUserError>;\n\n/// A tuple of stream ID and quiche HTTP/3 frame.\npub type PromptedFrame = (u64, quiche::h3::frame::Frame);\n\nthread_local! {static CONNECTION_IDLE_TIMEOUT: RefCell<u64> = const { RefCell::new(0) }}\n\n// TODO(erittenhouse): exploring generating prompts at compile-time\nconst HEADERS: &str = \"headers\";\nconst HEADERS_NO_PSEUDO: &str = \"headers_no_pseudo\";\nconst HEADERS_LITERAL: &str = \"headers_literal\";\nconst HEADERS_NO_PSEUDO_LITERAL: &str = \"headers_no_pseudo_literal\";\nconst DATA: &str = \"data\";\nconst SETTINGS: &str = \"settings\";\nconst PUSH_PROMISE: &str = \"push_promise\";\nconst CANCEL_PUSH: &str = \"cancel_push\";\nconst GOAWAY: &str = \"goaway\";\nconst MAX_PUSH_ID: &str = \"max_push_id\";\nconst PRIORITY_UPDATE: &str = \"priority_update\";\nconst GREASE: &str = \"grease\";\nconst EXTENSION: &str = \"extension_frame\";\nconst OPEN_UNI_STREAM: &str = \"open_uni_stream\";\nconst RESET_STREAM: &str = \"reset_stream\";\nconst STOP_SENDING: &str = \"stop_sending\";\nconst CONNECTION_CLOSE: &str = \"connection_close\";\nconst STREAM_BYTES: &str = \"stream_bytes\";\nconst DATAGRAM_QUARTER_STREAM_ID: &str = \"datagram_quarter_stream_id\";\nconst DATAGRAM_RAW_PAYLOAD: &str = \"datagram_raw_payload\";\n\nconst COMMIT: &str = \"commit\";\nconst FLUSH_PACKETS: &str = \"flush_packets\";\nconst WAIT: &str = \"wait\";\nconst QUIT: &str = \"quit\";\n\nconst YES: &str = \"Yes\";\nconst NO: &str = \"No\";\n\nconst ESC_TO_RET: &str = \"ESC to return to actions\";\nconst STREAM_ID_PROMPT: &str = \"stream ID:\";\nconst EMPTY_PICKS: &str = \"empty picks next available ID\";\nconst AUTO_PICK: &str = \"autopick StreamID\";\nconst PUSH_ID_PROMPT: &str = \"push ID:\";\n\nenum PromptOutcome {\n    Action(Box<Action>),\n    Repeat,\n    Commit,\n    Clear,\n}\n\n/// The main prompter interface and state management.\npub struct Prompter {\n    host_port: String,\n    bidi_sid_alloc: StreamIdAllocator,\n    uni_sid_alloc: StreamIdAllocator,\n}\n\nimpl Prompter {\n    /// Construct a prompter with the provided `config`.\n    pub fn with_config(config: &Config) -> Self {\n        CONNECTION_IDLE_TIMEOUT.with(|v| *v.borrow_mut() = config.idle_timeout);\n\n        Self {\n            host_port: config.host_port.clone(),\n            bidi_sid_alloc: StreamIdAllocator { id: 0 },\n            uni_sid_alloc: StreamIdAllocator { id: 2 },\n        }\n    }\n\n    fn handle_action(&mut self, action: &str) -> PromptOutcome {\n        let res = match action {\n            HEADERS |\n            HEADERS_NO_PSEUDO |\n            HEADERS_LITERAL |\n            HEADERS_NO_PSEUDO_LITERAL => {\n                let literal = action == HEADERS_LITERAL ||\n                    action == HEADERS_NO_PSEUDO_LITERAL;\n                let raw = action == HEADERS_NO_PSEUDO ||\n                    action == HEADERS_NO_PSEUDO_LITERAL;\n                headers::prompt_headers(\n                    &mut self.bidi_sid_alloc,\n                    &self.host_port,\n                    raw,\n                    literal,\n                )\n            },\n\n            DATA => prompt_data(),\n            SETTINGS => settings::prompt_settings(),\n            OPEN_UNI_STREAM =>\n                stream::prompt_open_uni_stream(&mut self.uni_sid_alloc),\n            RESET_STREAM => stream::prompt_reset_stream(),\n            STOP_SENDING => stream::prompt_stop_sending(),\n            GREASE => prompt_grease(),\n            EXTENSION => prompt_extension(),\n            GOAWAY => prompt_goaway(),\n            MAX_PUSH_ID => prompt_max_push_id(),\n            CANCEL_PUSH => prompt_cancel_push(),\n            PUSH_PROMISE => prompt_push_promise(),\n            PRIORITY_UPDATE => priority::prompt_priority(),\n            CONNECTION_CLOSE => prompt_connection_close(),\n            STREAM_BYTES => prompt_stream_bytes(),\n            DATAGRAM_QUARTER_STREAM_ID | DATAGRAM_RAW_PAYLOAD =>\n                prompt_send_datagram(action == DATAGRAM_QUARTER_STREAM_ID),\n            FLUSH_PACKETS =>\n                return PromptOutcome::Action(Box::new(Action::FlushPackets)),\n            COMMIT => return PromptOutcome::Commit,\n            WAIT => prompt_wait(),\n            QUIT => return PromptOutcome::Clear,\n\n            _ => {\n                println!(\"error: unknown action {action}\");\n                return PromptOutcome::Repeat;\n            },\n        };\n\n        match res {\n            Ok(action) => PromptOutcome::Action(Box::new(action)),\n            Err(e) =>\n                if handle_action_loop_error(e) {\n                    PromptOutcome::Commit\n                } else {\n                    PromptOutcome::Repeat\n                },\n        }\n    }\n\n    /// Start the prompt loop.\n    ///\n    /// This continues to prompt for actions until a terminal choice is\n    /// made.\n    ///\n    /// Returns an ordered list of [Action]s, which may be empty.\n    pub fn prompt(&mut self) -> Vec<Action> {\n        let mut actions = vec![];\n\n        loop {\n            println!();\n\n            let action = match prompt_action() {\n                Ok(v) => v,\n                Err(inquire::InquireError::OperationCanceled) |\n                Err(inquire::InquireError::OperationInterrupted) =>\n                    return actions,\n                Err(e) => {\n                    println!(\"Unexpected error while determining action: {e}\");\n                    return actions;\n                },\n            };\n\n            match self.handle_action(&action) {\n                PromptOutcome::Action(action) => actions.push(*action),\n                PromptOutcome::Repeat => continue,\n                PromptOutcome::Commit => return actions,\n                PromptOutcome::Clear => return vec![],\n            }\n        }\n    }\n}\n\nfn handle_action_loop_error(err: InquireError) -> bool {\n    match err {\n        inquire::InquireError::OperationCanceled |\n        inquire::InquireError::OperationInterrupted => false,\n\n        _ => {\n            println!(\"Unexpected error: {err}\");\n            true\n        },\n    }\n}\n\nfn prompt_action() -> InquireResult<String> {\n    let name = Text::new(\n        \"Select an action to queue. `Commit` ends selection and flushes queue.\",\n    )\n    .with_autocomplete(&action_suggester)\n    .with_page_size(18)\n    .prompt();\n\n    name\n}\n\nfn action_suggester(val: &str) -> SuggestionResult<Vec<String>> {\n    // TODO: make this an enum to automatically pick up new actions\n    let suggestions = [\n        HEADERS,\n        HEADERS_NO_PSEUDO,\n        HEADERS_LITERAL,\n        HEADERS_NO_PSEUDO_LITERAL,\n        DATA,\n        SETTINGS,\n        GOAWAY,\n        PRIORITY_UPDATE,\n        PUSH_PROMISE,\n        CANCEL_PUSH,\n        MAX_PUSH_ID,\n        GREASE,\n        EXTENSION,\n        OPEN_UNI_STREAM,\n        RESET_STREAM,\n        STOP_SENDING,\n        CONNECTION_CLOSE,\n        STREAM_BYTES,\n        DATAGRAM_QUARTER_STREAM_ID,\n        DATAGRAM_RAW_PAYLOAD,\n        FLUSH_PACKETS,\n        COMMIT,\n        WAIT,\n        QUIT,\n    ];\n\n    squish_suggester(&suggestions, val)\n}\n\nfn squish_suggester(\n    suggestions: &[&str], val: &str,\n) -> SuggestionResult<Vec<String>> {\n    let val_lower = val.to_lowercase();\n\n    Ok(suggestions\n        .iter()\n        .filter(|s| s.to_lowercase().contains(&val_lower))\n        .map(|s| String::from(*s))\n        .collect())\n}\n\nfn validate_varint(id: &str) -> SuggestionResult<Validation> {\n    let x = id.parse::<u64>();\n\n    match x {\n        Ok(v) =>\n            if v >= u64::pow(2, 62) {\n                return Ok(Validation::Invalid(ErrorMessage::Default));\n            },\n\n        Err(_) => {\n            return Ok(Validation::Invalid(ErrorMessage::Default));\n        },\n    }\n\n    Ok(Validation::Valid)\n}\n\nfn prompt_stream_id() -> InquireResult<u64> {\n    prompt_varint(STREAM_ID_PROMPT)\n}\n\nfn prompt_control_stream_id() -> InquireResult<u64> {\n    let id = Text::new(STREAM_ID_PROMPT)\n        .with_validator(h3::validate_varint)\n        .with_autocomplete(&control_stream_suggestor)\n        .with_help_message(ESC_TO_RET)\n        .prompt()?;\n\n    // id is already validated so unwrap always succeeds\n    Ok(id.parse::<u64>().unwrap())\n}\n\nfn prompt_varint(str: &str) -> InquireResult<u64> {\n    let id = Text::new(str)\n        .with_validator(h3::validate_varint)\n        .with_placeholder(\"Integer <= 2^62 -1\")\n        .with_help_message(ESC_TO_RET)\n        .prompt()?;\n\n    // id is already validated so unwrap always succeeds\n    Ok(id.parse::<u64>().unwrap())\n}\n\nfn control_stream_suggestor(val: &str) -> SuggestionResult<Vec<String>> {\n    let suggestions = [\"2\"];\n\n    squish_suggester(&suggestions, val)\n}\n\nfn prompt_data() -> InquireResult<Action> {\n    let stream_id = h3::prompt_stream_id()?;\n\n    let payload = Text::new(\"payload:\").prompt()?;\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame: quiche::h3::frame::Frame::Data {\n            payload: payload.into(),\n        },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\nfn prompt_max_push_id() -> InquireResult<Action> {\n    let stream_id = h3::prompt_stream_id()?;\n    let push_id = h3::prompt_varint(PUSH_ID_PROMPT)?;\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame: quiche::h3::frame::Frame::MaxPushId { push_id },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\nfn prompt_cancel_push() -> InquireResult<Action> {\n    let stream_id = h3::prompt_stream_id()?;\n    let push_id = h3::prompt_varint(PUSH_ID_PROMPT)?;\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame: quiche::h3::frame::Frame::CancelPush { push_id },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\nfn prompt_goaway() -> InquireResult<Action> {\n    let stream_id = h3::prompt_stream_id()?;\n    let id = h3::prompt_varint(\"ID:\")?;\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame: quiche::h3::frame::Frame::GoAway { id },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\nfn prompt_grease() -> InquireResult<Action> {\n    let stream_id = h3::prompt_control_stream_id()?;\n    let raw_type = quiche::h3::grease_value();\n    let payload = Text::new(\"payload:\")\n        .prompt()\n        .expect(\"An error happened when asking for payload, try again later.\");\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame: quiche::h3::frame::Frame::Unknown {\n            raw_type,\n            payload: payload.into(),\n        },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\nfn prompt_extension() -> InquireResult<Action> {\n    let stream_id = h3::prompt_control_stream_id()?;\n    let raw_type = h3::prompt_varint(\"frame type:\")?;\n    let payload = Text::new(\"payload:\")\n        .with_help_message(ESC_TO_RET)\n        .prompt()\n        .expect(\"An error happened when asking for payload, try again later.\");\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame: quiche::h3::frame::Frame::Unknown {\n            raw_type,\n            payload: payload.into(),\n        },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\npub fn prompt_connection_close() -> InquireResult<Action> {\n    let (error_space, error_code) = errors::prompt_transport_or_app_error()?;\n    let reason = Text::new(\"reason phrase:\")\n        .with_placeholder(\"optional reason phrase\")\n        .prompt()\n        .unwrap_or_default();\n\n    Ok(Action::ConnectionClose {\n        error: ConnectionError {\n            is_app: matches!(error_space, ErrorSpace::Application),\n            error_code,\n            reason: reason.as_bytes().to_vec(),\n        },\n    })\n}\n\npub fn prompt_stream_bytes() -> InquireResult<Action> {\n    let stream_id = h3::prompt_stream_id()?;\n    let bytes = Text::new(\"bytes:\").prompt()?;\n    let fin_stream = prompt_fin_stream()?;\n\n    Ok(Action::StreamBytes {\n        stream_id,\n        fin_stream,\n        bytes: bytes.as_bytes().to_vec(),\n        expected_result: Default::default(),\n    })\n}\n\npub fn prompt_send_datagram(with_quarter_stream: bool) -> InquireResult<Action> {\n    if with_quarter_stream {\n        let stream_id = h3::prompt_varint(\"stream ID to be quartered:\")?;\n        // https://www.rfc-editor.org/rfc/rfc9297#name-http-3-datagrams\n        let quarter_stream_id = stream_id / 4;\n\n        let payload = Text::new(\"payload bytes:\").prompt()?;\n\n        let len = octets::varint_len(quarter_stream_id) + payload.len();\n        let mut d = vec![0; len];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        b.put_varint(quarter_stream_id).unwrap();\n        b.put_bytes(payload.as_bytes()).unwrap();\n        Ok(Action::SendDatagram { payload: d })\n    } else {\n        let payload_str = Text::new(\"payload bytes:\").prompt()?;\n        Ok(Action::SendDatagram {\n            payload: payload_str.as_bytes().to_owned(),\n        })\n    }\n}\n\nfn validate_wait_period(period: &str) -> SuggestionResult<Validation> {\n    let x = period.parse::<u64>();\n\n    match x {\n        Ok(v) => {\n            let local_conn_timeout =\n                CONNECTION_IDLE_TIMEOUT.with(|v| *v.borrow());\n            if v >= local_conn_timeout {\n                return Ok(Validation::Invalid(ErrorMessage::Custom(format!(\n                    \"wait time >= local connection idle timeout {local_conn_timeout}\"\n                ))));\n            }\n        },\n\n        Err(_) => return Ok(Validation::Invalid(ErrorMessage::Default)),\n    }\n\n    Ok(Validation::Valid)\n}\n\nfn prompt_yes_no(msg: &str) -> InquireResult<bool> {\n    let res = Select::new(msg, vec![NO, YES]).prompt()?;\n\n    Ok(res == YES)\n}\n\nmod errors;\nmod headers;\nmod priority;\nmod settings;\nmod stream;\nmod wait;\n"
  },
  {
    "path": "h3i/src/prompts/h3/priority.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse inquire::error::InquireResult;\nuse inquire::Select;\nuse inquire::Text;\n\nuse super::stream::prompt_fin_stream;\nuse crate::actions::h3::Action;\nuse crate::prompts::h3;\n\nuse crate::quiche;\n\nconst REQUEST: &str = \"request\";\nconst PUSH: &str = \"push\";\n\npub fn prompt_priority() -> InquireResult<Action> {\n    let stream_id = h3::prompt_stream_id()?;\n\n    let ty = prompt_request_or_push()?;\n    let prioritized_element_id = h3::prompt_varint(\"Prioritized Element ID:\")?;\n\n    let priority_field_value = Text::new(\"priority field value:\").prompt()?;\n\n    let frame = if ty.as_str() == REQUEST {\n        quiche::h3::frame::Frame::PriorityUpdateRequest {\n            prioritized_element_id,\n            priority_field_value: priority_field_value.into(),\n        }\n    } else {\n        quiche::h3::frame::Frame::PriorityUpdatePush {\n            prioritized_element_id,\n            priority_field_value: priority_field_value.into(),\n        }\n    };\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame,\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\nfn prompt_request_or_push() -> InquireResult<String> {\n    Ok(Select::new(\"request or push:\", vec![REQUEST, PUSH])\n        .prompt()?\n        .to_string())\n}\n"
  },
  {
    "path": "h3i/src/prompts/h3/settings.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse inquire::error::InquireResult;\nuse inquire::validator::Validation;\nuse inquire::Text;\n\nuse super::squish_suggester;\nuse super::stream::prompt_fin_stream;\nuse super::SuggestionResult;\nuse crate::actions::h3::Action;\nuse crate::prompts::h3;\n\nuse crate::quiche;\n\nconst QPACK_MAX_TABLE_CAPACITY: &str = \"QPACK_MAX_TABLE_CAPACITY\";\nconst MAX_FIELD_SECTION_SIZE: &str = \"MAX_FIELD_SECTION_SIZE\";\nconst QPACK_BLOCKED_STREAMS: &str = \"QPACK_BLOCKED_STREAMS\";\nconst ENABLE_CONNECT_PROTOCOL: &str = \"ENABLE_CONNECT_PROTOCOL\";\nconst H3_DATAGRAM: &str = \"H3_DATAGRAM\";\n\ntype RawSettings = Vec<(u64, u64)>;\n\npub fn prompt_settings() -> InquireResult<Action> {\n    let stream_id = h3::prompt_control_stream_id()?;\n    let settings = settings_read_loop();\n\n    let fin_stream = prompt_fin_stream()?;\n\n    let action = Action::SendFrame {\n        stream_id,\n        fin_stream,\n        frame: quiche::h3::frame::Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            raw: None,\n            additional_settings: Some(settings),\n        },\n        expected_result: Default::default(),\n    };\n\n    Ok(action)\n}\n\nfn settings_read_loop() -> RawSettings {\n    let mut settings = vec![];\n\n    loop {\n        let ty = match Text::new(\"setting type:\")\n            .with_validator(validate_setting_type)\n            .with_autocomplete(&settings_type_suggestor)\n            .with_help_message(\"type 'q!' to stop adding settings\")\n            .prompt()\n        {\n            Ok(h) => {\n                if h == \"q!\" {\n                    break;\n                }\n\n                h\n            },\n            Err(_) => {\n                println!(\"An error happened, stopping.\");\n                break;\n            },\n        };\n\n        let ty = match ty.as_str() {\n            QPACK_MAX_TABLE_CAPACITY => 0x1,\n            MAX_FIELD_SECTION_SIZE => 0x6,\n            QPACK_BLOCKED_STREAMS => 0x7,\n            ENABLE_CONNECT_PROTOCOL => 0x8,\n            H3_DATAGRAM => 0x33,\n\n            v => v.parse::<u64>().unwrap(),\n        };\n\n        let value = Text::new(\"setting value:\")\n            .with_validator(h3::validate_varint)\n            .prompt()\n            .expect(\"An error happened, stopping.\")\n            .parse::<u64>()\n            .unwrap();\n\n        settings.push((ty, value));\n    }\n\n    settings\n}\n\nfn validate_setting_type(id: &str) -> SuggestionResult<Validation> {\n    if matches!(\n        id,\n        \"q!\" | QPACK_MAX_TABLE_CAPACITY |\n            MAX_FIELD_SECTION_SIZE |\n            QPACK_BLOCKED_STREAMS |\n            ENABLE_CONNECT_PROTOCOL |\n            H3_DATAGRAM\n    ) {\n        return Ok(Validation::Valid);\n    }\n\n    h3::validate_varint(id)\n}\n\nfn settings_type_suggestor(val: &str) -> SuggestionResult<Vec<String>> {\n    let suggestions = [\n        QPACK_MAX_TABLE_CAPACITY,\n        MAX_FIELD_SECTION_SIZE,\n        QPACK_BLOCKED_STREAMS,\n        ENABLE_CONNECT_PROTOCOL,\n        H3_DATAGRAM,\n    ];\n\n    squish_suggester(&suggestions, val)\n}\n"
  },
  {
    "path": "h3i/src/prompts/h3/stream.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse inquire::error::InquireResult;\nuse inquire::validator::Validation;\nuse inquire::Text;\n\nuse crate::actions::h3::Action;\nuse crate::prompts::h3;\nuse crate::prompts::h3::errors::prompt_transport_or_app_error;\nuse crate::prompts::h3::prompt_yes_no;\nuse crate::StreamIdAllocator;\n\nuse super::squish_suggester;\nuse super::SuggestionResult;\nuse super::AUTO_PICK;\nuse super::EMPTY_PICKS;\nuse super::ESC_TO_RET;\nuse super::STREAM_ID_PROMPT;\n\nconst CONTROL_STREAM: &str = \"Control Stream\";\nconst PUSH_STREAM: &str = \"Push Stream\";\nconst QPACK_ENCODER: &str = \"QPACK Encoder Stream\";\nconst QPACK_DECODER: &str = \"QPACK Decoder Stream\";\n\nfn validate_stream_id(id: &str) -> SuggestionResult<Validation> {\n    if id.is_empty() {\n        return Ok(Validation::Valid);\n    }\n\n    h3::validate_varint(id)\n}\n\npub fn autopick_stream_id(\n    sid_alloc: &mut StreamIdAllocator,\n) -> InquireResult<u64> {\n    let stream_id = Text::new(STREAM_ID_PROMPT)\n        .with_placeholder(EMPTY_PICKS)\n        .with_help_message(ESC_TO_RET)\n        .with_validator(validate_stream_id)\n        .prompt()?;\n\n    Ok(match stream_id.as_str() {\n        \"\" => {\n            let id = sid_alloc.take_next_id();\n            println!(\"{AUTO_PICK}={id}\");\n            id\n        },\n\n        _ => stream_id.parse::<u64>().unwrap(),\n    })\n}\n\npub fn prompt_open_uni_stream(\n    sid_alloc: &mut StreamIdAllocator,\n) -> InquireResult<Action> {\n    let stream_id = autopick_stream_id(sid_alloc)?;\n    let stream_type = Text::new(\"stream type:\")\n        .with_validator(validate_stream_type)\n        .with_autocomplete(&stream_type_suggestor)\n        .prompt()?;\n\n    let ty = match stream_type.as_str() {\n        CONTROL_STREAM => 0x0,\n        PUSH_STREAM => 0x1,\n        QPACK_ENCODER => 0x2,\n        QPACK_DECODER => 0x3,\n        _ => stream_type.parse::<u64>().unwrap(),\n    };\n\n    let fin_stream = prompt_fin_stream()?;\n\n    Ok(Action::OpenUniStream {\n        stream_id,\n        fin_stream,\n        stream_type: ty,\n        expected_result: Default::default(),\n    })\n}\n\nfn validate_stream_type(id: &str) -> SuggestionResult<Validation> {\n    if matches!(\n        id,\n        CONTROL_STREAM | PUSH_STREAM | QPACK_ENCODER | QPACK_DECODER\n    ) {\n        return Ok(Validation::Valid);\n    }\n\n    h3::validate_varint(id)\n}\n\nfn stream_type_suggestor(val: &str) -> SuggestionResult<Vec<String>> {\n    let suggestions = [CONTROL_STREAM, PUSH_STREAM, QPACK_ENCODER, QPACK_DECODER];\n\n    squish_suggester(&suggestions, val)\n}\n\npub fn prompt_fin_stream() -> InquireResult<bool> {\n    prompt_yes_no(\"fin stream:\")\n}\n\npub fn prompt_reset_stream() -> InquireResult<Action> {\n    let (stream_id, error_code) = prompt_close_stream()?;\n\n    Ok(Action::ResetStream {\n        stream_id,\n        error_code,\n    })\n}\n\npub fn prompt_stop_sending() -> InquireResult<Action> {\n    let (stream_id, error_code) = prompt_close_stream()?;\n\n    Ok(Action::StopSending {\n        stream_id,\n        error_code,\n    })\n}\n\nfn prompt_close_stream() -> InquireResult<(u64, u64)> {\n    let id = h3::prompt_stream_id()?;\n\n    let (_, error_code) = prompt_transport_or_app_error()?;\n\n    Ok((id, error_code))\n}\n"
  },
  {
    "path": "h3i/src/prompts/h3/wait.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Duration;\n\nuse inquire::error::InquireResult;\nuse inquire::validator::Validation;\nuse inquire::Text;\n\nuse crate::actions::h3::Action;\nuse crate::actions::h3::StreamEvent;\nuse crate::actions::h3::StreamEventType;\nuse crate::actions::h3::WaitType;\n\nuse super::prompt_stream_id;\nuse super::squish_suggester;\nuse super::validate_wait_period;\nuse super::SuggestionResult;\n\nconst DURATION: &str = \"duration\";\nconst HEADERS: &str = \"headers\";\nconst DATA: &str = \"data\";\nconst FINISHED: &str = \"stream finished\";\n\npub fn prompt_wait() -> InquireResult<Action> {\n    let wait_type = Text::new(\"wait type:\")\n        .with_autocomplete(&wait_type_suggestor)\n        .with_validator(wait_type_validator)\n        .prompt()?;\n\n    let actual = match wait_type.as_str() {\n        DURATION => Some(prompt_wait_period()),\n        t @ (HEADERS | DATA | FINISHED) => Some(prompt_stream_wait(t)),\n        _ => None,\n    };\n\n    let action = Action::Wait {\n        // unwrap should be safe due to validation\n        wait_type: actual.unwrap()?,\n    };\n\n    Ok(action)\n}\n\nfn wait_type_suggestor(val: &str) -> SuggestionResult<Vec<String>> {\n    let suggestions = [DURATION, HEADERS, DATA, FINISHED];\n\n    squish_suggester(&suggestions, val)\n}\n\nfn wait_type_validator(wait_type: &str) -> SuggestionResult<Validation> {\n    match wait_type {\n        DURATION | HEADERS | DATA | FINISHED => Ok(Validation::Valid),\n        _ => Ok(Validation::Invalid(\n            inquire::validator::ErrorMessage::Default,\n        )),\n    }\n}\n\nfn prompt_stream_wait(stream_wait_type: &str) -> InquireResult<WaitType> {\n    let stream_id = prompt_stream_id()?;\n\n    let event_type = if let HEADERS = stream_wait_type {\n        Some(StreamEventType::Headers)\n    } else if let DATA = stream_wait_type {\n        Some(StreamEventType::Data)\n    } else if let FINISHED = stream_wait_type {\n        Some(StreamEventType::Finished)\n    } else {\n        None\n    }\n    // If somehow we've gotten an invalid input, we can panic. This is post validation so that\n    // shouldn't happen\n    .unwrap();\n\n    Ok(WaitType::StreamEvent(StreamEvent {\n        stream_id,\n        event_type,\n    }))\n}\n\npub fn prompt_wait_period() -> InquireResult<WaitType> {\n    let period = Text::new(\"wait period (ms):\")\n        .with_validator(validate_wait_period)\n        .prompt()?;\n\n    // period is already validated so unwrap always succeeds\n    let period = Duration::from_millis(period.parse::<u64>().unwrap());\n\n    Ok(WaitType::WaitDuration(period))\n}\n"
  },
  {
    "path": "h3i/src/prompts/mod.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! A collection of interactive CLI prompts based on [inquire].\n\npub mod h3;\n"
  },
  {
    "path": "h3i/src/recordreplay/mod.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Support for recording h3i Actions and replaying them.\npub mod qlog;\n"
  },
  {
    "path": "h3i/src/recordreplay/qlog.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\n\nuse crate::quiche;\nuse qlog::events::http3::FrameCreated;\nuse qlog::events::http3::Http3Frame;\nuse qlog::events::http3::HttpHeader;\nuse qlog::events::http3::Initiator;\nuse qlog::events::http3::StreamTypeSet;\nuse qlog::events::quic::ErrorSpace;\nuse qlog::events::quic::PacketSent;\nuse qlog::events::quic::QuicFrame;\nuse qlog::events::Event;\nuse qlog::events::EventData;\nuse qlog::events::ExData;\nuse qlog::events::JsonEvent;\nuse qlog::events::RawInfo;\nuse quiche::h3::frame::Frame;\nuse quiche::h3::NameValue;\n\nuse serde_json::json;\n\nuse smallvec::smallvec;\n\nuse crate::actions::h3::Action;\nuse crate::actions::h3::WaitType;\nuse crate::encode_header_block;\nuse crate::encode_header_block_literal;\nuse crate::fake_packet_sent;\nuse crate::HTTP3_CONTROL_STREAM_TYPE_ID;\nuse crate::HTTP3_PUSH_STREAM_TYPE_ID;\nuse crate::QPACK_DECODER_STREAM_TYPE_ID;\nuse crate::QPACK_ENCODER_STREAM_TYPE_ID;\n\n/// A qlog event representation using either the official RFC format or the\n/// catch-al JSON event.\npub enum QlogEvent {\n    Event {\n        data: Box<EventData>,\n        ex_data: ExData,\n    },\n    JsonEvent(JsonEvent),\n}\n\n/// A collection of [QlogEvent]s.\npub type QlogEvents = Vec<QlogEvent>;\n\n/// A collection of [Action]s.\npub struct H3Actions(pub Vec<Action>);\n\n/// A qlog HTTP/3 [FrameCreated] event, with [ExData].\npub struct H3FrameCreatedEx {\n    frame_created: FrameCreated,\n    ex_data: ExData,\n}\n\nimpl From<&Action> for QlogEvents {\n    fn from(action: &Action) -> Self {\n        match action {\n            Action::SendFrame {\n                stream_id,\n                fin_stream,\n                frame,\n                ..\n            } => {\n                let frame_ev = EventData::Http3FrameCreated(FrameCreated {\n                    stream_id: *stream_id,\n                    frame: frame.to_qlog(),\n                    ..Default::default()\n                });\n\n                let mut ex = BTreeMap::new();\n\n                if *fin_stream {\n                    ex.insert(\"fin_stream\".to_string(), json!(true));\n                }\n\n                vec![QlogEvent::Event {\n                    data: Box::new(frame_ev),\n                    ex_data: ex,\n                }]\n            },\n\n            Action::SendHeadersFrame {\n                stream_id,\n                fin_stream,\n                headers,\n                literal_headers,\n                ..\n            } => {\n                let qlog_headers = headers\n                    .iter()\n                    .map(|h| qlog::events::http3::HttpHeader {\n                        name: Some(\n                            String::from_utf8_lossy(h.name()).into_owned(),\n                        ),\n                        name_bytes: None,\n                        value: Some(\n                            String::from_utf8_lossy(h.value()).into_owned(),\n                        ),\n                        value_bytes: None,\n                    })\n                    .collect();\n\n                let frame = Http3Frame::Headers {\n                    headers: qlog_headers,\n                };\n\n                let frame_ev = EventData::Http3FrameCreated(FrameCreated {\n                    stream_id: *stream_id,\n                    frame,\n                    ..Default::default()\n                });\n\n                let mut ex = BTreeMap::new();\n\n                if *fin_stream {\n                    ex.insert(\"fin_stream\".to_string(), json!(true));\n                }\n\n                if *literal_headers {\n                    ex.insert(\"literal_headers\".to_string(), json!(true));\n                }\n\n                vec![QlogEvent::Event {\n                    data: Box::new(frame_ev),\n                    ex_data: ex,\n                }]\n            },\n\n            Action::OpenUniStream {\n                stream_id,\n                fin_stream,\n                stream_type,\n                ..\n            } => {\n                let ty = match *stream_type {\n                    HTTP3_CONTROL_STREAM_TYPE_ID =>\n                        qlog::events::http3::StreamType::Control,\n                    HTTP3_PUSH_STREAM_TYPE_ID =>\n                        qlog::events::http3::StreamType::Push,\n                    QPACK_ENCODER_STREAM_TYPE_ID =>\n                        qlog::events::http3::StreamType::QpackEncode,\n                    QPACK_DECODER_STREAM_TYPE_ID =>\n                        qlog::events::http3::StreamType::QpackDecode,\n\n                    _ => qlog::events::http3::StreamType::Unknown,\n                };\n                let ty_val =\n                    if matches!(ty, qlog::events::http3::StreamType::Unknown) {\n                        Some(*stream_type)\n                    } else {\n                        None\n                    };\n\n                let stream_ev = EventData::Http3StreamTypeSet(StreamTypeSet {\n                    initiator: Some(Initiator::Local),\n                    stream_id: *stream_id,\n                    stream_type: ty,\n                    stream_type_bytes: ty_val,\n                    ..Default::default()\n                });\n                let mut ex = BTreeMap::new();\n\n                if *fin_stream {\n                    ex.insert(\"fin_stream\".to_string(), json!(true));\n                }\n\n                vec![QlogEvent::Event {\n                    data: Box::new(stream_ev),\n                    ex_data: ex,\n                }]\n            },\n\n            Action::StreamBytes {\n                stream_id,\n                fin_stream,\n                bytes,\n                ..\n            } => {\n                let len = bytes.len() as u64;\n                let ev = fake_packet_sent(Some(smallvec![QuicFrame::Stream {\n                    stream_id: *stream_id,\n                    fin: Some(*fin_stream),\n                    // ignore offset\n                    offset: None,\n                    raw: Some(RawInfo {\n                        length: None,\n                        payload_length: Some(len),\n                        data: String::from_utf8(bytes.clone()).ok()\n                    })\n                }]));\n\n                vec![QlogEvent::Event {\n                    data: Box::new(ev),\n                    ex_data: BTreeMap::new(),\n                }]\n            },\n\n            Action::SendDatagram { payload } => {\n                let len = payload.len() as u64;\n                let ev = fake_packet_sent(Some(smallvec![QuicFrame::Datagram {\n                    raw: Some(RawInfo {\n                        length: None,\n                        payload_length: Some(len),\n                        data: String::from_utf8(payload.clone()).ok()\n                    })\n                }]));\n\n                vec![QlogEvent::Event {\n                    data: Box::new(ev),\n                    ex_data: BTreeMap::new(),\n                }]\n            },\n\n            Action::ResetStream {\n                stream_id,\n                error_code,\n            } => {\n                let ev =\n                    fake_packet_sent(Some(smallvec![QuicFrame::ResetStream {\n                        stream_id: *stream_id,\n                        error: qlog::events::ApplicationError::Unknown,\n                        error_code: Some(*error_code),\n                        final_size: 0,\n                        raw: None,\n                    }]));\n                vec![QlogEvent::Event {\n                    data: Box::new(ev),\n                    ex_data: BTreeMap::new(),\n                }]\n            },\n\n            Action::StopSending {\n                stream_id,\n                error_code,\n            } => {\n                let ev =\n                    fake_packet_sent(Some(smallvec![QuicFrame::StopSending {\n                        stream_id: *stream_id,\n                        error: qlog::events::ApplicationError::Unknown,\n                        error_code: Some(*error_code),\n                        raw: None,\n                    }]));\n                vec![QlogEvent::Event {\n                    data: Box::new(ev),\n                    ex_data: BTreeMap::new(),\n                }]\n            },\n\n            Action::Wait { wait_type } => {\n                let name = \"h3i:wait\".into();\n\n                let data = match wait_type {\n                    d @ WaitType::WaitDuration(_) =>\n                        serde_json::to_value(d).unwrap(),\n                    WaitType::StreamEvent(event) =>\n                        serde_json::to_value(event).unwrap(),\n                    d @ WaitType::CanOpenNumStreams(_) =>\n                        serde_json::to_value(d).unwrap(),\n                };\n\n                vec![QlogEvent::JsonEvent(qlog::events::JsonEvent {\n                    time: 0.0,\n                    importance: qlog::events::EventImportance::Core,\n                    name,\n                    data,\n                })]\n            },\n\n            Action::ConnectionClose { error } => {\n                let error_space = if error.is_app {\n                    ErrorSpace::Application\n                } else {\n                    ErrorSpace::Transport\n                };\n\n                let reason = if error.reason.is_empty() {\n                    None\n                } else {\n                    Some(String::from_utf8(error.reason.clone()).unwrap())\n                };\n\n                let ev = fake_packet_sent(Some(smallvec![\n                    QuicFrame::ConnectionClose {\n                        error_space: Some(error_space),\n                        error: None,\n                        error_code: Some(error.error_code),\n                        reason,\n                        reason_bytes: None,\n                        trigger_frame_type: None\n                    }\n                ]));\n\n                vec![QlogEvent::Event {\n                    data: Box::new(ev),\n                    ex_data: BTreeMap::new(),\n                }]\n            },\n\n            Action::FlushPackets => {\n                vec![]\n            },\n        }\n    }\n}\n\npub fn actions_from_qlog(event: Event, host_override: Option<&str>) -> H3Actions {\n    let mut actions = vec![];\n    match &event.data {\n        EventData::QuicPacketSent(ps) => {\n            let packet_actions: H3Actions = ps.into();\n            actions.extend(packet_actions.0);\n        },\n\n        EventData::Http3FrameCreated(fc) => {\n            let mut frame_created = H3FrameCreatedEx {\n                frame_created: fc.clone(),\n                ex_data: event.ex_data.clone(),\n            };\n\n            // Insert custom data so that conversion of frames to Actions can\n            // use it.\n            if let Some(host) = host_override {\n                frame_created\n                    .ex_data\n                    .insert(\"host_override\".into(), host.into());\n            }\n\n            actions.push(frame_created.into());\n        },\n\n        EventData::Http3StreamTypeSet(st) => {\n            let stream_actions = from_qlog_stream_type_set(st, &event.ex_data);\n            actions.extend(stream_actions);\n        },\n\n        _ => (),\n    }\n\n    H3Actions(actions)\n}\n\nimpl From<JsonEvent> for H3Actions {\n    fn from(event: JsonEvent) -> Self {\n        let mut actions = vec![];\n        match event.name.as_ref() {\n            \"h3i:wait\" => {\n                let wait_type =\n                    serde_json::from_value::<WaitType>(event.clone().data);\n\n                if let Ok(wt) = wait_type {\n                    actions.push(Action::Wait { wait_type: wt });\n                } else {\n                    log::debug!(\"couldn't create action from event: {event:?}\");\n                }\n            },\n            _ => unimplemented!(),\n        }\n\n        Self(actions)\n    }\n}\n\nimpl From<&PacketSent> for H3Actions {\n    fn from(ps: &PacketSent) -> Self {\n        let mut actions = vec![];\n        if let Some(frames) = &ps.frames {\n            for frame in frames {\n                match &frame {\n                    // TODO add these\n                    QuicFrame::ResetStream {\n                        stream_id,\n                        error_code,\n                        ..\n                    } => actions.push(Action::ResetStream {\n                        stream_id: *stream_id,\n                        error_code: error_code.unwrap_or_default(),\n                    }),\n\n                    QuicFrame::StopSending {\n                        stream_id,\n                        error_code,\n                        ..\n                    } => actions.push(Action::StopSending {\n                        stream_id: *stream_id,\n                        error_code: error_code.unwrap_or_default(),\n                    }),\n\n                    QuicFrame::ConnectionClose {\n                        error_space,\n                        error_code,\n                        reason,\n                        ..\n                    } => {\n                        let is_app = matches!(\n                            error_space.as_ref().expect(\n                                \"invalid CC frame in qlog input, no error space\"\n                            ),\n                            ErrorSpace::Application\n                        );\n\n                        actions.push(Action::ConnectionClose {\n                            error: quiche::ConnectionError {\n                                is_app,\n                                // TODO: remove unwrap when https://github.com/cloudflare/quiche/issues/1731\n                                // is done\n                                error_code: error_code.expect(\"invalid CC frame in qlog input, no error code\"),\n                                reason: reason\n                                    .as_ref()\n                                    .map(|s| s.as_bytes().to_vec())\n                                    .unwrap_or_default(),\n                            },\n                        })\n                    },\n\n                    QuicFrame::Stream { stream_id, fin, .. } => {\n                        let fin = fin.unwrap_or_default();\n\n                        if fin {\n                            actions.push(Action::StreamBytes {\n                                stream_id: *stream_id,\n                                fin_stream: true,\n                                bytes: vec![],\n                                expected_result: Default::default(),\n                            });\n                        }\n                    },\n\n                    QuicFrame::Datagram { raw, .. } => {\n                        actions.push(Action::SendDatagram {\n                            payload: raw\n                                .clone()\n                                .unwrap_or_default()\n                                .data\n                                .unwrap_or_default()\n                                .into(),\n                        });\n                    },\n                    _ => (),\n                }\n            }\n        }\n\n        Self(actions)\n    }\n}\n\nfn map_header(\n    hdr: &HttpHeader, host_override: Option<&str>,\n) -> quiche::h3::Header {\n    let name = hdr.name.as_deref().unwrap_or(\"\");\n    let value = hdr.value.as_deref().unwrap_or(\"\");\n\n    if name.eq_ignore_ascii_case(\":authority\") ||\n        name.eq_ignore_ascii_case(\"host\")\n    {\n        if let Some(host) = host_override {\n            return quiche::h3::Header::new(name.as_bytes(), host.as_bytes());\n        }\n    }\n\n    quiche::h3::Header::new(name.as_bytes(), value.as_bytes())\n}\n\nimpl From<H3FrameCreatedEx> for Action {\n    fn from(value: H3FrameCreatedEx) -> Self {\n        let stream_id = value.frame_created.stream_id;\n        let fin_stream = value\n            .ex_data\n            .get(\"fin_stream\")\n            .unwrap_or(&serde_json::Value::Null)\n            .as_bool()\n            .unwrap_or_default();\n        let host_override = value\n            .ex_data\n            .get(\"host_override\")\n            .unwrap_or(&serde_json::Value::Null)\n            .as_str();\n\n        let ret = match &value.frame_created.frame {\n            Http3Frame::Settings { settings } => {\n                let mut raw_settings = vec![];\n                let mut additional_settings = vec![];\n                // This is ugly but it reflects ambiguity in the qlog\n                // specs.\n                for s in settings {\n                    let name = s.name.as_deref().unwrap_or(\"\");\n                    match name {\n                        \"MAX_FIELD_SECTION_SIZE\" =>\n                            raw_settings.push((0x6, s.value)),\n                        \"QPACK_MAX_TABLE_CAPACITY\" =>\n                            raw_settings.push((0x1, s.value)),\n                        \"QPACK_BLOCKED_STREAMS\" =>\n                            raw_settings.push((0x7, s.value)),\n                        \"SETTINGS_ENABLE_CONNECT_PROTOCOL\" =>\n                            raw_settings.push((0x8, s.value)),\n                        \"H3_DATAGRAM\" => raw_settings.push((0x33, s.value)),\n\n                        _ =>\n                            if let Some(ty) = s.name_bytes {\n                                raw_settings.push((ty, s.value));\n                                additional_settings.push((ty, s.value));\n                            } else if let Ok(ty) = name.parse::<u64>() {\n                                raw_settings.push((ty, s.value));\n                                additional_settings.push((ty, s.value));\n                            },\n                    }\n                }\n\n                Action::SendFrame {\n                    stream_id,\n                    fin_stream,\n                    frame: Frame::Settings {\n                        max_field_section_size: None,\n                        qpack_max_table_capacity: None,\n                        qpack_blocked_streams: None,\n                        connect_protocol_enabled: None,\n                        h3_datagram: None,\n                        grease: None,\n                        raw: Some(raw_settings),\n                        additional_settings: Some(additional_settings),\n                    },\n                    expected_result: Default::default(),\n                }\n            },\n\n            Http3Frame::Headers { headers } => {\n                let hdrs: Vec<quiche::h3::Header> = headers\n                    .iter()\n                    .map(|h| map_header(h, host_override))\n                    .collect();\n\n                let literal_headers = value\n                    .ex_data\n                    .get(\"literal_headers\")\n                    .unwrap_or(&serde_json::Value::Null)\n                    .as_bool()\n                    .unwrap_or_default();\n\n                let header_block = if literal_headers {\n                    encode_header_block_literal(&hdrs).unwrap()\n                } else {\n                    encode_header_block(&hdrs).unwrap()\n                };\n\n                Action::SendHeadersFrame {\n                    stream_id,\n                    fin_stream,\n                    literal_headers,\n                    headers: hdrs,\n                    frame: Frame::Headers { header_block },\n                    expected_result: Default::default(),\n                }\n            },\n\n            Http3Frame::Data { raw } => {\n                let mut payload = vec![];\n                if let Some(r) = raw {\n                    payload = r\n                        .data\n                        .clone()\n                        .unwrap_or(\"\".to_string())\n                        .as_bytes()\n                        .to_vec();\n                }\n\n                Action::SendFrame {\n                    stream_id,\n                    fin_stream,\n                    frame: Frame::Data { payload },\n                    expected_result: Default::default(),\n                }\n            },\n\n            Http3Frame::Goaway { id } => Action::SendFrame {\n                stream_id,\n                fin_stream,\n                frame: Frame::GoAway { id: *id },\n                expected_result: Default::default(),\n            },\n\n            _ => unimplemented!(),\n        };\n\n        ret\n    }\n}\n\nfn from_qlog_stream_type_set(\n    st: &StreamTypeSet, ex_data: &ExData,\n) -> Vec<Action> {\n    let mut actions = vec![];\n    let fin_stream = parse_ex_data(ex_data);\n    let stream_type = match st.stream_type {\n        qlog::events::http3::StreamType::Control => Some(0x0),\n        qlog::events::http3::StreamType::Push => Some(0x1),\n        qlog::events::http3::StreamType::QpackEncode => Some(0x2),\n        qlog::events::http3::StreamType::QpackDecode => Some(0x3),\n        qlog::events::http3::StreamType::Reserved |\n        qlog::events::http3::StreamType::Unknown => st.stream_type_bytes,\n        _ => None,\n    };\n\n    if let Some(ty) = stream_type {\n        actions.push(Action::OpenUniStream {\n            stream_id: st.stream_id,\n            fin_stream,\n            stream_type: ty,\n            expected_result: Default::default(),\n        })\n    }\n\n    actions\n}\n\nfn parse_ex_data(ex_data: &ExData) -> bool {\n    ex_data\n        .get(\"fin_stream\")\n        .unwrap_or(&serde_json::Value::Null)\n        .as_bool()\n        .unwrap_or_default()\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::actions::h3::StreamEvent;\n    use crate::actions::h3::StreamEventType;\n    use crate::encode_header_block_literal;\n    use std::time::Duration;\n\n    use super::*;\n    use quiche::h3::Header;\n    use serde_json;\n\n    const NOW: f64 = 123.0;\n    const H3I_WAIT: &str = \"h3i:wait\";\n\n    #[test]\n    fn ser_duration_wait() {\n        let ev = JsonEvent {\n            time: NOW,\n            importance: qlog::events::EventImportance::Core,\n            name: H3I_WAIT.to_string(),\n            data: serde_json::to_value(WaitType::WaitDuration(\n                Duration::from_millis(12345),\n            ))\n            .unwrap(),\n        };\n        let serialized = serde_json::to_string(&ev);\n\n        let expected =\n            r#\"{\"time\":123.0,\"name\":\"h3i:wait\",\"data\":{\"duration\":12345.0}}\"#;\n        assert_eq!(&serialized.unwrap(), expected);\n    }\n\n    #[test]\n    fn deser_duration_wait() {\n        let ev = JsonEvent {\n            time: NOW,\n            importance: qlog::events::EventImportance::Core,\n            name: H3I_WAIT.to_string(),\n            data: serde_json::to_value(WaitType::WaitDuration(\n                Duration::from_millis(12345),\n            ))\n            .unwrap(),\n        };\n\n        let expected =\n            r#\"{\"time\":123.0,\"name\":\"h3i:wait\",\"data\":{\"duration\":12345.0}}\"#;\n        let deser = serde_json::from_str::<JsonEvent>(expected).unwrap();\n        assert_eq!(deser.data, ev.data);\n    }\n\n    #[test]\n    fn ser_stream_wait() {\n        let expected = r#\"{\"time\":123.0,\"name\":\"h3i:wait\",\"data\":{\"stream_id\":0,\"type\":\"data\"}}\"#;\n        let ev = JsonEvent {\n            time: NOW,\n            importance: qlog::events::EventImportance::Core,\n            name: H3I_WAIT.to_string(),\n            data: serde_json::to_value(StreamEvent {\n                stream_id: 0,\n                event_type: StreamEventType::Data,\n            })\n            .unwrap(),\n        };\n\n        let serialized = serde_json::to_string(&ev);\n        assert_eq!(&serialized.unwrap(), expected);\n    }\n\n    #[test]\n    fn deser_stream_wait() {\n        let ev = JsonEvent {\n            time: NOW,\n            importance: qlog::events::EventImportance::Core,\n            name: H3I_WAIT.to_string(),\n            data: serde_json::to_value(StreamEvent {\n                stream_id: 0,\n                event_type: StreamEventType::Data,\n            })\n            .unwrap(),\n        };\n\n        let expected = r#\"{\"time\":123.0,\"name\":\"h3i:wait\",\"data\":{\"stream_id\":0,\"type\":\"data\"}}\"#;\n        let deser = serde_json::from_str::<JsonEvent>(expected).unwrap();\n        assert_eq!(deser.data, ev.data);\n    }\n\n    #[test]\n    fn deser_http_headers_to_action() {\n        let serialized = r#\"{\"time\":0.074725,\"name\":\"http3:frame_created\",\"data\":{\"stream_id\":0,\"frame\":{\"frame_type\":\"headers\",\"headers\":[{\"name\":\":method\",\"value\":\"GET\"},{\"name\":\":authority\",\"value\":\"example.net\"},{\"name\":\":path\",\"value\":\"/\"},{\"name\":\":scheme\",\"value\":\"https\"}]}},\"fin_stream\":true}\"#;\n        let deserialized = serde_json::from_str::<Event>(serialized).unwrap();\n        let actions = actions_from_qlog(deserialized, None);\n        assert!(actions.0.len() == 1);\n\n        let headers = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":authority\", b\"example.net\"),\n            Header::new(b\":path\", b\"/\"),\n            Header::new(b\":scheme\", b\"https\"),\n        ];\n        let header_block = encode_header_block(&headers).unwrap();\n        let frame = Frame::Headers { header_block };\n        let expected = Action::SendHeadersFrame {\n            stream_id: 0,\n            fin_stream: true,\n            literal_headers: false,\n            headers,\n            frame,\n            expected_result: Default::default(),\n        };\n\n        assert_eq!(actions.0[0], expected);\n    }\n\n    #[test]\n    fn deser_http_headers_host_overrid_to_action() {\n        let serialized = r#\"{\"time\":0.074725,\"name\":\"http3:frame_created\",\"data\":{\"stream_id\":0,\"frame\":{\"frame_type\":\"headers\",\"headers\":[{\"name\":\":method\",\"value\":\"GET\"},{\"name\":\":authority\",\"value\":\"bla.com\"},{\"name\":\":path\",\"value\":\"/\"},{\"name\":\":scheme\",\"value\":\"https\"}]}},\"fin_stream\":true}\"#;\n        let deserialized = serde_json::from_str::<Event>(serialized).unwrap();\n        let actions = actions_from_qlog(deserialized, Some(\"example.org\"));\n        assert!(actions.0.len() == 1);\n\n        let headers = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":authority\", b\"example.org\"),\n            Header::new(b\":path\", b\"/\"),\n            Header::new(b\":scheme\", b\"https\"),\n        ];\n        let header_block = encode_header_block(&headers).unwrap();\n        let frame = Frame::Headers { header_block };\n        let expected = Action::SendHeadersFrame {\n            stream_id: 0,\n            fin_stream: true,\n            literal_headers: false,\n            headers,\n            frame,\n            expected_result: Default::default(),\n        };\n\n        assert_eq!(actions.0[0], expected);\n    }\n\n    #[test]\n    fn deser_http_headers_literal_to_action() {\n        let serialized = r#\"{\"time\":0.074725,\"name\":\"http3:frame_created\",\"data\":{\"stream_id\":0,\"frame\":{\"frame_type\":\"headers\",\"headers\":[{\"name\":\":method\",\"value\":\"GET\"},{\"name\":\":authority\",\"value\":\"bla.com\"},{\"name\":\":path\",\"value\":\"/\"},{\"name\":\":scheme\",\"value\":\"https\"},{\"name\":\"Foo\",\"value\":\"bar\"}]}},\"fin_stream\":true,\"literal_headers\":true}\"#;\n        let deserialized = serde_json::from_str::<Event>(serialized).unwrap();\n        let actions = actions_from_qlog(deserialized, None);\n        assert!(actions.0.len() == 1);\n\n        let headers = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":authority\", b\"bla.com\"),\n            Header::new(b\":path\", b\"/\"),\n            Header::new(b\":scheme\", b\"https\"),\n            Header::new(b\"Foo\", b\"bar\"),\n        ];\n        let header_block = encode_header_block_literal(&headers).unwrap();\n        let frame = Frame::Headers { header_block };\n        let expected = Action::SendHeadersFrame {\n            stream_id: 0,\n            fin_stream: true,\n            literal_headers: true,\n            headers,\n            frame,\n            expected_result: Default::default(),\n        };\n\n        assert_eq!(actions.0[0], expected);\n    }\n}\n"
  },
  {
    "path": "netlog/Cargo.toml",
    "content": "[package]\nname = \"netlog\"\nversion = \"0.1.0\"\ndescription = \"Chrome netlog parsing\"\nrepository = { workspace = true }\nauthors = [\"Lucas Pardue <lucas@lucaspardue.com>\"]\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = [\"quic\", \"http2\", \"http3\"]\ncategories = { workspace = true }\nreadme = \"README.md\"\n\n[dependencies]\nlog = { workspace = true }\nregex = { workspace = true }\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true, features = [\"preserve_order\"] }\n"
  },
  {
    "path": "netlog/README.md",
    "content": "# netlog\n\nThe netlog crate is a reverse-engineered deserializer for the Chrome\n[netlog] format. It supports QUIC and HTTP(/2 and /3) events.\n\n## Overview\n\nChromium-based browsers allow users to enable detailed logging, netlog,\nwhich is useful for debugging interoperability or performance issues. A\nnetlog file uses a kind of line-delimited JSON format. The first line\ncontains \"constants\", which are specific to the version of the software used\nto generate the log. These constants are used for a form of compressed\nencoding for the netlog events that appear on each subsequent newline.\n\nThis crate supports parsing a netlog file and converting a subset of netlog\nevents into Rust structures, via Serde.\n\n## Example usage\n\nAssuming a netlog file name of `chrome-net-export-log-error.json`, the first\ntask is to create a `BufReader` for the file and initialize the netlog\nconstants.\n\n```rust\nuse netlog::read_netlog_constants;\nuse std::fs::File;\nuse std::io::BufReader;\n\nlet mut reader =\n    BufReader::new(File::open(\"chrome-net-export-log-error.json\").unwrap());\n\nlet constants = read_netlog_constants(&mut reader).unwrap();\n```\n\nThen move on to parsing the netlog file until the end.\n\n```rust\nuse netlog::read_netlog_record;\nuse netlog::EventHeader;\nuse netlog::h2::Http2SessionEvent;\nuse netlog::quic::QuicSessionEvent;\n// The second line of a netlog is `\"events\" [`, which can be skipped over.\nread_netlog_record(&mut reader);\n\nwhile let Some(record) = read_netlog_record(&mut reader) {\n    let res: Result<EventHeader, serde_json::Error> =\n        serde_json::from_slice(&record);\n\n    match res {\n        Ok(mut event_hdr) => {\n            event_hdr.populate_strings(&constants);\n            event_hdr.time_num = event_hdr.time.parse::<u64>().unwrap();\n\n            // Netlogs can hold many different sessions.\n            // Application might want to track these separately\n            if event_hdr.phase_string == \"PHASE_BEGIN\" {\n                match event_hdr.ty_string.as_str() {\n                    \"HTTP2_SESSION\" => {\n                        let ev: Http2SessionEvent =\n                            serde_json::from_slice(&record).unwrap();\n                        // Handle new session event ...\n                    },\n                    \"QUIC_SESSION\" => {\n                        let ev: QuicSessionEvent =\n                            serde_json::from_slice(&record).unwrap();\n                        // Handle new session event ...\n                    },\n\n                    // Ignore others\n                    _ => (),\n                }\n            }\n\n            // Try to parse other events.\n            if let Some(ev) = netlog::parse_event(&event_hdr, &record) {\n                // Handle parsed event.\n            }\n        },\n\n        Err(e) => {\n            println!(\"Error deserializing: {}\", e);\n            println!(\"input value {}\", String::from_utf8_lossy(&record));\n        },\n    }\n}\n```\n\n[netlog]: (https://www.chromium.org/developers/design-documents/network-stack/netlog/)\n"
  },
  {
    "path": "netlog/src/constants.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::HashMap;\n\nuse serde::Deserialize;\n\n#[derive(Deserialize, Debug, Default)]\npub struct ClientInfo {\n    pub cl: Option<String>,\n    pub command_line: Option<String>,\n    pub name: Option<String>,\n    pub official: Option<String>,\n    pub os_type: Option<String>,\n    pub version: Option<String>,\n    pub version_mod: Option<String>,\n}\n\n#[derive(Deserialize, Debug)]\n#[serde(rename_all = \"camelCase\")]\npub struct Constants {\n    #[serde(skip)]\n    pub active_field_trial_groups_id_keyed: Vec<String>,\n    #[serde(skip)]\n    pub address_family_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub cert_path_builder_digest_policy_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub cert_status_flag_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub cert_verifier_flags_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub certificate_trust_type_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub dns_query_type_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub load_flag_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub load_state_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub log_event_phase_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub log_event_types_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub log_source_type_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub net_error_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub quic_error_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub quic_rst_stream_error_id_keyed: HashMap<i64, String>,\n    #[serde(skip)]\n    pub secure_dns_mode_id_keyed: HashMap<i64, String>,\n\n    pub active_field_trial_groups: Vec<String>,\n    pub address_family: HashMap<String, i64>,\n    pub cert_path_builder_digest_policy: HashMap<String, i64>,\n    pub cert_status_flag: HashMap<String, i64>,\n    pub cert_verifier_flags: HashMap<String, i64>,\n    pub certificate_trust_type: Option<HashMap<String, i64>>,\n    pub client_info: ClientInfo,\n    pub dns_query_type: HashMap<String, i64>,\n    pub load_flag: HashMap<String, i64>,\n    pub load_state: HashMap<String, i64>,\n    pub log_event_phase: HashMap<String, i64>,\n    pub log_event_types: HashMap<String, i64>,\n    pub log_format_version: u64,\n    pub log_source_type: HashMap<String, i64>,\n    pub net_error: HashMap<String, i64>,\n    pub quic_error: HashMap<String, i64>,\n    pub quic_rst_stream_error: HashMap<String, i64>,\n    pub secure_dns_mode: HashMap<String, i64>,\n\n    pub time_tick_offset: u64,\n}\n\n#[derive(Deserialize, Debug)]\npub struct ConstantsLine {\n    pub constants: Constants,\n}\n\nimpl Constants {\n    pub fn populate_id_keyed(&mut self) {\n        self.address_family_id_keyed = self\n            .address_family\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.cert_path_builder_digest_policy_id_keyed = self\n            .cert_path_builder_digest_policy\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.cert_status_flag_id_keyed = self\n            .cert_status_flag\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.cert_verifier_flags_id_keyed = self\n            .cert_verifier_flags\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        if let Some(trust_map) = &self.certificate_trust_type {\n            self.certificate_trust_type_id_keyed = trust_map\n                .iter()\n                .map(|(k, v)| (*v, k.clone()))\n                .collect::<HashMap<_, _>>();\n        }\n\n        self.dns_query_type_id_keyed = self\n            .dns_query_type\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.load_flag_id_keyed = self\n            .load_flag\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.load_state_id_keyed = self\n            .load_state\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.log_event_phase_id_keyed = self\n            .log_event_phase\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.log_event_types_id_keyed = self\n            .log_event_types\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.log_source_type_id_keyed = self\n            .log_source_type\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.net_error_id_keyed = self\n            .net_error\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.quic_error_id_keyed = self\n            .quic_error\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.quic_rst_stream_error_id_keyed = self\n            .quic_rst_stream_error\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n\n        self.secure_dns_mode_id_keyed = self\n            .secure_dns_mode\n            .iter()\n            .map(|(k, v)| (*v, k.clone()))\n            .collect::<HashMap<_, _>>();\n    }\n}\n"
  },
  {
    "path": "netlog/src/h2.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse serde::Deserialize;\n\nuse super::EventHeader;\nuse super::SourceDependency;\n\nuse regex::Regex;\nuse std::convert::TryFrom;\n\n#[derive(Debug)]\npub enum Event {\n    Http2Session(Http2SessionEvent),\n    Http2SessionInitialized(Http2SessionInitializedEvent),\n    Http2SessionSendSettings(Http2SessionSendSettingsEvent),\n    Http2SessionRecvSetting(Http2SessionRecvSettingEvent),\n    Http2SessionSendHeaders(Http2SessionSendHeadersEvent),\n    Http2SessionSendData(Http2SessionSendDataEvent),\n    Http2SessionRecvHeaders(Http2SessionRecvHeadersEvent),\n    Http2SessionRecvData(Http2SessionRecvDataEvent),\n    Http2SessionUpdateRecvWindow(Http2SessionUpdateRecvWindowEvent),\n    Http2SessionUpdateSendWindow(Http2SessionUpdateSendWindowEvent),\n    Http2SessionUpdateStreamsSendWindowSize(\n        Http2SessionUpdateStreamsSendWindowSizeEvent,\n    ),\n    Http2SessionSendWindowUpdate(Http2SessionSendWindowUpdateEvent),\n    Http2SessionRecvWindowUpdate(Http2SessionRecvWindowUpdateEvent),\n    Http2StreamUpdateSendWindow(Http2StreamUpdateSendWindowEvent),\n    Http2StreamUpdateRecvWindow(Http2StreamUpdateRecvWindowEvent),\n    Http2StreamStalledByStreamSendWindow(\n        Http2StreamStalledByStreamSendWindowEvent,\n    ),\n    Http2SessionPing(Http2SessionPingEvent),\n    Http2SessionSendRstStream(Http2SessionSendRstStreamEvent),\n    Http2SessionRecvRstStream(Http2SessionRecvRstStreamEvent),\n    Http2SessionRecvGoaway(Http2SessionRecvGoawayEvent),\n    Http2SessionClose(Http2SessionCloseEvent),\n    Http2SessionStalledMaxStreams(Htt2SessionStalledMaxStreamsEvent),\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionParams {\n    pub host: String,\n    pub proxy: String,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionEvent {\n    pub params: Http2SessionParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionInitializedParams {\n    pub protocol: String,\n    pub source_dependency: SourceDependency,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionInitializedEvent {\n    pub params: Http2SessionInitializedParams,\n}\n\n// Example: \"[id:1 (SETTINGS_HEADER_TABLE_SIZE) value:65536]\"\npub const H2_SEND_SETTINGS_PATTERN: &str = r\"^\\[id:(\\d+) \\(\\w+\\) value:(\\d+)\\]\";\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendSettingsParams {\n    pub settings: Vec<String>,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendSettingsEvent {\n    pub params: Http2SessionSendSettingsParams,\n}\n\n// Example: \"3 (SETTINGS_MAX_CONCURRENT_STREAMS)\"\npub const H2_RECV_SETTING_PATTERN: &str = r\"^(\\d+) \\(\\w+\\)\";\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvSettingParams {\n    pub id: String,\n    pub value: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvSettingEvent {\n    pub params: Http2SessionRecvSettingParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendHeadersParams {\n    pub stream_id: u32,\n    pub headers: Vec<String>,\n    pub fin: bool,\n\n    pub has_priority: bool,\n    pub exclusive: bool,\n    pub weight: u16, /* TODO: this is really an 8-bit integer but the number\n                      * range is 1-256 so u8 fails */\n    pub parent_stream_id: u32,\n\n    pub source_dependency: SourceDependency,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendHeadersEvent {\n    pub params: Http2SessionSendHeadersParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendDataParams {\n    pub stream_id: u32,\n    pub size: u32,\n    pub fin: bool,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendDataEvent {\n    pub params: Http2SessionSendDataParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvHeadersParams {\n    pub stream_id: u32,\n    pub headers: Vec<String>,\n    pub fin: bool,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvHeadersEvent {\n    pub params: Http2SessionRecvHeadersParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvDataParams {\n    pub stream_id: u32,\n    pub size: u32,\n    pub fin: bool,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvDataEvent {\n    pub params: Http2SessionSendDataParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionUpdateRecvWindowParams {\n    pub delta: i32,\n    pub window_size: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionUpdateRecvWindowEvent {\n    pub params: Http2SessionUpdateRecvWindowParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionUpdateSendWindowParams {\n    pub delta: i32,\n    pub window_size: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionUpdateSendWindowEvent {\n    pub params: Http2SessionUpdateSendWindowParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionUpdateStreamsSendWindowSizeParams {\n    pub delta_window_size: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionUpdateStreamsSendWindowSizeEvent {\n    pub params: Http2SessionUpdateStreamsSendWindowSizeParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendWindowUpdateParams {\n    pub delta: i32,\n    pub stream_id: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendWindowUpdateEvent {\n    pub params: Http2SessionSendWindowUpdateParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvWindowUpdateParams {\n    pub delta: i32,\n    pub stream_id: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvWindowUpdateEvent {\n    pub params: Http2SessionRecvWindowUpdateParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2StreamUpdateSendWindowParams {\n    pub delta: i32,\n    pub stream_id: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2StreamUpdateSendWindowEvent {\n    pub params: Http2StreamUpdateSendWindowParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2StreamUpdateRecvWindowParams {\n    pub delta: i32,\n    pub stream_id: u32,\n    pub window_size: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2StreamUpdateRecvWindowEvent {\n    pub params: Http2StreamUpdateRecvWindowParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2StreamStalledByStreamSendWindowParams {\n    pub stream_id: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2StreamStalledByStreamSendWindowEvent {\n    pub params: Http2StreamStalledByStreamSendWindowParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionPingParams {\n    pub is_ack: bool,\n    #[serde(rename = \"type\")]\n    pub ty: String,\n    pub unique_id: u64,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionPingEvent {\n    pub params: Http2SessionPingParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendRstStreamParams {\n    pub stream_id: u32,\n    pub description: String,\n    pub error_code: String,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionSendRstStreamEvent {\n    pub params: Http2SessionSendRstStreamParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvRstStreamParams {\n    pub stream_id: u32,\n    pub error_code: String,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvRstStreamEvent {\n    pub params: Http2SessionRecvRstStreamParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvGoawayParams {\n    pub active_streams: u32,\n    pub debug_data: String,\n    pub error_code: String,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionRecvGoawayEvent {\n    pub params: Http2SessionRecvGoawayParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionCloseParams {\n    pub description: String,\n    pub net_error: i64,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Http2SessionCloseEvent {\n    pub params: Http2SessionCloseParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Htt2SessionStalledMaxStreamsParams {\n    pub max_concurrent_streams: u32,\n    pub num_active_streams: u32,\n    pub num_created_streams: u32,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct Htt2SessionStalledMaxStreamsEvent {\n    pub params: Htt2SessionStalledMaxStreamsParams,\n}\n\nconst H2_HEADER_TABLE_SIZE: u16 = 0x01;\nconst H2_ENABLE_PUSH: u16 = 0x02;\nconst H2_MAX_CONCURRENT_STREAMS: u16 = 0x03;\nconst H2_INITIAL_WINDOW_SIZE: u16 = 0x04;\nconst H2_MAX_FRAME_SIZE: u16 = 0x05;\nconst H2_MAX_HEADER_LIST_SIZE: u16 = 0x06;\nconst H2_ENABLE_CONNECT_PROTOCOL: u16 = 0x08;\nconst H2_NO_RFC7540_PRIORITIES: u16 = 0x09;\nconst H2_TLS_RENEG_PERMITTED: u16 = 0x10;\nconst H2_ENABLE_METADATA: u16 = 0x4d44;\n\npub const H2_DEFAULT_WINDOW_SIZE: u32 = 65535;\n\n#[derive(Debug, Default)]\npub struct Http2Settings {\n    pub header_table_size: Option<u32>,\n    pub enable_push: Option<u32>,\n    pub max_concurrent_streams: Option<u32>,\n    pub initial_window_size: Option<u32>,\n    pub max_frame_size: Option<u32>,\n    pub max_header_list_size: Option<u32>,\n    pub enable_connect_protocol: Option<u32>,\n    pub no_rfc7540_priorities: Option<u32>,\n    pub tls_reneg_permitted: Option<u32>,\n    pub enable_metadata: Option<u32>,\n}\n\nimpl Http2Settings {\n    pub fn set_from_wire(&mut self, id: u16, value: u32) {\n        match id {\n            H2_HEADER_TABLE_SIZE => self.header_table_size = Some(value),\n            H2_ENABLE_PUSH => self.enable_push = Some(value),\n            H2_MAX_CONCURRENT_STREAMS =>\n                self.max_concurrent_streams = Some(value),\n            H2_INITIAL_WINDOW_SIZE => self.initial_window_size = Some(value),\n            H2_MAX_FRAME_SIZE => self.max_frame_size = Some(value),\n            H2_MAX_HEADER_LIST_SIZE => self.max_header_list_size = Some(value),\n            H2_ENABLE_CONNECT_PROTOCOL =>\n                self.enable_connect_protocol = Some(value),\n            H2_NO_RFC7540_PRIORITIES => self.no_rfc7540_priorities = Some(value),\n            H2_TLS_RENEG_PERMITTED => self.tls_reneg_permitted = Some(value),\n            H2_ENABLE_METADATA => self.enable_metadata = Some(value),\n\n            // TODO: ignoring unknown settings but could capture them\n            _ => (),\n        }\n    }\n}\n\nimpl TryFrom<&[String]> for Http2Settings {\n    type Error = String;\n\n    fn try_from(settings: &[String]) -> Result<Self, Self::Error> {\n        let re = Regex::new(H2_SEND_SETTINGS_PATTERN).unwrap();\n        let mut parsed = Self::default();\n\n        for setting in settings {\n            match re.captures(setting) {\n                Some(caps) => match (caps.get(1), caps.get(2)) {\n                    (Some(id), Some(value)) => {\n                        match (\n                            id.as_str().parse::<u16>(),\n                            value.as_str().parse::<u32>(),\n                        ) {\n                            (Ok(id), Ok(v)) => {\n                                parsed.set_from_wire(id, v);\n                            },\n\n                            _ =>\n                                return Err(format!(\n                                    \"error: parsing H2 setting {}\",\n                                    setting\n                                )),\n                        }\n                    },\n\n                    _ =>\n                        return Err(format!(\n                            \"error: parsing H2 setting {}\",\n                            setting\n                        )),\n                },\n\n                None =>\n                    return Err(format!(\"error: parsing H2 setting {}\", setting)),\n            }\n        }\n\n        Ok(parsed)\n    }\n}\n\n/// Parses the provided `event` based on the event type provided in `event_hdr`.\npub fn parse_event(\n    event_hdr: &EventHeader, event: &[u8],\n) -> Option<super::Event> {\n    match event_hdr.ty_string.as_str() {\n        \"HTTP2_SESSION\" =>\n            if event_hdr.phase_string == \"PHASE_BEGIN\" {\n                let ev: Http2SessionEvent =\n                    serde_json::from_slice(event).unwrap();\n                return Some(super::Event::H2(Event::Http2Session(ev)));\n            },\n\n        \"HTTP2_SESSION_INITIALIZED\" => {\n            let ev: Http2SessionInitializedEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionInitialized(ev)));\n        },\n\n        \"HTTP2_SESSION_SEND_SETTINGS\" => {\n            let ev: Http2SessionSendSettingsEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionSendSettings(ev)));\n        },\n\n        \"HTTP2_SESSION_RECV_SETTING\" => {\n            let ev: Http2SessionRecvSettingEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionRecvSetting(ev)));\n        },\n\n        \"HTTP2_SESSION_UPDATE_RECV_WINDOW\" => {\n            let ev: Http2SessionUpdateRecvWindowEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionUpdateRecvWindow(\n                ev,\n            )));\n        },\n\n        \"HTTP2_SESSION_UPDATE_SEND_WINDOW\" => {\n            let ev: Http2SessionUpdateSendWindowEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionUpdateSendWindow(\n                ev,\n            )));\n        },\n\n        \"HTTP2_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE\" => {\n            let ev: Http2SessionUpdateStreamsSendWindowSizeEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(\n                Event::Http2SessionUpdateStreamsSendWindowSize(ev),\n            ));\n        },\n\n        \"HTTP2_SESSION_SEND_WINDOW_UPDATE\" => {\n            let ev: Http2SessionSendWindowUpdateEvent =\n                serde_json::from_slice(event).unwrap();\n\n            return Some(super::Event::H2(Event::Http2SessionSendWindowUpdate(\n                ev,\n            )));\n        },\n\n        \"HTTP2_SESSION_RECV_WINDOW_UPDATE\" => {\n            let ev: Http2SessionRecvWindowUpdateEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionRecvWindowUpdate(\n                ev,\n            )));\n        },\n\n        \"HTTP2_STREAM_UPDATE_SEND_WINDOW\" => {\n            let ev: Http2StreamUpdateSendWindowEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2StreamUpdateSendWindow(\n                ev,\n            )));\n        },\n\n        \"HTTP2_STREAM_UPDATE_RECV_WINDOW\" => {\n            let ev: Http2StreamUpdateRecvWindowEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2StreamUpdateRecvWindow(\n                ev,\n            )));\n        },\n\n        \"HTTP2_SESSION_STREAM_STALLED_BY_STREAM_SEND_WINDOW\" => {\n            let ev: Http2StreamStalledByStreamSendWindowEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(\n                Event::Http2StreamStalledByStreamSendWindow(ev),\n            ));\n        },\n\n        \"HTTP2_SESSION_SEND_HEADERS\" => {\n            let ev: Http2SessionSendHeadersEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionSendHeaders(ev)));\n        },\n\n        \"HTTP2_SESSION_SEND_DATA\" => {\n            let ev: Http2SessionSendDataEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionSendData(ev)));\n        },\n\n        \"HTTP2_SESSION_RECV_HEADERS\" => {\n            let ev: Http2SessionRecvHeadersEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionRecvHeaders(ev)));\n        },\n\n        \"HTTP2_SESSION_RECV_DATA\" => {\n            let ev: Http2SessionRecvDataEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionRecvData(ev)));\n        },\n\n        \"HTTP2_SESSION_PING\" => {\n            let ev: Http2SessionPingEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionPing(ev)));\n        },\n\n        \"HTTP2_SESSION_SEND_RST_STREAM\" => {\n            let ev: Http2SessionSendRstStreamEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionSendRstStream(ev)));\n        },\n\n        \"HTTP2_SESSION_RECV_RST_STREAM\" => {\n            let ev: Http2SessionRecvRstStreamEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionRecvRstStream(ev)));\n        },\n\n        \"HTTP2_SESSION_RECV_GOAWAY\" => {\n            let ev: Http2SessionRecvGoawayEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionRecvGoaway(ev)));\n        },\n\n        \"HTTP2_SESSION_CLOSE\" => {\n            let ev: Http2SessionCloseEvent =\n                serde_json::from_slice(event).unwrap();\n\n            return Some(super::Event::H2(Event::Http2SessionClose(ev)));\n        },\n\n        \"HTTP2_SESSION_STALLED_MAX_STREAMS\" => {\n            let ev: Htt2SessionStalledMaxStreamsEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H2(Event::Http2SessionStalledMaxStreams(\n                ev,\n            )));\n        },\n\n        // TODO\n        \"HTTP2_PROXY_CLIENT_SESSION\" |\n        \"HTTP2_SESSION_INITIAL_WINDOW_SIZE_OUT_OF_RANGE\" |\n        \"HTTP2_SESSION_RECV_INVALID_HEADER\" |\n        \"HTTP2_SESSION_RECV_PUSH_PROMISE\" |\n        \"HTTP2_SESSION_SEND_GREASED_FRAME\" |\n        \"HTTP2_SESSION_STREAM_STALLED_BY_SESSION_SEND_WINDOW\" |\n        \"HTTP2_STREAM\" |\n        \"HTTP2_STREAM_ADOPTED_PUSH_STREAM\" |\n        \"HTTP2_STREAM_ERROR\" |\n        \"HTTP2_STREAM_FLOW_CONTROL_UNSTALLED\" |\n        \"HTTP2_STREAM_SEND_PRIORITY\" => log::trace!(\n            \"todo: {} => {}\\n\",\n            event_hdr.ty_string,\n            String::from_utf8_lossy(event)\n        ),\n\n        // Other events observed in netlogs but not currently supported.\n        \"HTTP2_SESSION_POOL_CREATED_NEW_SESSION\" |\n        \"HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION\" |\n        \"HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL\" |\n        \"HTTP2_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET\" |\n        \"HTTP2_SESSION_POOL_REMOVE_SESSION\" |\n        \"HTTP2_SESSION_RECV_ACCEPT_CH\" |\n        \"HTTP2_SESSION_RECV_SETTINGS\" |\n        \"HTTP2_SESSION_SEND_SETTINGS_ACK\" |\n        \"HTTP2_SESSION_RECV_SETTINGS_ACK\" => (),\n\n        // The netlog format is continually evolving, log any unknown types in\n        // case they are interesting.\n        _ =>\n            log::trace!(\"skipping unknown HTTP/2 type....{}\", event_hdr.ty_string),\n    }\n\n    None\n}\n"
  },
  {
    "path": "netlog/src/h3.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse super::EventHeader;\n\n#[derive(Debug)]\npub enum Event {\n    Http3PriorityUpdateSent(Http3PriorityUpdateSentEvent),\n    Http3HeadersSent(Http3HeadersSentEvent),\n    Http3DataSent(Http3DataSentEvent),\n    Http3HeadersReceived(Http3HeadersReceivedEvent),\n    Http3HeadersDecoded(Http3HeadersDecodedEvent),\n    Http3DataFrameReceived(Http3DataFrameReceivedEvent),\n}\n#[derive(Serialize, Deserialize, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum PrioritizedElementType {\n    PushStream,\n    #[default]\n    RequestStream,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3PriorityUpdateSentParams {\n    pub prioritized_element_id: u64,\n    pub priority_field_value: String,\n    #[serde(rename = \"type\")]\n    pub ty: Option<PrioritizedElementType>,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3PriorityUpdateSentEvent {\n    pub params: Http3PriorityUpdateSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3HeadersSentParams {\n    pub stream_id: u64,\n    pub headers: Vec<String>,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3HeadersSentEvent {\n    pub params: Http3HeadersSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3DataSentParams {\n    pub payload_length: u64,\n    pub stream_id: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3DataSentEvent {\n    pub params: Http3DataSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3HeadersReceivedParams {\n    pub stream_id: u64,\n    pub compressed_headers_length: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3HeadersReceivedEvent {\n    pub params: Http3HeadersReceivedParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3HeadersDecodedParams {\n    pub stream_id: u64,\n    pub headers: Vec<String>,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3HeadersDecodedEvent {\n    pub params: Http3HeadersDecodedParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3DataFrameReceivedParams {\n    pub payload_length: u64,\n    pub stream_id: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct Http3DataFrameReceivedEvent {\n    pub params: Http3DataFrameReceivedParams,\n}\n\n/// Parses the provided `event` based on the event type provided in `event_hdr`.\npub fn parse_event(\n    event_hdr: &EventHeader, event: &[u8],\n) -> Option<super::Event> {\n    match event_hdr.ty_string.as_str() {\n        \"HTTP3_HEADERS_SENT\" => {\n            let ev: Http3HeadersSentEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H3(Event::Http3HeadersSent(ev)));\n        },\n\n        \"HTTP3_DATA_SENT\" => {\n            let ev: Http3DataSentEvent = serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H3(Event::Http3DataSent(ev)));\n        },\n\n        \"HTTP3_HEADERS_RECEIVED\" => {\n            let ev: Http3HeadersReceivedEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H3(Event::Http3HeadersReceived(ev)));\n        },\n\n        \"HTTP3_HEADERS_DECODED\" => {\n            let ev: Http3HeadersDecodedEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H3(Event::Http3HeadersDecoded(ev)));\n        },\n\n        \"HTTP3_DATA_FRAME_RECEIVED\" => {\n            let ev: Http3DataFrameReceivedEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H3(Event::Http3DataFrameReceived(ev)));\n        },\n\n        \"HTTP3_PRIORITY_UPDATE_SENT\" => {\n            let ev: Http3PriorityUpdateSentEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::H3(Event::Http3PriorityUpdateSent(ev)));\n        },\n\n        \"HTTP3_GOAWAY_SENT\" | \"HTTP3_GOAWAY_RECEIVED\" => {\n            // TODO\n        },\n\n        // Other events observed in netlogs but not currently supported.\n        \"HTTP3_LOCAL_CONTROL_STREAM_CREATED\" |\n        \"HTTP3_LOCAL_QPACK_DECODER_STREAM_CREATED\" |\n        \"HTTP3_LOCAL_QPACK_ENCODER_STREAM_CREATED\" |\n        \"HTTP3_PEER_QPACK_DECODER_STREAM_CREATED\" |\n        \"HTTP3_PEER_QPACK_ENCODER_STREAM_CREATED\" |\n        \"HTTP3_SETTINGS_SENT\" |\n        \"HTTP3_SETTINGS_RESUMED\" |\n        \"HTTP3_PEER_CONTROL_STREAM_CREATED\" |\n        \"HTTP3_SETTINGS_RECEIVED\" |\n        \"HTTP3_UNKNOWN_FRAME_RECEIVED\" => (),\n\n        // The netlog format is continually evolving, log any unknown types\n        // in case they are interesting.\n        _ =>\n            log::trace!(\"skipping unknown HTTP/3 type....{}\", event_hdr.ty_string),\n    }\n\n    None\n}\n"
  },
  {
    "path": "netlog/src/http.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\n\nuse serde::Deserialize;\n\nuse super::EventHeader;\nuse super::SourceDependency;\n\n#[derive(Debug)]\npub enum Event {\n    HttpTransactionSendRequestHeaders(HttpTransactionSendRequestHeadersEvent),\n    HttpTransactionHttp2SendRequestHeaders(\n        HttpTransactionHttp2SendRequestHeadersEvent,\n    ),\n    HttpTransactionQuicSendRequestHeaders(\n        HttpTransactionQuicSendRequestHeadersEvent,\n    ),\n    HttpTransactionReadResponseHeaders(HttpTransactionReadResponseHeadersEvent),\n    HttpStreamJobBoundToRequest(HttpStreamJobBoundToRequestEvent),\n    HttpStreamRequestBoundToJob(HttpStreamRequestBoundToJobEvent),\n    HttpStreamRequestBoundToQuicSession(HttpStreamRequestBoundToQuicSessionEvent),\n}\n#[derive(Deserialize, Debug, Default)]\npub struct HttpTransactionSendRequestHeadersParams {\n    pub headers: Vec<String>,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpTransactionSendRequestHeadersEvent {\n    pub params: HttpTransactionSendRequestHeadersParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpTransactionHttp2SendRequestHeadersParams {\n    pub headers: Vec<String>,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpTransactionHttp2SendRequestHeadersEvent {\n    pub params: HttpTransactionHttp2SendRequestHeadersParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpTransactionQuicSendRequestHeadersParams {\n    pub headers: Vec<String>,\n    pub quic_priority_incremental: bool,\n    pub quic_priority_type: String,\n    pub quic_priority_urgency: u8,\n    pub quic_stream_id: u64,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpTransactionQuicSendRequestHeadersEvent {\n    pub params: HttpTransactionQuicSendRequestHeadersParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpTransactionReadResponseHeadersParams {\n    pub headers: Vec<String>,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpTransactionReadResponseHeadersEvent {\n    pub params: HttpTransactionReadResponseHeadersParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpStreamJobBoundToRequestParams {\n    pub source_dependency: SourceDependency,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpStreamRequestBoundToJobEvent {\n    pub params: HttpStreamJobBoundToRequestParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpStreamRequestBoundToJobParams {\n    pub source_dependency: SourceDependency,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpStreamJobBoundToRequestEvent {\n    pub params: HttpStreamJobBoundToRequestParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpStreamRequestBoundToQuicSessionEvent {\n    pub params: HttpStreamRequestBoundToQuicSessionParams,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct HttpStreamRequestBoundToQuicSessionParams {\n    pub source_dependency: SourceDependency,\n}\n\npub fn headers_to_map(hdrs: &[String]) -> BTreeMap<String, String> {\n    let mut ret = BTreeMap::new();\n\n    for hdr in hdrs {\n        let mut split = hdr.split(\": \");\n        let name = split.next();\n        let val = split.next();\n\n        match (name, val) {\n            (Some(k), Some(v)) => {\n                ret.insert(k.to_string(), v.to_string());\n            },\n\n            (Some(k), None) => {\n                ret.insert(k.to_string(), \"\".to_string());\n            },\n\n            _ => (),\n        }\n    }\n\n    ret\n}\n\n/// Parses the provided `event` based on the event type provided in `event_hdr`.\npub fn parse_event(\n    event_hdr: &EventHeader, event: &[u8],\n) -> Option<super::Event> {\n    match event_hdr.ty_string.as_str() {\n        \"HTTP_TRANSACTION_HTTP2_SEND_REQUEST_HEADERS\" => {\n            let ev: HttpTransactionHttp2SendRequestHeadersEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Http(\n                Event::HttpTransactionHttp2SendRequestHeaders(ev),\n            ));\n        },\n\n        \"HTTP_TRANSACTION_QUIC_SEND_REQUEST_HEADERS\" => {\n            let ev: HttpTransactionQuicSendRequestHeadersEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Http(\n                Event::HttpTransactionQuicSendRequestHeaders(ev),\n            ));\n        },\n\n        \"HTTP_TRANSACTION_SEND_REQUEST_HEADERS\" => {\n            let ev: HttpTransactionSendRequestHeadersEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Http(\n                Event::HttpTransactionSendRequestHeaders(ev),\n            ));\n        },\n\n        \"HTTP_TRANSACTION_READ_RESPONSE_HEADERS\" => {\n            let ev: HttpTransactionReadResponseHeadersEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Http(\n                Event::HttpTransactionReadResponseHeaders(ev),\n            ));\n        },\n\n        \"HTTP_STREAM_REQUEST_BOUND_TO_JOB\" => {\n            let ev: HttpStreamRequestBoundToJobEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Http(Event::HttpStreamRequestBoundToJob(\n                ev,\n            )));\n        },\n\n        \"HTTP_STREAM_JOB_BOUND_TO_REQUEST\" => {\n            let ev: HttpStreamJobBoundToRequestEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Http(Event::HttpStreamJobBoundToRequest(\n                ev,\n            )));\n        },\n\n        \"HTTP_STREAM_REQUEST_BOUND_TO_QUIC_SESSION\" => {\n            let ev: HttpStreamRequestBoundToQuicSessionEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Http(\n                Event::HttpStreamRequestBoundToQuicSession(ev),\n            ));\n        },\n\n        // ignore these for now\n        \"HTTP_TRANSACTION_READ_EARLY_HINTS_RESPONSE_HEADERS\" |\n        \"HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS\" |\n        \"HTTP_TRANSACTION_RESTART_AFTER_ERROR\" |\n        \"HTTP_TRANSACTION_RESTART_MISDIRECTED_REQUEST\" |\n        \"HTTP_TRANSACTION_SEND_REQUEST_BODY\" |\n        \"HTTP_TRANSACTION_SEND_TUNNEL_HEADERS\" |\n        \"HTTP_TRANSACTION_TUNNEL_READ_HEADERS\" |\n        \"HTTP_TRANSACTION_TUNNEL_SEND_REQUEST\" => (),\n\n        // ignore these ones since they contain no extra params\n        \"HTTP_TRANSACTION_SEND_REQUEST\" |\n        \"HTTP_TRANSACTION_READ_HEADERS\" |\n        \"HTTP_TRANSACTION_READ_BODY\" => (),\n\n        _ => log::trace!(\"skipping unknown type....{}\", event_hdr.ty_string),\n    }\n\n    None\n}\n"
  },
  {
    "path": "netlog/src/lib.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! The netlog crate is a reverse-engineered deserializer for the Chrome\n//! [netlog] format. It supports QUIC and HTTP(/2 and /3) events.\n//!\n//! # Overview\n//!\n//! Chromium-based browsers allow users to enable detailed logging, netlog,\n//! which is useful for debugging interoperability or performance issues. A\n//! netlog file uses a kind of line-delimited JSON format. The first line\n//! contains \"constants\", which are specific to the version of the software used\n//! to generate the log. These constants are used for a form of compressed\n//! encoding for the netlog events that appear on each subsequent newline.\n//!\n//! This crate supports parsing a netlog file and converting a subset of netlog\n//! events into Rust structures, via Serde.\n//!\n//! # Example usage\n//!\n//! Assuming a netlog file name of `chrome-net-export-log-error.json`, the first\n//! task is to create a `BufReader` for the file and initialize the netlog\n//! constants.\n//!\n//! ```no_run\n//! use netlog::read_netlog_constants;\n//! use std::fs::File;\n//! use std::io::BufReader;\n//!\n//! let mut reader =\n//!     BufReader::new(File::open(\"chrome-net-export-log-error.json\").unwrap());\n//!\n//! let constants = read_netlog_constants(&mut reader).unwrap();\n//! ```\n//!\n//! Then move on to parsing the netlog file until the end.\n//!\n//! ```no_run\n//! # use std::io::BufReader;\n//! # use std::fs::File;\n//! # use netlog::read_netlog_constants;\n//! use netlog::read_netlog_record;\n//! use netlog::EventHeader;\n//! use netlog::h2::Http2SessionEvent;\n//! use netlog::quic::QuicSessionEvent;\n//! # let mut reader =\n//! #    BufReader::new(File::open(\"chrome-net-export-log-error.json\").unwrap());\n//! # let constants = read_netlog_constants(&mut reader).unwrap();\n//! // The second line of a netlog is `\"events\" [`, which can be skipped over.\n//! read_netlog_record(&mut reader);\n//!\n//! while let Some(record) = read_netlog_record(&mut reader) {\n//!     let res: Result<EventHeader, serde_json::Error> =\n//!         serde_json::from_slice(&record);\n//!\n//!     match res {\n//!         Ok(mut event_hdr) => {\n//!             event_hdr.populate_strings(&constants);\n//!             event_hdr.time_num = event_hdr.time.parse::<u64>().unwrap();\n//!\n//!             // Netlogs can hold many different sessions.\n//!             // Application might want to track these separately\n//!             if event_hdr.phase_string == \"PHASE_BEGIN\" {\n//!                 match event_hdr.ty_string.as_str() {\n//!                     \"HTTP2_SESSION\" => {\n//!                         let ev: Http2SessionEvent =\n//!                             serde_json::from_slice(&record).unwrap();\n//!                         // Handle new session event ...\n//!                     },\n//!                     \"QUIC_SESSION\" => {\n//!                         let ev: QuicSessionEvent =\n//!                             serde_json::from_slice(&record).unwrap();\n//!                         // Handle new session event ...\n//!                     },\n//!\n//!                     // Ignore others\n//!                     _ => (),\n//!                 }\n//!             }\n//!\n//!             // Try to parse other events.\n//!             if let Some(ev) = netlog::parse_event(&event_hdr, &record) {\n//!                 // Handle parsed event.\n//!             }\n//!         },\n//!\n//!         Err(e) => {\n//!             println!(\"Error deserializing: {}\", e);\n//!             println!(\"input value {}\", String::from_utf8_lossy(&record));\n//!         },\n//!     }\n//! }\n//! ```\n//!\n//! [netlog]:\n//! (https://www.chromium.org/developers/design-documents/network-stack/netlog/)\nuse std::io::BufRead;\n\nuse serde::Deserialize;\n\nuse crate::constants::Constants;\nuse crate::constants::ConstantsLine;\n\n#[derive(Deserialize, Debug, Default)]\npub struct EventSource {\n    #[serde(skip)]\n    pub start_time_int: u64,\n\n    pub id: i64,\n    pub start_time: String,\n    #[serde(rename = \"type\")]\n    pub ty: i64,\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct EventHeader {\n    #[serde(skip)]\n    pub ty_string: String,\n    #[serde(skip)]\n    pub phase_string: String,\n    #[serde(skip)]\n    pub time_num: u64,\n\n    pub phase: i64,\n    pub source: EventSource,\n    pub time: String,\n    #[serde(rename = \"type\")]\n    pub ty: i64,\n}\n\nimpl EventHeader {\n    /// Populate the event details based on the provided netlog file constants.\n    pub fn populate_strings(&mut self, constants: &constants::Constants) {\n        self.ty_string = constants.log_event_types_id_keyed[&self.ty].clone();\n        self.phase_string =\n            constants.log_event_phase_id_keyed[&self.phase].clone();\n    }\n}\n\n#[derive(Deserialize, Debug, Default)]\npub struct SourceDependency {\n    pub id: i64,\n    #[serde(rename = \"type\")]\n    pub ty: i64,\n}\n\n/// The core netlog event type with several domain-specific variants.\n#[derive(Debug)]\npub enum Event {\n    Http(http::Event),\n    H2(h2::Event),\n    H3(h3::Event),\n    Quic(quic::Event),\n}\n\n/// Read the netlog constants from a netlog file accessed by a BufRead.\npub fn read_netlog_constants<R: BufRead>(\n    reader: &mut R,\n) -> Result<Constants, serde_json::Error> {\n    let mut buf = Vec::<u8>::new();\n\n    // Read the constants line and replace the trailing comma (,) with a brace\n    // (}) to close the object and make it parseable.\n    let len = reader.read_until(b'\\n', &mut buf).unwrap();\n    buf[len - 2] = b'}';\n\n    let res: Result<ConstantsLine, serde_json::Error> =\n        serde_json::from_slice(&buf);\n\n    match res {\n        Ok(mut line) => {\n            line.constants.populate_id_keyed();\n\n            Ok(line.constants)\n        },\n\n        Err(e) => {\n            log::error!(\"Error deserializing constants: {}\", e);\n\n            Err(e)\n        },\n    }\n}\n\n/// Reads a single record from a netlog file accessed by a BufRead.\npub fn read_netlog_record<R: BufRead>(reader: &mut R) -> Option<Vec<u8>> {\n    let mut buf = Vec::<u8>::new();\n    let size = reader.read_until(b'\\n', &mut buf).unwrap();\n\n    if size <= 1 {\n        return None;\n    }\n\n    // After netlog events, line holds polledData struct. Ignore it and return\n    if buf[0] != b'{' {\n        return None;\n    }\n\n    // Remove trailing comma and newline\n    buf.truncate(buf.len() - 2);\n\n    // Last line of events closes array. Lets ignore it.\n    if buf[buf.len() - 1] == b']' {\n        buf.truncate(buf.len() - 1);\n    }\n\n    log::trace!(\n        \"read record={}\",\n        String::from_utf8(buf.clone()).expect(\"from_utf8 failed\")\n    );\n\n    Some(buf)\n}\n\n/// Parses the provided `event` based on the event type provided in `event_hdr`.\npub fn parse_event(event_hdr: &EventHeader, event: &[u8]) -> Option<Event> {\n    if event_hdr.ty_string.starts_with(\"HTTP_\") {\n        return http::parse_event(event_hdr, event);\n    } else if event_hdr.ty_string.starts_with(\"HTTP2_\") {\n        return h2::parse_event(event_hdr, event);\n    } else if event_hdr.ty_string.starts_with(\"HTTP3_\") {\n        return h3::parse_event(event_hdr, event);\n    } else if event_hdr.ty_string.starts_with(\"QUIC\") {\n        return quic::parse_event(event_hdr, event);\n    }\n\n    None\n}\n\npub mod constants;\npub mod h2;\npub mod h3;\npub mod http;\npub mod quic;\n"
  },
  {
    "path": "netlog/src/quic.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse super::EventHeader;\n\n#[derive(Debug)]\npub enum Event {\n    QuicSession(QuicSessionEvent),\n    QuicSessionTransportParametersSent(QuicSessionTransportParametersSentEvent),\n    QuicSessionTransportParametersReceived(\n        QuicSessionTransportParametersReceivedEvent,\n    ),\n    QuicSessionUnauthenticatedPacketHeaderReceived(\n        QuicSessionUnauthenticatedPacketHeaderReceived,\n    ),\n    QuicSessionPacketSent(QuicSessionPacketSent),\n    QuicSessionAckFrameSent(QuicSessionAckFrameSent),\n    QuicSessionAckFrameReceived(QuicSessionAckFrameReceived),\n    QuicSessionStreamFrameReceived(QuicSessionStreamFrameReceivedEvent),\n    QuicSessionStopSendingFrameSent(QuicSessionStopSendingFrameSentEvent),\n    QuicSessionRstStreamFrameSent(QuicSessionRstStreamFrameSentEvent),\n    QuicSessionRstStreamFrameReceived(QuicSessionRstStreamFrameReceivedEvent),\n    QuicSessionBlockedFrameReceived(QuicSessionBlockedFrameReceivedEvent),\n    QuicSessionWindowUpdateFrameSent(QuicSessionWindowUpdateFrameSentEvent),\n    QuicSessionClosed(QuicSessionClosedEvent),\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionParams {\n    pub cert_verify_flags: u64,\n    pub connection_id: String,\n    pub host: String,\n    pub port: u16,\n    #[serde(alias = \"network_anonymization_key\")]\n    pub network_isolation_key: String,\n    pub privacy_mode: String,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionEvent {\n    pub params: QuicSessionParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionTransportParametersSentParams {\n    pub quic_transport_parameters: String,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionTransportParametersSentEvent {\n    pub params: QuicSessionTransportParametersSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionTransportParametersReceivedParams {\n    pub quic_transport_parameters: String,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionTransportParametersReceivedEvent {\n    pub params: QuicSessionTransportParametersReceivedParams,\n}\n\n// TODO: this should really be done using serde, not hacked like this. What\n// makes it difficult is netlogs concats all these fields into one string.\n#[derive(Debug, Default)]\npub struct TransportParameters {\n    pub versions: String,\n    pub max_idle_timeout: Option<u64>,\n    pub max_udp_payload_size: Option<u64>,\n    pub initial_max_data: Option<u64>,\n    pub initial_max_stream_data_bidi_local: Option<u64>,\n    pub initial_max_stream_data_bidi_remote: Option<u64>,\n    pub initial_max_stream_data_uni: Option<u64>,\n    pub initial_max_streams_bidi: Option<u64>,\n    pub initial_max_streams_uni: Option<u64>,\n    pub initial_source_connection_id: Option<String>,\n    pub max_datagram_frame_size: Option<u64>,\n}\n\nimpl From<String> for TransportParameters {\n    // Example string:\n    // [Client legacy[version 00000001] [chosen_version 00000001 other_versions\n    // 00000001] max_idle_timeout 30000 max_udp_payload_size 1472 initial_max_data\n    // 15728640 initial_max_stream_data_bidi_local 6291456\n    // initial_max_stream_data_bidi_remote 6291456 initial_max_stream_data_uni\n    // 6291456 initial_max_streams_bidi 100 initial_max_streams_uni 103\n    // initial_source_connection_id 0 max_datagram_frame_size 65536]\n    fn from(value: String) -> Self {\n        if value.len() < 3 {\n            // String is probably bogus, so just return defaults\n            return Default::default();\n        }\n\n        let mut tp = TransportParameters::default();\n\n        // The format is quite gnarly, potentially this could be parsed using\n        // regex but doing something very simple for now.\n        let inner = &value[1..value.len() - 1];\n        let last_version_pos = inner.rfind(']').unwrap_or_default();\n        tp.versions = inner[0..last_version_pos + 1].to_string();\n        let rest = &inner[last_version_pos + 2..inner.len()];\n\n        let mut split = rest.split(' ').peekable();\n\n        while let Some(item) = split.next() {\n            if split.peek().is_none() {\n                break;\n            }\n\n            match item {\n                \"max_idle_timeout\" =>\n                    tp.max_idle_timeout = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                \"max_udp_payload_size\" =>\n                    tp.max_udp_payload_size = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                \"initial_max_data\" =>\n                    tp.initial_max_data = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                \"initial_max_stream_data_bidi_local\" =>\n                    tp.initial_max_stream_data_bidi_local = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                \"initial_max_stream_data_bidi_remote\" =>\n                    tp.initial_max_stream_data_bidi_remote = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                \"initial_max_stream_data_uni\" =>\n                    tp.initial_max_stream_data_uni = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                \"initial_max_streams_bidi\" =>\n                    tp.initial_max_streams_bidi = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                \"initial_max_streams_uni\" =>\n                    tp.initial_max_streams_uni = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                \"initial_source_connection_id\" =>\n                    tp.initial_source_connection_id =\n                        split.peek().map(|v| (*v).to_string()),\n                \"max_datagram_frame_size\" =>\n                    tp.max_datagram_frame_size = split\n                        .peek()\n                        .map(|v| (*v).parse::<u64>().unwrap_or_default()),\n                _ => (),\n            }\n        }\n\n        tp\n    }\n}\n\nimpl From<QuicSessionTransportParametersReceivedParams> for TransportParameters {\n    fn from(value: QuicSessionTransportParametersReceivedParams) -> Self {\n        value.quic_transport_parameters.into()\n    }\n}\n\nimpl From<QuicSessionTransportParametersSentParams> for TransportParameters {\n    fn from(value: QuicSessionTransportParametersSentParams) -> Self {\n        value.quic_transport_parameters.into()\n    }\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionUnauthenticatedPacketHeaderReceivedParams {\n    pub connection_id: String,\n    pub header_format: String,\n    pub long_header_type: Option<String>,\n    pub packet_number: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionUnauthenticatedPacketHeaderReceived {\n    pub params: QuicSessionUnauthenticatedPacketHeaderReceivedParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionPacketSentParams {\n    pub encryption_level: String,\n    pub packet_number: u64,\n    pub sent_time_us: u64,\n    transmission_type: String,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionPacketSent {\n    pub params: QuicSessionPacketSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionAckFrameSentParams {\n    pub delta_time_largest_observed_us: u64,\n    pub largest_observed: u64,\n    pub missing_packets: Vec<u64>,\n    pub received_packet_times: Vec<u64>,\n    pub smallest_observed: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionAckFrameSent {\n    pub params: QuicSessionAckFrameSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionAckFrameReceivedParams {\n    pub delta_time_largest_observed_us: u64,\n    pub largest_observed: u64,\n    pub missing_packets: Vec<u64>,\n    pub received_packet_times: Vec<u64>,\n    pub smallest_observed: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionAckFrameReceived {\n    pub params: QuicSessionAckFrameReceivedParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionStreamFrameReceivedParams {\n    pub stream_id: u64,\n    pub fin: bool,\n    pub offset: u64,\n    pub length: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionStreamFrameReceivedEvent {\n    pub params: QuicSessionStreamFrameReceivedParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionStopSendingFrameSentParams {\n    pub stream_id: u64,\n    pub quic_rst_stream_error: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionStopSendingFrameSentEvent {\n    pub params: QuicSessionStopSendingFrameSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionRstStreamFrameSentParams {\n    pub stream_id: u64,\n    pub quic_rst_stream_error: u64,\n    pub offset: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionRstStreamFrameSentEvent {\n    pub params: QuicSessionRstStreamFrameSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionRstStreamFrameReceivedParams {\n    pub stream_id: u64,\n    pub quic_rst_stream_error: u64,\n    pub offset: u64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionRstStreamFrameReceivedEvent {\n    pub params: QuicSessionRstStreamFrameReceivedParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionBlockedFrameReceivedParams {\n    pub stream_id: i64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionBlockedFrameReceivedEvent {\n    pub params: QuicSessionBlockedFrameReceivedParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionWindowUpdateFrameSentParams {\n    pub byte_offset: u64,\n    pub stream_id: i64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionWindowUpdateFrameSentEvent {\n    pub params: QuicSessionWindowUpdateFrameSentParams,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionClosedParams {\n    pub details: String,\n    pub from_peer: bool,\n    pub quic_error: i64,\n}\n\n#[derive(Serialize, Deserialize, Debug, Default)]\npub struct QuicSessionClosedEvent {\n    pub params: QuicSessionClosedParams,\n}\n\n/// Parses the provided `event` based on the event type provided in `event_hdr`.\npub fn parse_event(\n    event_hdr: &EventHeader, event: &[u8],\n) -> Option<super::Event> {\n    match event_hdr.ty_string.as_str() {\n        \"QUIC_SESSION\" =>\n            if event_hdr.phase_string == \"PHASE_BEGIN\" {\n                let ev: QuicSessionEvent = serde_json::from_slice(event).unwrap();\n                return Some(super::Event::Quic(Event::QuicSession(ev)));\n            },\n\n        \"QUIC_SESSION_TRANSPORT_PARAMETERS_SENT\" => {\n            let ev: QuicSessionTransportParametersSentEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Quic(\n                Event::QuicSessionTransportParametersSent(ev),\n            ));\n        },\n\n        \"QUIC_SESSION_TRANSPORT_PARAMETERS_RECEIVED\" => {\n            let ev: QuicSessionTransportParametersReceivedEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Quic(\n                Event::QuicSessionTransportParametersReceived(ev),\n            ));\n        },\n\n        \"QUIC_SESSION_STREAM_FRAME_RECEIVED\" => {\n            let ev: QuicSessionStreamFrameReceivedEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Quic(\n                Event::QuicSessionStreamFrameReceived(ev),\n            ));\n        },\n\n        \"QUIC_SESSION_STOP_SENDING_FRAME_SENT\" => {\n            let ev: QuicSessionStopSendingFrameSentEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Quic(\n                Event::QuicSessionStopSendingFrameSent(ev),\n            ));\n        },\n\n        \"QUIC_SESSION_STOP_SENDING_FRAME_RECEIVED\" => {\n            // TODO\n        },\n\n        \"QUIC_SESSION_RST_STREAM_FRAME_SENT\" => {\n            let ev: QuicSessionRstStreamFrameSentEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Quic(\n                Event::QuicSessionRstStreamFrameSent(ev),\n            ));\n        },\n\n        \"QUIC_SESSION_RST_STREAM_FRAME_RECEIVED\" => {\n            let ev: QuicSessionRstStreamFrameReceivedEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Quic(\n                Event::QuicSessionRstStreamFrameReceived(ev),\n            ));\n        },\n\n        \"QUIC_SESSION_UNAUTHENTICATED_PACKET_HEADER_RECEIVED\" => {\n            let ev: QuicSessionUnauthenticatedPacketHeaderReceived =\n                serde_json::from_slice(event).unwrap();\n\n            return Some(super::Event::Quic(\n                Event::QuicSessionUnauthenticatedPacketHeaderReceived(ev),\n            ));\n        },\n\n        \"QUIC_SESSION_PACKET_SENT\" => {\n            let ev: QuicSessionPacketSent =\n                serde_json::from_slice(event).unwrap();\n\n            return Some(super::Event::Quic(Event::QuicSessionPacketSent(ev)));\n        },\n\n        \"QUIC_SESSION_PACKET_RETRANSMITTED\" => {\n            // TODO\n        },\n\n        \"QUIC_SESSION_ACK_FRAME_SENT\" => {\n            let ev: QuicSessionAckFrameSent =\n                serde_json::from_slice(event).unwrap();\n\n            return Some(super::Event::Quic(Event::QuicSessionAckFrameSent(ev)));\n        },\n\n        \"QUIC_SESSION_ACK_FRAME_RECEIVED\" => {\n            let ev: QuicSessionAckFrameReceived =\n                serde_json::from_slice(event).unwrap();\n\n            return Some(super::Event::Quic(Event::QuicSessionAckFrameReceived(\n                ev,\n            )));\n        },\n\n        \"QUIC_SESSION_PACKET_LOST\" => {\n            // TODO\n        },\n\n        \"QUIC_SESSION_CLOSED\" => {\n            let ev: QuicSessionClosedEvent =\n                serde_json::from_slice(event).unwrap();\n\n            return Some(super::Event::Quic(Event::QuicSessionClosed(ev)));\n        },\n\n        \"QUIC_SESSION_BLOCKED_FRAME_RECEIVED\" => {\n            let ev: QuicSessionBlockedFrameReceivedEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Quic(\n                Event::QuicSessionBlockedFrameReceived(ev),\n            ));\n        },\n\n        \"QUIC_SESSION_STREAMS_BLOCKED_FRAME_RECEIVED\" => {\n            // TODO\n        },\n\n        \"QUIC_SESSION_WINDOW_UPDATE_FRAME_SENT\" => {\n            let ev: QuicSessionWindowUpdateFrameSentEvent =\n                serde_json::from_slice(event).unwrap();\n            return Some(super::Event::Quic(\n                Event::QuicSessionWindowUpdateFrameSent(ev),\n            ));\n        },\n\n        // Other events observed in netlogs but not currently supported.\n        \"QUIC_ACCEPT_CH_FRAME_RECEIVED\" |\n        \"QUIC_CHROMIUM_CLIENT_STREAM_READ_EARLY_HINTS_RESPONSE_HEADERS\" |\n        \"QUIC_CHROMIUM_CLIENT_STREAM_READ_RESPONSE_HEADERS\" |\n        \"QUIC_CHROMIUM_CLIENT_STREAM_READ_RESPONSE_TRAILERS\" |\n        \"QUIC_CHROMIUM_CLIENT_STREAM_SEND_REQUEST_HEADERS\" |\n        \"QUIC_CONNECTION_MIGRATION_FAILURE\" |\n        \"QUIC_CONNECTION_MIGRATION_FAILURE_AFTER_PROBING\" |\n        \"QUIC_CONNECTION_MIGRATION_ON_MIGRATE_BACK\" |\n        \"QUIC_CONNECTION_MIGRATION_ON_NETWORK_CONNECTED\" |\n        \"QUIC_CONNECTION_MIGRATION_ON_NETWORK_DISCONNECTED\" |\n        \"QUIC_CONNECTION_MIGRATION_ON_NETWORK_MADE_DEFAULT\" |\n        \"QUIC_CONNECTION_MIGRATION_ON_PATH_DEGRADING\" |\n        \"QUIC_CONNECTION_MIGRATION_ON_WRITE_ERROR\" |\n        \"QUIC_CONNECTION_MIGRATION_PLATFORM_NOTIFICATION\" |\n        \"QUIC_CONNECTION_MIGRATION_SUCCESS\" |\n        \"QUIC_CONNECTION_MIGRATION_SUCCESS_AFTER_PROBING\" |\n        \"QUIC_CONNECTION_MIGRATION_TRIGGERED\" |\n        \"QUIC_CONNECTIVITY_PROBING_MANAGER_CANCEL_PROBING\" |\n        \"QUIC_CONNECTIVITY_PROBING_MANAGER_PROBE_RECEIVED\" |\n        \"QUIC_CONNECTIVITY_PROBING_MANAGER_PROBE_SENT\" |\n        \"QUIC_CONNECTIVITY_PROBING_MANAGER_START_PROBING\" |\n        \"QUIC_CONNECTIVITY_PROBING_MANAGER_STATELESS_RESET_RECEIVED\" |\n        \"QUIC_HTTP_STREAM_ADOPTED_PUSH_STREAM\" |\n        \"QUIC_HTTP_STREAM_PUSH_PROMISE_RENDEZVOUS\" |\n        \"QUIC_PORT_MIGRATION_FAILURE\" |\n        \"QUIC_PORT_MIGRATION_SUCCESS\" |\n        \"QUIC_PORT_MIGRATION_TRIGGERED\" |\n        \"QUIC_READ_ERROR\" |\n        \"QUIC_SESSION_ATTEMPTING_TO_PROCESS_UNDECRYPTABLE_PACKET\" |\n        \"QUIC_SESSION_BLOCKED_FRAME_SENT\" |\n        \"QUIC_SESSION_BUFFERED_UNDECRYPTABLE_PACKET\" |\n        \"QUIC_SESSION_CLIENT_GOAWAY_ON_PATH_DEGRADING\" |\n        \"QUIC_SESSION_CLOSE_ON_ERROR\" |\n        \"QUIC_SESSION_CONNECTION_CLOSE_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_CONNECTION_CLOSE_FRAME_SENT\" |\n        \"QUIC_SESSION_CONNECTIVITY_PROBING_FINISHED\" |\n        \"QUIC_SESSION_DROPPED_UNDECRYPTABLE_PACKET\" |\n        \"QUIC_SESSION_DUPLICATE_PACKET_RECEIVED\" |\n        \"QUIC_SESSION_GOAWAY_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_GOAWAY_FRAME_SENT\" |\n        \"QUIC_SESSION_KEY_UPDATE\" |\n        \"QUIC_SESSION_MAX_STREAMS_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_MAX_STREAMS_FRAME_SENT\" |\n        \"QUIC_SESSION_MESSAGE_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_MESSAGE_FRAME_SENT\" |\n        \"QUIC_SESSION_MTU_DISCOVERY_FRAME_SENT\" |\n        \"QUIC_SESSION_NEW_CONNECTION_ID_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_NEW_CONNECTION_ID_FRAME_SENT\" |\n        \"QUIC_SESSION_NEW_TOKEN_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_NEW_TOKEN_FRAME_SENT\" |\n        \"QUIC_SESSION_PATH_CHALLENGE_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_PATH_CHALLENGE_FRAME_SENT\" |\n        \"QUIC_SESSION_PATH_RESPONSE_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_PATH_RESPONSE_FRAME_SENT\" |\n        \"QUIC_SESSION_PING_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_PING_FRAME_SENT\" |\n        \"QUIC_SESSION_PUBLIC_RESET_PACKET_RECEIVED\" |\n        \"QUIC_SESSION_PUSH_PROMISE_RECEIVED\" |\n        \"QUIC_SESSION_RETIRE_CONNECTION_ID_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_RETIRE_CONNECTION_ID_FRAME_SENT\" |\n        \"QUIC_SESSION_STOP_WAITING_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_STOP_WAITING_FRAME_SENT\" |\n        \"QUIC_SESSION_TRANSPORT_PARAMETERS_RESUMED\" |\n        \"QUIC_SESSION_WEBTRANSPORT_CLIENT_ALIVE\" |\n        \"QUIC_SESSION_WEBTRANSPORT_CLIENT_STATE_CHANGED\" |\n        \"QUIC_SESSION_WINDOW_UPDATE_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_ZERO_RTT_REJECTED\" |\n        \"QUIC_SESSION_CERTIFICATE_VERIFIED\" |\n        \"QUIC_SESSION_CERTIFICATE_VERIFY_FAILED\" |\n        \"QUIC_SESSION_COALESCED_PACKET_SENT\" |\n        \"QUIC_SESSION_CRYPTO_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_CRYPTO_FRAME_SENT\" |\n        \"QUIC_SESSION_CRYPTO_HANDSHAKE_MESSAGE_RECEIVED\" |\n        \"QUIC_SESSION_CRYPTO_HANDSHAKE_MESSAGE_SENT\" |\n        \"QUIC_SESSION_HANDSHAKE_DONE_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_PACKET_HEADER_REVIVED\" |\n        \"QUIC_SESSION_PADDING_FRAME_RECEIVED\" |\n        \"QUIC_SESSION_PADDING_FRAME_SENT\" |\n        \"QUIC_SESSION_STREAMS_BLOCKED_FRAME_SENT\" |\n        \"QUIC_SESSION_STREAM_FRAME_COALESCED\" |\n        \"QUIC_SESSION_STREAM_FRAME_SENT\" |\n        \"QUIC_SESSION_VERSION_NEGOTIATED\" |\n        \"QUIC_SESSION_VERSION_NEGOTIATION_PACKET_RECEIVED\" => (),\n\n        // Most likely uninteresting events\n        \"QUIC_SESSION_PACKET_RECEIVED\" |\n        \"QUIC_CONNECTION_MIGRATION_MODE\" |\n        \"QUIC_STREAM_FACTORY_JOB\" |\n        \"QUIC_STREAM_FACTORY_JOB_BOUND_TO_HTTP_STREAM_JOB\" |\n        \"QUIC_STREAM_FACTORY_JOB_CONNECT\" |\n        \"QUIC_STREAM_FACTORY_JOB_RETRY_ON_ALTERNATE_NETWORK\" |\n        \"QUIC_STREAM_FACTORY_JOB_STALE_HOST_NOT_USED_ON_CONNECTION\" |\n        \"QUIC_STREAM_FACTORY_JOB_STALE_HOST_RESOLUTION_MATCHED\" |\n        \"QUIC_STREAM_FACTORY_JOB_STALE_HOST_RESOLUTION_NO_MATCH\" |\n        \"QUIC_STREAM_FACTORY_JOB_STALE_HOST_TRIED_ON_CONNECTION\" => (),\n\n        // Ignored since it contains no extra params\n        \"QUIC_SESSION_PACKET_AUTHENTICATED\" => (),\n\n        // The netlog format is continually evolving, log any unknown types\n        // in case they are interesting.\n        _ => log::trace!(\"skipping unknown QUIC type....{}\", event_hdr.ty_string),\n    }\n\n    None\n}\n"
  },
  {
    "path": "octets/Cargo.toml",
    "content": "[package]\nname = \"octets\"\nversion = \"0.3.5\"\nauthors = [\"Alessandro Ghedini <alessandro@ghedini.me>\"]\ndescription = \"Zero-copy abstraction for parsing and constructing network packets\"\nedition = { workspace = true }\nrepository = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\n\n[features]\n# Build the Huffman encoding support as defined by HPACK (RFC7541).\nhuffman_hpack = []\n\n[lints.rust]\nunexpected_cfgs = { level = \"warn\", check-cfg = [\n    'cfg(test_invalid_len_compilation_fail)',\n] }\n"
  },
  {
    "path": "octets/src/huffman_table.rs",
    "content": "// (num-bits, bits)\npub static ENCODE_TABLE: [(usize, u64); 257] = [\n    (13, 0x1ff8),\n    (23, 0x7fffd8),\n    (28, 0xfffffe2),\n    (28, 0xfffffe3),\n    (28, 0xfffffe4),\n    (28, 0xfffffe5),\n    (28, 0xfffffe6),\n    (28, 0xfffffe7),\n    (28, 0xfffffe8),\n    (24, 0xffffea),\n    (30, 0x3ffffffc),\n    (28, 0xfffffe9),\n    (28, 0xfffffea),\n    (30, 0x3ffffffd),\n    (28, 0xfffffeb),\n    (28, 0xfffffec),\n    (28, 0xfffffed),\n    (28, 0xfffffee),\n    (28, 0xfffffef),\n    (28, 0xffffff0),\n    (28, 0xffffff1),\n    (28, 0xffffff2),\n    (30, 0x3ffffffe),\n    (28, 0xffffff3),\n    (28, 0xffffff4),\n    (28, 0xffffff5),\n    (28, 0xffffff6),\n    (28, 0xffffff7),\n    (28, 0xffffff8),\n    (28, 0xffffff9),\n    (28, 0xffffffa),\n    (28, 0xffffffb),\n    (6, 0x14),\n    (10, 0x3f8),\n    (10, 0x3f9),\n    (12, 0xffa),\n    (13, 0x1ff9),\n    (6, 0x15),\n    (8, 0xf8),\n    (11, 0x7fa),\n    (10, 0x3fa),\n    (10, 0x3fb),\n    (8, 0xf9),\n    (11, 0x7fb),\n    (8, 0xfa),\n    (6, 0x16),\n    (6, 0x17),\n    (6, 0x18),\n    (5, 0x0),\n    (5, 0x1),\n    (5, 0x2),\n    (6, 0x19),\n    (6, 0x1a),\n    (6, 0x1b),\n    (6, 0x1c),\n    (6, 0x1d),\n    (6, 0x1e),\n    (6, 0x1f),\n    (7, 0x5c),\n    (8, 0xfb),\n    (15, 0x7ffc),\n    (6, 0x20),\n    (12, 0xffb),\n    (10, 0x3fc),\n    (13, 0x1ffa),\n    (6, 0x21),\n    (7, 0x5d),\n    (7, 0x5e),\n    (7, 0x5f),\n    (7, 0x60),\n    (7, 0x61),\n    (7, 0x62),\n    (7, 0x63),\n    (7, 0x64),\n    (7, 0x65),\n    (7, 0x66),\n    (7, 0x67),\n    (7, 0x68),\n    (7, 0x69),\n    (7, 0x6a),\n    (7, 0x6b),\n    (7, 0x6c),\n    (7, 0x6d),\n    (7, 0x6e),\n    (7, 0x6f),\n    (7, 0x70),\n    (7, 0x71),\n    (7, 0x72),\n    (8, 0xfc),\n    (7, 0x73),\n    (8, 0xfd),\n    (13, 0x1ffb),\n    (19, 0x7fff0),\n    (13, 0x1ffc),\n    (14, 0x3ffc),\n    (6, 0x22),\n    (15, 0x7ffd),\n    (5, 0x3),\n    (6, 0x23),\n    (5, 0x4),\n    (6, 0x24),\n    (5, 0x5),\n    (6, 0x25),\n    (6, 0x26),\n    (6, 0x27),\n    (5, 0x6),\n    (7, 0x74),\n    (7, 0x75),\n    (6, 0x28),\n    (6, 0x29),\n    (6, 0x2a),\n    (5, 0x7),\n    (6, 0x2b),\n    (7, 0x76),\n    (6, 0x2c),\n    (5, 0x8),\n    (5, 0x9),\n    (6, 0x2d),\n    (7, 0x77),\n    (7, 0x78),\n    (7, 0x79),\n    (7, 0x7a),\n    (7, 0x7b),\n    (15, 0x7ffe),\n    (11, 0x7fc),\n    (14, 0x3ffd),\n    (13, 0x1ffd),\n    (28, 0xffffffc),\n    (20, 0xfffe6),\n    (22, 0x3fffd2),\n    (20, 0xfffe7),\n    (20, 0xfffe8),\n    (22, 0x3fffd3),\n    (22, 0x3fffd4),\n    (22, 0x3fffd5),\n    (23, 0x7fffd9),\n    (22, 0x3fffd6),\n    (23, 0x7fffda),\n    (23, 0x7fffdb),\n    (23, 0x7fffdc),\n    (23, 0x7fffdd),\n    (23, 0x7fffde),\n    (24, 0xffffeb),\n    (23, 0x7fffdf),\n    (24, 0xffffec),\n    (24, 0xffffed),\n    (22, 0x3fffd7),\n    (23, 0x7fffe0),\n    (24, 0xffffee),\n    (23, 0x7fffe1),\n    (23, 0x7fffe2),\n    (23, 0x7fffe3),\n    (23, 0x7fffe4),\n    (21, 0x1fffdc),\n    (22, 0x3fffd8),\n    (23, 0x7fffe5),\n    (22, 0x3fffd9),\n    (23, 0x7fffe6),\n    (23, 0x7fffe7),\n    (24, 0xffffef),\n    (22, 0x3fffda),\n    (21, 0x1fffdd),\n    (20, 0xfffe9),\n    (22, 0x3fffdb),\n    (22, 0x3fffdc),\n    (23, 0x7fffe8),\n    (23, 0x7fffe9),\n    (21, 0x1fffde),\n    (23, 0x7fffea),\n    (22, 0x3fffdd),\n    (22, 0x3fffde),\n    (24, 0xfffff0),\n    (21, 0x1fffdf),\n    (22, 0x3fffdf),\n    (23, 0x7fffeb),\n    (23, 0x7fffec),\n    (21, 0x1fffe0),\n    (21, 0x1fffe1),\n    (22, 0x3fffe0),\n    (21, 0x1fffe2),\n    (23, 0x7fffed),\n    (22, 0x3fffe1),\n    (23, 0x7fffee),\n    (23, 0x7fffef),\n    (20, 0xfffea),\n    (22, 0x3fffe2),\n    (22, 0x3fffe3),\n    (22, 0x3fffe4),\n    (23, 0x7ffff0),\n    (22, 0x3fffe5),\n    (22, 0x3fffe6),\n    (23, 0x7ffff1),\n    (26, 0x3ffffe0),\n    (26, 0x3ffffe1),\n    (20, 0xfffeb),\n    (19, 0x7fff1),\n    (22, 0x3fffe7),\n    (23, 0x7ffff2),\n    (22, 0x3fffe8),\n    (25, 0x1ffffec),\n    (26, 0x3ffffe2),\n    (26, 0x3ffffe3),\n    (26, 0x3ffffe4),\n    (27, 0x7ffffde),\n    (27, 0x7ffffdf),\n    (26, 0x3ffffe5),\n    (24, 0xfffff1),\n    (25, 0x1ffffed),\n    (19, 0x7fff2),\n    (21, 0x1fffe3),\n    (26, 0x3ffffe6),\n    (27, 0x7ffffe0),\n    (27, 0x7ffffe1),\n    (26, 0x3ffffe7),\n    (27, 0x7ffffe2),\n    (24, 0xfffff2),\n    (21, 0x1fffe4),\n    (21, 0x1fffe5),\n    (26, 0x3ffffe8),\n    (26, 0x3ffffe9),\n    (28, 0xffffffd),\n    (27, 0x7ffffe3),\n    (27, 0x7ffffe4),\n    (27, 0x7ffffe5),\n    (20, 0xfffec),\n    (24, 0xfffff3),\n    (20, 0xfffed),\n    (21, 0x1fffe6),\n    (22, 0x3fffe9),\n    (21, 0x1fffe7),\n    (21, 0x1fffe8),\n    (23, 0x7ffff3),\n    (22, 0x3fffea),\n    (22, 0x3fffeb),\n    (25, 0x1ffffee),\n    (25, 0x1ffffef),\n    (24, 0xfffff4),\n    (24, 0xfffff5),\n    (26, 0x3ffffea),\n    (23, 0x7ffff4),\n    (26, 0x3ffffeb),\n    (27, 0x7ffffe6),\n    (26, 0x3ffffec),\n    (26, 0x3ffffed),\n    (27, 0x7ffffe7),\n    (27, 0x7ffffe8),\n    (27, 0x7ffffe9),\n    (27, 0x7ffffea),\n    (27, 0x7ffffeb),\n    (28, 0xffffffe),\n    (27, 0x7ffffec),\n    (27, 0x7ffffed),\n    (27, 0x7ffffee),\n    (27, 0x7ffffef),\n    (27, 0x7fffff0),\n    (26, 0x3ffffee),\n    (30, 0x3fffffff),\n];\n\n// (next-state, byte, flags)\npub static DECODE_TABLE: [[(usize, u8, u8); 16]; 256] = [\n    // 0\n    [\n        (4, 0, 0x00),\n        (5, 0, 0x00),\n        (7, 0, 0x00),\n        (8, 0, 0x00),\n        (11, 0, 0x00),\n        (12, 0, 0x00),\n        (16, 0, 0x00),\n        (19, 0, 0x00),\n        (25, 0, 0x00),\n        (28, 0, 0x00),\n        (32, 0, 0x00),\n        (35, 0, 0x00),\n        (42, 0, 0x00),\n        (49, 0, 0x00),\n        (57, 0, 0x00),\n        (64, 0, 0x01),\n    ],\n    // 1\n    [\n        (0, 48, 0x02),\n        (0, 49, 0x02),\n        (0, 50, 0x02),\n        (0, 97, 0x02),\n        (0, 99, 0x02),\n        (0, 101, 0x02),\n        (0, 105, 0x02),\n        (0, 111, 0x02),\n        (0, 115, 0x02),\n        (0, 116, 0x02),\n        (13, 0, 0x00),\n        (14, 0, 0x00),\n        (17, 0, 0x00),\n        (18, 0, 0x00),\n        (20, 0, 0x00),\n        (21, 0, 0x00),\n    ],\n    // 2\n    [\n        (1, 48, 0x02),\n        (22, 48, 0x03),\n        (1, 49, 0x02),\n        (22, 49, 0x03),\n        (1, 50, 0x02),\n        (22, 50, 0x03),\n        (1, 97, 0x02),\n        (22, 97, 0x03),\n        (1, 99, 0x02),\n        (22, 99, 0x03),\n        (1, 101, 0x02),\n        (22, 101, 0x03),\n        (1, 105, 0x02),\n        (22, 105, 0x03),\n        (1, 111, 0x02),\n        (22, 111, 0x03),\n    ],\n    // 3\n    [\n        (2, 48, 0x02),\n        (9, 48, 0x02),\n        (23, 48, 0x02),\n        (40, 48, 0x03),\n        (2, 49, 0x02),\n        (9, 49, 0x02),\n        (23, 49, 0x02),\n        (40, 49, 0x03),\n        (2, 50, 0x02),\n        (9, 50, 0x02),\n        (23, 50, 0x02),\n        (40, 50, 0x03),\n        (2, 97, 0x02),\n        (9, 97, 0x02),\n        (23, 97, 0x02),\n        (40, 97, 0x03),\n    ],\n    // 4\n    [\n        (3, 48, 0x02),\n        (6, 48, 0x02),\n        (10, 48, 0x02),\n        (15, 48, 0x02),\n        (24, 48, 0x02),\n        (31, 48, 0x02),\n        (41, 48, 0x02),\n        (56, 48, 0x03),\n        (3, 49, 0x02),\n        (6, 49, 0x02),\n        (10, 49, 0x02),\n        (15, 49, 0x02),\n        (24, 49, 0x02),\n        (31, 49, 0x02),\n        (41, 49, 0x02),\n        (56, 49, 0x03),\n    ],\n    // 5\n    [\n        (3, 50, 0x02),\n        (6, 50, 0x02),\n        (10, 50, 0x02),\n        (15, 50, 0x02),\n        (24, 50, 0x02),\n        (31, 50, 0x02),\n        (41, 50, 0x02),\n        (56, 50, 0x03),\n        (3, 97, 0x02),\n        (6, 97, 0x02),\n        (10, 97, 0x02),\n        (15, 97, 0x02),\n        (24, 97, 0x02),\n        (31, 97, 0x02),\n        (41, 97, 0x02),\n        (56, 97, 0x03),\n    ],\n    // 6\n    [\n        (2, 99, 0x02),\n        (9, 99, 0x02),\n        (23, 99, 0x02),\n        (40, 99, 0x03),\n        (2, 101, 0x02),\n        (9, 101, 0x02),\n        (23, 101, 0x02),\n        (40, 101, 0x03),\n        (2, 105, 0x02),\n        (9, 105, 0x02),\n        (23, 105, 0x02),\n        (40, 105, 0x03),\n        (2, 111, 0x02),\n        (9, 111, 0x02),\n        (23, 111, 0x02),\n        (40, 111, 0x03),\n    ],\n    // 7\n    [\n        (3, 99, 0x02),\n        (6, 99, 0x02),\n        (10, 99, 0x02),\n        (15, 99, 0x02),\n        (24, 99, 0x02),\n        (31, 99, 0x02),\n        (41, 99, 0x02),\n        (56, 99, 0x03),\n        (3, 101, 0x02),\n        (6, 101, 0x02),\n        (10, 101, 0x02),\n        (15, 101, 0x02),\n        (24, 101, 0x02),\n        (31, 101, 0x02),\n        (41, 101, 0x02),\n        (56, 101, 0x03),\n    ],\n    // 8\n    [\n        (3, 105, 0x02),\n        (6, 105, 0x02),\n        (10, 105, 0x02),\n        (15, 105, 0x02),\n        (24, 105, 0x02),\n        (31, 105, 0x02),\n        (41, 105, 0x02),\n        (56, 105, 0x03),\n        (3, 111, 0x02),\n        (6, 111, 0x02),\n        (10, 111, 0x02),\n        (15, 111, 0x02),\n        (24, 111, 0x02),\n        (31, 111, 0x02),\n        (41, 111, 0x02),\n        (56, 111, 0x03),\n    ],\n    // 9\n    [\n        (1, 115, 0x02),\n        (22, 115, 0x03),\n        (1, 116, 0x02),\n        (22, 116, 0x03),\n        (0, 32, 0x02),\n        (0, 37, 0x02),\n        (0, 45, 0x02),\n        (0, 46, 0x02),\n        (0, 47, 0x02),\n        (0, 51, 0x02),\n        (0, 52, 0x02),\n        (0, 53, 0x02),\n        (0, 54, 0x02),\n        (0, 55, 0x02),\n        (0, 56, 0x02),\n        (0, 57, 0x02),\n    ],\n    // 10\n    [\n        (2, 115, 0x02),\n        (9, 115, 0x02),\n        (23, 115, 0x02),\n        (40, 115, 0x03),\n        (2, 116, 0x02),\n        (9, 116, 0x02),\n        (23, 116, 0x02),\n        (40, 116, 0x03),\n        (1, 32, 0x02),\n        (22, 32, 0x03),\n        (1, 37, 0x02),\n        (22, 37, 0x03),\n        (1, 45, 0x02),\n        (22, 45, 0x03),\n        (1, 46, 0x02),\n        (22, 46, 0x03),\n    ],\n    // 11\n    [\n        (3, 115, 0x02),\n        (6, 115, 0x02),\n        (10, 115, 0x02),\n        (15, 115, 0x02),\n        (24, 115, 0x02),\n        (31, 115, 0x02),\n        (41, 115, 0x02),\n        (56, 115, 0x03),\n        (3, 116, 0x02),\n        (6, 116, 0x02),\n        (10, 116, 0x02),\n        (15, 116, 0x02),\n        (24, 116, 0x02),\n        (31, 116, 0x02),\n        (41, 116, 0x02),\n        (56, 116, 0x03),\n    ],\n    // 12\n    [\n        (2, 32, 0x02),\n        (9, 32, 0x02),\n        (23, 32, 0x02),\n        (40, 32, 0x03),\n        (2, 37, 0x02),\n        (9, 37, 0x02),\n        (23, 37, 0x02),\n        (40, 37, 0x03),\n        (2, 45, 0x02),\n        (9, 45, 0x02),\n        (23, 45, 0x02),\n        (40, 45, 0x03),\n        (2, 46, 0x02),\n        (9, 46, 0x02),\n        (23, 46, 0x02),\n        (40, 46, 0x03),\n    ],\n    // 13\n    [\n        (3, 32, 0x02),\n        (6, 32, 0x02),\n        (10, 32, 0x02),\n        (15, 32, 0x02),\n        (24, 32, 0x02),\n        (31, 32, 0x02),\n        (41, 32, 0x02),\n        (56, 32, 0x03),\n        (3, 37, 0x02),\n        (6, 37, 0x02),\n        (10, 37, 0x02),\n        (15, 37, 0x02),\n        (24, 37, 0x02),\n        (31, 37, 0x02),\n        (41, 37, 0x02),\n        (56, 37, 0x03),\n    ],\n    // 14\n    [\n        (3, 45, 0x02),\n        (6, 45, 0x02),\n        (10, 45, 0x02),\n        (15, 45, 0x02),\n        (24, 45, 0x02),\n        (31, 45, 0x02),\n        (41, 45, 0x02),\n        (56, 45, 0x03),\n        (3, 46, 0x02),\n        (6, 46, 0x02),\n        (10, 46, 0x02),\n        (15, 46, 0x02),\n        (24, 46, 0x02),\n        (31, 46, 0x02),\n        (41, 46, 0x02),\n        (56, 46, 0x03),\n    ],\n    // 15\n    [\n        (1, 47, 0x02),\n        (22, 47, 0x03),\n        (1, 51, 0x02),\n        (22, 51, 0x03),\n        (1, 52, 0x02),\n        (22, 52, 0x03),\n        (1, 53, 0x02),\n        (22, 53, 0x03),\n        (1, 54, 0x02),\n        (22, 54, 0x03),\n        (1, 55, 0x02),\n        (22, 55, 0x03),\n        (1, 56, 0x02),\n        (22, 56, 0x03),\n        (1, 57, 0x02),\n        (22, 57, 0x03),\n    ],\n    // 16\n    [\n        (2, 47, 0x02),\n        (9, 47, 0x02),\n        (23, 47, 0x02),\n        (40, 47, 0x03),\n        (2, 51, 0x02),\n        (9, 51, 0x02),\n        (23, 51, 0x02),\n        (40, 51, 0x03),\n        (2, 52, 0x02),\n        (9, 52, 0x02),\n        (23, 52, 0x02),\n        (40, 52, 0x03),\n        (2, 53, 0x02),\n        (9, 53, 0x02),\n        (23, 53, 0x02),\n        (40, 53, 0x03),\n    ],\n    // 17\n    [\n        (3, 47, 0x02),\n        (6, 47, 0x02),\n        (10, 47, 0x02),\n        (15, 47, 0x02),\n        (24, 47, 0x02),\n        (31, 47, 0x02),\n        (41, 47, 0x02),\n        (56, 47, 0x03),\n        (3, 51, 0x02),\n        (6, 51, 0x02),\n        (10, 51, 0x02),\n        (15, 51, 0x02),\n        (24, 51, 0x02),\n        (31, 51, 0x02),\n        (41, 51, 0x02),\n        (56, 51, 0x03),\n    ],\n    // 18\n    [\n        (3, 52, 0x02),\n        (6, 52, 0x02),\n        (10, 52, 0x02),\n        (15, 52, 0x02),\n        (24, 52, 0x02),\n        (31, 52, 0x02),\n        (41, 52, 0x02),\n        (56, 52, 0x03),\n        (3, 53, 0x02),\n        (6, 53, 0x02),\n        (10, 53, 0x02),\n        (15, 53, 0x02),\n        (24, 53, 0x02),\n        (31, 53, 0x02),\n        (41, 53, 0x02),\n        (56, 53, 0x03),\n    ],\n    // 19\n    [\n        (2, 54, 0x02),\n        (9, 54, 0x02),\n        (23, 54, 0x02),\n        (40, 54, 0x03),\n        (2, 55, 0x02),\n        (9, 55, 0x02),\n        (23, 55, 0x02),\n        (40, 55, 0x03),\n        (2, 56, 0x02),\n        (9, 56, 0x02),\n        (23, 56, 0x02),\n        (40, 56, 0x03),\n        (2, 57, 0x02),\n        (9, 57, 0x02),\n        (23, 57, 0x02),\n        (40, 57, 0x03),\n    ],\n    // 20\n    [\n        (3, 54, 0x02),\n        (6, 54, 0x02),\n        (10, 54, 0x02),\n        (15, 54, 0x02),\n        (24, 54, 0x02),\n        (31, 54, 0x02),\n        (41, 54, 0x02),\n        (56, 54, 0x03),\n        (3, 55, 0x02),\n        (6, 55, 0x02),\n        (10, 55, 0x02),\n        (15, 55, 0x02),\n        (24, 55, 0x02),\n        (31, 55, 0x02),\n        (41, 55, 0x02),\n        (56, 55, 0x03),\n    ],\n    // 21\n    [\n        (3, 56, 0x02),\n        (6, 56, 0x02),\n        (10, 56, 0x02),\n        (15, 56, 0x02),\n        (24, 56, 0x02),\n        (31, 56, 0x02),\n        (41, 56, 0x02),\n        (56, 56, 0x03),\n        (3, 57, 0x02),\n        (6, 57, 0x02),\n        (10, 57, 0x02),\n        (15, 57, 0x02),\n        (24, 57, 0x02),\n        (31, 57, 0x02),\n        (41, 57, 0x02),\n        (56, 57, 0x03),\n    ],\n    // 22\n    [\n        (26, 0, 0x00),\n        (27, 0, 0x00),\n        (29, 0, 0x00),\n        (30, 0, 0x00),\n        (33, 0, 0x00),\n        (34, 0, 0x00),\n        (36, 0, 0x00),\n        (37, 0, 0x00),\n        (43, 0, 0x00),\n        (46, 0, 0x00),\n        (50, 0, 0x00),\n        (53, 0, 0x00),\n        (58, 0, 0x00),\n        (61, 0, 0x00),\n        (65, 0, 0x00),\n        (68, 0, 0x01),\n    ],\n    // 23\n    [\n        (0, 61, 0x02),\n        (0, 65, 0x02),\n        (0, 95, 0x02),\n        (0, 98, 0x02),\n        (0, 100, 0x02),\n        (0, 102, 0x02),\n        (0, 103, 0x02),\n        (0, 104, 0x02),\n        (0, 108, 0x02),\n        (0, 109, 0x02),\n        (0, 110, 0x02),\n        (0, 112, 0x02),\n        (0, 114, 0x02),\n        (0, 117, 0x02),\n        (38, 0, 0x00),\n        (39, 0, 0x00),\n    ],\n    // 24\n    [\n        (1, 61, 0x02),\n        (22, 61, 0x03),\n        (1, 65, 0x02),\n        (22, 65, 0x03),\n        (1, 95, 0x02),\n        (22, 95, 0x03),\n        (1, 98, 0x02),\n        (22, 98, 0x03),\n        (1, 100, 0x02),\n        (22, 100, 0x03),\n        (1, 102, 0x02),\n        (22, 102, 0x03),\n        (1, 103, 0x02),\n        (22, 103, 0x03),\n        (1, 104, 0x02),\n        (22, 104, 0x03),\n    ],\n    // 25\n    [\n        (2, 61, 0x02),\n        (9, 61, 0x02),\n        (23, 61, 0x02),\n        (40, 61, 0x03),\n        (2, 65, 0x02),\n        (9, 65, 0x02),\n        (23, 65, 0x02),\n        (40, 65, 0x03),\n        (2, 95, 0x02),\n        (9, 95, 0x02),\n        (23, 95, 0x02),\n        (40, 95, 0x03),\n        (2, 98, 0x02),\n        (9, 98, 0x02),\n        (23, 98, 0x02),\n        (40, 98, 0x03),\n    ],\n    // 26\n    [\n        (3, 61, 0x02),\n        (6, 61, 0x02),\n        (10, 61, 0x02),\n        (15, 61, 0x02),\n        (24, 61, 0x02),\n        (31, 61, 0x02),\n        (41, 61, 0x02),\n        (56, 61, 0x03),\n        (3, 65, 0x02),\n        (6, 65, 0x02),\n        (10, 65, 0x02),\n        (15, 65, 0x02),\n        (24, 65, 0x02),\n        (31, 65, 0x02),\n        (41, 65, 0x02),\n        (56, 65, 0x03),\n    ],\n    // 27\n    [\n        (3, 95, 0x02),\n        (6, 95, 0x02),\n        (10, 95, 0x02),\n        (15, 95, 0x02),\n        (24, 95, 0x02),\n        (31, 95, 0x02),\n        (41, 95, 0x02),\n        (56, 95, 0x03),\n        (3, 98, 0x02),\n        (6, 98, 0x02),\n        (10, 98, 0x02),\n        (15, 98, 0x02),\n        (24, 98, 0x02),\n        (31, 98, 0x02),\n        (41, 98, 0x02),\n        (56, 98, 0x03),\n    ],\n    // 28\n    [\n        (2, 100, 0x02),\n        (9, 100, 0x02),\n        (23, 100, 0x02),\n        (40, 100, 0x03),\n        (2, 102, 0x02),\n        (9, 102, 0x02),\n        (23, 102, 0x02),\n        (40, 102, 0x03),\n        (2, 103, 0x02),\n        (9, 103, 0x02),\n        (23, 103, 0x02),\n        (40, 103, 0x03),\n        (2, 104, 0x02),\n        (9, 104, 0x02),\n        (23, 104, 0x02),\n        (40, 104, 0x03),\n    ],\n    // 29\n    [\n        (3, 100, 0x02),\n        (6, 100, 0x02),\n        (10, 100, 0x02),\n        (15, 100, 0x02),\n        (24, 100, 0x02),\n        (31, 100, 0x02),\n        (41, 100, 0x02),\n        (56, 100, 0x03),\n        (3, 102, 0x02),\n        (6, 102, 0x02),\n        (10, 102, 0x02),\n        (15, 102, 0x02),\n        (24, 102, 0x02),\n        (31, 102, 0x02),\n        (41, 102, 0x02),\n        (56, 102, 0x03),\n    ],\n    // 30\n    [\n        (3, 103, 0x02),\n        (6, 103, 0x02),\n        (10, 103, 0x02),\n        (15, 103, 0x02),\n        (24, 103, 0x02),\n        (31, 103, 0x02),\n        (41, 103, 0x02),\n        (56, 103, 0x03),\n        (3, 104, 0x02),\n        (6, 104, 0x02),\n        (10, 104, 0x02),\n        (15, 104, 0x02),\n        (24, 104, 0x02),\n        (31, 104, 0x02),\n        (41, 104, 0x02),\n        (56, 104, 0x03),\n    ],\n    // 31\n    [\n        (1, 108, 0x02),\n        (22, 108, 0x03),\n        (1, 109, 0x02),\n        (22, 109, 0x03),\n        (1, 110, 0x02),\n        (22, 110, 0x03),\n        (1, 112, 0x02),\n        (22, 112, 0x03),\n        (1, 114, 0x02),\n        (22, 114, 0x03),\n        (1, 117, 0x02),\n        (22, 117, 0x03),\n        (0, 58, 0x02),\n        (0, 66, 0x02),\n        (0, 67, 0x02),\n        (0, 68, 0x02),\n    ],\n    // 32\n    [\n        (2, 108, 0x02),\n        (9, 108, 0x02),\n        (23, 108, 0x02),\n        (40, 108, 0x03),\n        (2, 109, 0x02),\n        (9, 109, 0x02),\n        (23, 109, 0x02),\n        (40, 109, 0x03),\n        (2, 110, 0x02),\n        (9, 110, 0x02),\n        (23, 110, 0x02),\n        (40, 110, 0x03),\n        (2, 112, 0x02),\n        (9, 112, 0x02),\n        (23, 112, 0x02),\n        (40, 112, 0x03),\n    ],\n    // 33\n    [\n        (3, 108, 0x02),\n        (6, 108, 0x02),\n        (10, 108, 0x02),\n        (15, 108, 0x02),\n        (24, 108, 0x02),\n        (31, 108, 0x02),\n        (41, 108, 0x02),\n        (56, 108, 0x03),\n        (3, 109, 0x02),\n        (6, 109, 0x02),\n        (10, 109, 0x02),\n        (15, 109, 0x02),\n        (24, 109, 0x02),\n        (31, 109, 0x02),\n        (41, 109, 0x02),\n        (56, 109, 0x03),\n    ],\n    // 34\n    [\n        (3, 110, 0x02),\n        (6, 110, 0x02),\n        (10, 110, 0x02),\n        (15, 110, 0x02),\n        (24, 110, 0x02),\n        (31, 110, 0x02),\n        (41, 110, 0x02),\n        (56, 110, 0x03),\n        (3, 112, 0x02),\n        (6, 112, 0x02),\n        (10, 112, 0x02),\n        (15, 112, 0x02),\n        (24, 112, 0x02),\n        (31, 112, 0x02),\n        (41, 112, 0x02),\n        (56, 112, 0x03),\n    ],\n    // 35\n    [\n        (2, 114, 0x02),\n        (9, 114, 0x02),\n        (23, 114, 0x02),\n        (40, 114, 0x03),\n        (2, 117, 0x02),\n        (9, 117, 0x02),\n        (23, 117, 0x02),\n        (40, 117, 0x03),\n        (1, 58, 0x02),\n        (22, 58, 0x03),\n        (1, 66, 0x02),\n        (22, 66, 0x03),\n        (1, 67, 0x02),\n        (22, 67, 0x03),\n        (1, 68, 0x02),\n        (22, 68, 0x03),\n    ],\n    // 36\n    [\n        (3, 114, 0x02),\n        (6, 114, 0x02),\n        (10, 114, 0x02),\n        (15, 114, 0x02),\n        (24, 114, 0x02),\n        (31, 114, 0x02),\n        (41, 114, 0x02),\n        (56, 114, 0x03),\n        (3, 117, 0x02),\n        (6, 117, 0x02),\n        (10, 117, 0x02),\n        (15, 117, 0x02),\n        (24, 117, 0x02),\n        (31, 117, 0x02),\n        (41, 117, 0x02),\n        (56, 117, 0x03),\n    ],\n    // 37\n    [\n        (2, 58, 0x02),\n        (9, 58, 0x02),\n        (23, 58, 0x02),\n        (40, 58, 0x03),\n        (2, 66, 0x02),\n        (9, 66, 0x02),\n        (23, 66, 0x02),\n        (40, 66, 0x03),\n        (2, 67, 0x02),\n        (9, 67, 0x02),\n        (23, 67, 0x02),\n        (40, 67, 0x03),\n        (2, 68, 0x02),\n        (9, 68, 0x02),\n        (23, 68, 0x02),\n        (40, 68, 0x03),\n    ],\n    // 38\n    [\n        (3, 58, 0x02),\n        (6, 58, 0x02),\n        (10, 58, 0x02),\n        (15, 58, 0x02),\n        (24, 58, 0x02),\n        (31, 58, 0x02),\n        (41, 58, 0x02),\n        (56, 58, 0x03),\n        (3, 66, 0x02),\n        (6, 66, 0x02),\n        (10, 66, 0x02),\n        (15, 66, 0x02),\n        (24, 66, 0x02),\n        (31, 66, 0x02),\n        (41, 66, 0x02),\n        (56, 66, 0x03),\n    ],\n    // 39\n    [\n        (3, 67, 0x02),\n        (6, 67, 0x02),\n        (10, 67, 0x02),\n        (15, 67, 0x02),\n        (24, 67, 0x02),\n        (31, 67, 0x02),\n        (41, 67, 0x02),\n        (56, 67, 0x03),\n        (3, 68, 0x02),\n        (6, 68, 0x02),\n        (10, 68, 0x02),\n        (15, 68, 0x02),\n        (24, 68, 0x02),\n        (31, 68, 0x02),\n        (41, 68, 0x02),\n        (56, 68, 0x03),\n    ],\n    // 40\n    [\n        (44, 0, 0x00),\n        (45, 0, 0x00),\n        (47, 0, 0x00),\n        (48, 0, 0x00),\n        (51, 0, 0x00),\n        (52, 0, 0x00),\n        (54, 0, 0x00),\n        (55, 0, 0x00),\n        (59, 0, 0x00),\n        (60, 0, 0x00),\n        (62, 0, 0x00),\n        (63, 0, 0x00),\n        (66, 0, 0x00),\n        (67, 0, 0x00),\n        (69, 0, 0x00),\n        (72, 0, 0x01),\n    ],\n    // 41\n    [\n        (0, 69, 0x02),\n        (0, 70, 0x02),\n        (0, 71, 0x02),\n        (0, 72, 0x02),\n        (0, 73, 0x02),\n        (0, 74, 0x02),\n        (0, 75, 0x02),\n        (0, 76, 0x02),\n        (0, 77, 0x02),\n        (0, 78, 0x02),\n        (0, 79, 0x02),\n        (0, 80, 0x02),\n        (0, 81, 0x02),\n        (0, 82, 0x02),\n        (0, 83, 0x02),\n        (0, 84, 0x02),\n    ],\n    // 42\n    [\n        (1, 69, 0x02),\n        (22, 69, 0x03),\n        (1, 70, 0x02),\n        (22, 70, 0x03),\n        (1, 71, 0x02),\n        (22, 71, 0x03),\n        (1, 72, 0x02),\n        (22, 72, 0x03),\n        (1, 73, 0x02),\n        (22, 73, 0x03),\n        (1, 74, 0x02),\n        (22, 74, 0x03),\n        (1, 75, 0x02),\n        (22, 75, 0x03),\n        (1, 76, 0x02),\n        (22, 76, 0x03),\n    ],\n    // 43\n    [\n        (2, 69, 0x02),\n        (9, 69, 0x02),\n        (23, 69, 0x02),\n        (40, 69, 0x03),\n        (2, 70, 0x02),\n        (9, 70, 0x02),\n        (23, 70, 0x02),\n        (40, 70, 0x03),\n        (2, 71, 0x02),\n        (9, 71, 0x02),\n        (23, 71, 0x02),\n        (40, 71, 0x03),\n        (2, 72, 0x02),\n        (9, 72, 0x02),\n        (23, 72, 0x02),\n        (40, 72, 0x03),\n    ],\n    // 44\n    [\n        (3, 69, 0x02),\n        (6, 69, 0x02),\n        (10, 69, 0x02),\n        (15, 69, 0x02),\n        (24, 69, 0x02),\n        (31, 69, 0x02),\n        (41, 69, 0x02),\n        (56, 69, 0x03),\n        (3, 70, 0x02),\n        (6, 70, 0x02),\n        (10, 70, 0x02),\n        (15, 70, 0x02),\n        (24, 70, 0x02),\n        (31, 70, 0x02),\n        (41, 70, 0x02),\n        (56, 70, 0x03),\n    ],\n    // 45\n    [\n        (3, 71, 0x02),\n        (6, 71, 0x02),\n        (10, 71, 0x02),\n        (15, 71, 0x02),\n        (24, 71, 0x02),\n        (31, 71, 0x02),\n        (41, 71, 0x02),\n        (56, 71, 0x03),\n        (3, 72, 0x02),\n        (6, 72, 0x02),\n        (10, 72, 0x02),\n        (15, 72, 0x02),\n        (24, 72, 0x02),\n        (31, 72, 0x02),\n        (41, 72, 0x02),\n        (56, 72, 0x03),\n    ],\n    // 46\n    [\n        (2, 73, 0x02),\n        (9, 73, 0x02),\n        (23, 73, 0x02),\n        (40, 73, 0x03),\n        (2, 74, 0x02),\n        (9, 74, 0x02),\n        (23, 74, 0x02),\n        (40, 74, 0x03),\n        (2, 75, 0x02),\n        (9, 75, 0x02),\n        (23, 75, 0x02),\n        (40, 75, 0x03),\n        (2, 76, 0x02),\n        (9, 76, 0x02),\n        (23, 76, 0x02),\n        (40, 76, 0x03),\n    ],\n    // 47\n    [\n        (3, 73, 0x02),\n        (6, 73, 0x02),\n        (10, 73, 0x02),\n        (15, 73, 0x02),\n        (24, 73, 0x02),\n        (31, 73, 0x02),\n        (41, 73, 0x02),\n        (56, 73, 0x03),\n        (3, 74, 0x02),\n        (6, 74, 0x02),\n        (10, 74, 0x02),\n        (15, 74, 0x02),\n        (24, 74, 0x02),\n        (31, 74, 0x02),\n        (41, 74, 0x02),\n        (56, 74, 0x03),\n    ],\n    // 48\n    [\n        (3, 75, 0x02),\n        (6, 75, 0x02),\n        (10, 75, 0x02),\n        (15, 75, 0x02),\n        (24, 75, 0x02),\n        (31, 75, 0x02),\n        (41, 75, 0x02),\n        (56, 75, 0x03),\n        (3, 76, 0x02),\n        (6, 76, 0x02),\n        (10, 76, 0x02),\n        (15, 76, 0x02),\n        (24, 76, 0x02),\n        (31, 76, 0x02),\n        (41, 76, 0x02),\n        (56, 76, 0x03),\n    ],\n    // 49\n    [\n        (1, 77, 0x02),\n        (22, 77, 0x03),\n        (1, 78, 0x02),\n        (22, 78, 0x03),\n        (1, 79, 0x02),\n        (22, 79, 0x03),\n        (1, 80, 0x02),\n        (22, 80, 0x03),\n        (1, 81, 0x02),\n        (22, 81, 0x03),\n        (1, 82, 0x02),\n        (22, 82, 0x03),\n        (1, 83, 0x02),\n        (22, 83, 0x03),\n        (1, 84, 0x02),\n        (22, 84, 0x03),\n    ],\n    // 50\n    [\n        (2, 77, 0x02),\n        (9, 77, 0x02),\n        (23, 77, 0x02),\n        (40, 77, 0x03),\n        (2, 78, 0x02),\n        (9, 78, 0x02),\n        (23, 78, 0x02),\n        (40, 78, 0x03),\n        (2, 79, 0x02),\n        (9, 79, 0x02),\n        (23, 79, 0x02),\n        (40, 79, 0x03),\n        (2, 80, 0x02),\n        (9, 80, 0x02),\n        (23, 80, 0x02),\n        (40, 80, 0x03),\n    ],\n    // 51\n    [\n        (3, 77, 0x02),\n        (6, 77, 0x02),\n        (10, 77, 0x02),\n        (15, 77, 0x02),\n        (24, 77, 0x02),\n        (31, 77, 0x02),\n        (41, 77, 0x02),\n        (56, 77, 0x03),\n        (3, 78, 0x02),\n        (6, 78, 0x02),\n        (10, 78, 0x02),\n        (15, 78, 0x02),\n        (24, 78, 0x02),\n        (31, 78, 0x02),\n        (41, 78, 0x02),\n        (56, 78, 0x03),\n    ],\n    // 52\n    [\n        (3, 79, 0x02),\n        (6, 79, 0x02),\n        (10, 79, 0x02),\n        (15, 79, 0x02),\n        (24, 79, 0x02),\n        (31, 79, 0x02),\n        (41, 79, 0x02),\n        (56, 79, 0x03),\n        (3, 80, 0x02),\n        (6, 80, 0x02),\n        (10, 80, 0x02),\n        (15, 80, 0x02),\n        (24, 80, 0x02),\n        (31, 80, 0x02),\n        (41, 80, 0x02),\n        (56, 80, 0x03),\n    ],\n    // 53\n    [\n        (2, 81, 0x02),\n        (9, 81, 0x02),\n        (23, 81, 0x02),\n        (40, 81, 0x03),\n        (2, 82, 0x02),\n        (9, 82, 0x02),\n        (23, 82, 0x02),\n        (40, 82, 0x03),\n        (2, 83, 0x02),\n        (9, 83, 0x02),\n        (23, 83, 0x02),\n        (40, 83, 0x03),\n        (2, 84, 0x02),\n        (9, 84, 0x02),\n        (23, 84, 0x02),\n        (40, 84, 0x03),\n    ],\n    // 54\n    [\n        (3, 81, 0x02),\n        (6, 81, 0x02),\n        (10, 81, 0x02),\n        (15, 81, 0x02),\n        (24, 81, 0x02),\n        (31, 81, 0x02),\n        (41, 81, 0x02),\n        (56, 81, 0x03),\n        (3, 82, 0x02),\n        (6, 82, 0x02),\n        (10, 82, 0x02),\n        (15, 82, 0x02),\n        (24, 82, 0x02),\n        (31, 82, 0x02),\n        (41, 82, 0x02),\n        (56, 82, 0x03),\n    ],\n    // 55\n    [\n        (3, 83, 0x02),\n        (6, 83, 0x02),\n        (10, 83, 0x02),\n        (15, 83, 0x02),\n        (24, 83, 0x02),\n        (31, 83, 0x02),\n        (41, 83, 0x02),\n        (56, 83, 0x03),\n        (3, 84, 0x02),\n        (6, 84, 0x02),\n        (10, 84, 0x02),\n        (15, 84, 0x02),\n        (24, 84, 0x02),\n        (31, 84, 0x02),\n        (41, 84, 0x02),\n        (56, 84, 0x03),\n    ],\n    // 56\n    [\n        (0, 85, 0x02),\n        (0, 86, 0x02),\n        (0, 87, 0x02),\n        (0, 89, 0x02),\n        (0, 106, 0x02),\n        (0, 107, 0x02),\n        (0, 113, 0x02),\n        (0, 118, 0x02),\n        (0, 119, 0x02),\n        (0, 120, 0x02),\n        (0, 121, 0x02),\n        (0, 122, 0x02),\n        (70, 0, 0x00),\n        (71, 0, 0x00),\n        (73, 0, 0x00),\n        (74, 0, 0x01),\n    ],\n    // 57\n    [\n        (1, 85, 0x02),\n        (22, 85, 0x03),\n        (1, 86, 0x02),\n        (22, 86, 0x03),\n        (1, 87, 0x02),\n        (22, 87, 0x03),\n        (1, 89, 0x02),\n        (22, 89, 0x03),\n        (1, 106, 0x02),\n        (22, 106, 0x03),\n        (1, 107, 0x02),\n        (22, 107, 0x03),\n        (1, 113, 0x02),\n        (22, 113, 0x03),\n        (1, 118, 0x02),\n        (22, 118, 0x03),\n    ],\n    // 58\n    [\n        (2, 85, 0x02),\n        (9, 85, 0x02),\n        (23, 85, 0x02),\n        (40, 85, 0x03),\n        (2, 86, 0x02),\n        (9, 86, 0x02),\n        (23, 86, 0x02),\n        (40, 86, 0x03),\n        (2, 87, 0x02),\n        (9, 87, 0x02),\n        (23, 87, 0x02),\n        (40, 87, 0x03),\n        (2, 89, 0x02),\n        (9, 89, 0x02),\n        (23, 89, 0x02),\n        (40, 89, 0x03),\n    ],\n    // 59\n    [\n        (3, 85, 0x02),\n        (6, 85, 0x02),\n        (10, 85, 0x02),\n        (15, 85, 0x02),\n        (24, 85, 0x02),\n        (31, 85, 0x02),\n        (41, 85, 0x02),\n        (56, 85, 0x03),\n        (3, 86, 0x02),\n        (6, 86, 0x02),\n        (10, 86, 0x02),\n        (15, 86, 0x02),\n        (24, 86, 0x02),\n        (31, 86, 0x02),\n        (41, 86, 0x02),\n        (56, 86, 0x03),\n    ],\n    // 60\n    [\n        (3, 87, 0x02),\n        (6, 87, 0x02),\n        (10, 87, 0x02),\n        (15, 87, 0x02),\n        (24, 87, 0x02),\n        (31, 87, 0x02),\n        (41, 87, 0x02),\n        (56, 87, 0x03),\n        (3, 89, 0x02),\n        (6, 89, 0x02),\n        (10, 89, 0x02),\n        (15, 89, 0x02),\n        (24, 89, 0x02),\n        (31, 89, 0x02),\n        (41, 89, 0x02),\n        (56, 89, 0x03),\n    ],\n    // 61\n    [\n        (2, 106, 0x02),\n        (9, 106, 0x02),\n        (23, 106, 0x02),\n        (40, 106, 0x03),\n        (2, 107, 0x02),\n        (9, 107, 0x02),\n        (23, 107, 0x02),\n        (40, 107, 0x03),\n        (2, 113, 0x02),\n        (9, 113, 0x02),\n        (23, 113, 0x02),\n        (40, 113, 0x03),\n        (2, 118, 0x02),\n        (9, 118, 0x02),\n        (23, 118, 0x02),\n        (40, 118, 0x03),\n    ],\n    // 62\n    [\n        (3, 106, 0x02),\n        (6, 106, 0x02),\n        (10, 106, 0x02),\n        (15, 106, 0x02),\n        (24, 106, 0x02),\n        (31, 106, 0x02),\n        (41, 106, 0x02),\n        (56, 106, 0x03),\n        (3, 107, 0x02),\n        (6, 107, 0x02),\n        (10, 107, 0x02),\n        (15, 107, 0x02),\n        (24, 107, 0x02),\n        (31, 107, 0x02),\n        (41, 107, 0x02),\n        (56, 107, 0x03),\n    ],\n    // 63\n    [\n        (3, 113, 0x02),\n        (6, 113, 0x02),\n        (10, 113, 0x02),\n        (15, 113, 0x02),\n        (24, 113, 0x02),\n        (31, 113, 0x02),\n        (41, 113, 0x02),\n        (56, 113, 0x03),\n        (3, 118, 0x02),\n        (6, 118, 0x02),\n        (10, 118, 0x02),\n        (15, 118, 0x02),\n        (24, 118, 0x02),\n        (31, 118, 0x02),\n        (41, 118, 0x02),\n        (56, 118, 0x03),\n    ],\n    // 64\n    [\n        (1, 119, 0x02),\n        (22, 119, 0x03),\n        (1, 120, 0x02),\n        (22, 120, 0x03),\n        (1, 121, 0x02),\n        (22, 121, 0x03),\n        (1, 122, 0x02),\n        (22, 122, 0x03),\n        (0, 38, 0x02),\n        (0, 42, 0x02),\n        (0, 44, 0x02),\n        (0, 59, 0x02),\n        (0, 88, 0x02),\n        (0, 90, 0x02),\n        (75, 0, 0x00),\n        (78, 0, 0x00),\n    ],\n    // 65\n    [\n        (2, 119, 0x02),\n        (9, 119, 0x02),\n        (23, 119, 0x02),\n        (40, 119, 0x03),\n        (2, 120, 0x02),\n        (9, 120, 0x02),\n        (23, 120, 0x02),\n        (40, 120, 0x03),\n        (2, 121, 0x02),\n        (9, 121, 0x02),\n        (23, 121, 0x02),\n        (40, 121, 0x03),\n        (2, 122, 0x02),\n        (9, 122, 0x02),\n        (23, 122, 0x02),\n        (40, 122, 0x03),\n    ],\n    // 66\n    [\n        (3, 119, 0x02),\n        (6, 119, 0x02),\n        (10, 119, 0x02),\n        (15, 119, 0x02),\n        (24, 119, 0x02),\n        (31, 119, 0x02),\n        (41, 119, 0x02),\n        (56, 119, 0x03),\n        (3, 120, 0x02),\n        (6, 120, 0x02),\n        (10, 120, 0x02),\n        (15, 120, 0x02),\n        (24, 120, 0x02),\n        (31, 120, 0x02),\n        (41, 120, 0x02),\n        (56, 120, 0x03),\n    ],\n    // 67\n    [\n        (3, 121, 0x02),\n        (6, 121, 0x02),\n        (10, 121, 0x02),\n        (15, 121, 0x02),\n        (24, 121, 0x02),\n        (31, 121, 0x02),\n        (41, 121, 0x02),\n        (56, 121, 0x03),\n        (3, 122, 0x02),\n        (6, 122, 0x02),\n        (10, 122, 0x02),\n        (15, 122, 0x02),\n        (24, 122, 0x02),\n        (31, 122, 0x02),\n        (41, 122, 0x02),\n        (56, 122, 0x03),\n    ],\n    // 68\n    [\n        (1, 38, 0x02),\n        (22, 38, 0x03),\n        (1, 42, 0x02),\n        (22, 42, 0x03),\n        (1, 44, 0x02),\n        (22, 44, 0x03),\n        (1, 59, 0x02),\n        (22, 59, 0x03),\n        (1, 88, 0x02),\n        (22, 88, 0x03),\n        (1, 90, 0x02),\n        (22, 90, 0x03),\n        (76, 0, 0x00),\n        (77, 0, 0x00),\n        (79, 0, 0x00),\n        (81, 0, 0x00),\n    ],\n    // 69\n    [\n        (2, 38, 0x02),\n        (9, 38, 0x02),\n        (23, 38, 0x02),\n        (40, 38, 0x03),\n        (2, 42, 0x02),\n        (9, 42, 0x02),\n        (23, 42, 0x02),\n        (40, 42, 0x03),\n        (2, 44, 0x02),\n        (9, 44, 0x02),\n        (23, 44, 0x02),\n        (40, 44, 0x03),\n        (2, 59, 0x02),\n        (9, 59, 0x02),\n        (23, 59, 0x02),\n        (40, 59, 0x03),\n    ],\n    // 70\n    [\n        (3, 38, 0x02),\n        (6, 38, 0x02),\n        (10, 38, 0x02),\n        (15, 38, 0x02),\n        (24, 38, 0x02),\n        (31, 38, 0x02),\n        (41, 38, 0x02),\n        (56, 38, 0x03),\n        (3, 42, 0x02),\n        (6, 42, 0x02),\n        (10, 42, 0x02),\n        (15, 42, 0x02),\n        (24, 42, 0x02),\n        (31, 42, 0x02),\n        (41, 42, 0x02),\n        (56, 42, 0x03),\n    ],\n    // 71\n    [\n        (3, 44, 0x02),\n        (6, 44, 0x02),\n        (10, 44, 0x02),\n        (15, 44, 0x02),\n        (24, 44, 0x02),\n        (31, 44, 0x02),\n        (41, 44, 0x02),\n        (56, 44, 0x03),\n        (3, 59, 0x02),\n        (6, 59, 0x02),\n        (10, 59, 0x02),\n        (15, 59, 0x02),\n        (24, 59, 0x02),\n        (31, 59, 0x02),\n        (41, 59, 0x02),\n        (56, 59, 0x03),\n    ],\n    // 72\n    [\n        (2, 88, 0x02),\n        (9, 88, 0x02),\n        (23, 88, 0x02),\n        (40, 88, 0x03),\n        (2, 90, 0x02),\n        (9, 90, 0x02),\n        (23, 90, 0x02),\n        (40, 90, 0x03),\n        (0, 33, 0x02),\n        (0, 34, 0x02),\n        (0, 40, 0x02),\n        (0, 41, 0x02),\n        (0, 63, 0x02),\n        (80, 0, 0x00),\n        (82, 0, 0x00),\n        (84, 0, 0x00),\n    ],\n    // 73\n    [\n        (3, 88, 0x02),\n        (6, 88, 0x02),\n        (10, 88, 0x02),\n        (15, 88, 0x02),\n        (24, 88, 0x02),\n        (31, 88, 0x02),\n        (41, 88, 0x02),\n        (56, 88, 0x03),\n        (3, 90, 0x02),\n        (6, 90, 0x02),\n        (10, 90, 0x02),\n        (15, 90, 0x02),\n        (24, 90, 0x02),\n        (31, 90, 0x02),\n        (41, 90, 0x02),\n        (56, 90, 0x03),\n    ],\n    // 74\n    [\n        (1, 33, 0x02),\n        (22, 33, 0x03),\n        (1, 34, 0x02),\n        (22, 34, 0x03),\n        (1, 40, 0x02),\n        (22, 40, 0x03),\n        (1, 41, 0x02),\n        (22, 41, 0x03),\n        (1, 63, 0x02),\n        (22, 63, 0x03),\n        (0, 39, 0x02),\n        (0, 43, 0x02),\n        (0, 124, 0x02),\n        (83, 0, 0x00),\n        (85, 0, 0x00),\n        (88, 0, 0x00),\n    ],\n    // 75\n    [\n        (2, 33, 0x02),\n        (9, 33, 0x02),\n        (23, 33, 0x02),\n        (40, 33, 0x03),\n        (2, 34, 0x02),\n        (9, 34, 0x02),\n        (23, 34, 0x02),\n        (40, 34, 0x03),\n        (2, 40, 0x02),\n        (9, 40, 0x02),\n        (23, 40, 0x02),\n        (40, 40, 0x03),\n        (2, 41, 0x02),\n        (9, 41, 0x02),\n        (23, 41, 0x02),\n        (40, 41, 0x03),\n    ],\n    // 76\n    [\n        (3, 33, 0x02),\n        (6, 33, 0x02),\n        (10, 33, 0x02),\n        (15, 33, 0x02),\n        (24, 33, 0x02),\n        (31, 33, 0x02),\n        (41, 33, 0x02),\n        (56, 33, 0x03),\n        (3, 34, 0x02),\n        (6, 34, 0x02),\n        (10, 34, 0x02),\n        (15, 34, 0x02),\n        (24, 34, 0x02),\n        (31, 34, 0x02),\n        (41, 34, 0x02),\n        (56, 34, 0x03),\n    ],\n    // 77\n    [\n        (3, 40, 0x02),\n        (6, 40, 0x02),\n        (10, 40, 0x02),\n        (15, 40, 0x02),\n        (24, 40, 0x02),\n        (31, 40, 0x02),\n        (41, 40, 0x02),\n        (56, 40, 0x03),\n        (3, 41, 0x02),\n        (6, 41, 0x02),\n        (10, 41, 0x02),\n        (15, 41, 0x02),\n        (24, 41, 0x02),\n        (31, 41, 0x02),\n        (41, 41, 0x02),\n        (56, 41, 0x03),\n    ],\n    // 78\n    [\n        (2, 63, 0x02),\n        (9, 63, 0x02),\n        (23, 63, 0x02),\n        (40, 63, 0x03),\n        (1, 39, 0x02),\n        (22, 39, 0x03),\n        (1, 43, 0x02),\n        (22, 43, 0x03),\n        (1, 124, 0x02),\n        (22, 124, 0x03),\n        (0, 35, 0x02),\n        (0, 62, 0x02),\n        (86, 0, 0x00),\n        (87, 0, 0x00),\n        (89, 0, 0x00),\n        (90, 0, 0x00),\n    ],\n    // 79\n    [\n        (3, 63, 0x02),\n        (6, 63, 0x02),\n        (10, 63, 0x02),\n        (15, 63, 0x02),\n        (24, 63, 0x02),\n        (31, 63, 0x02),\n        (41, 63, 0x02),\n        (56, 63, 0x03),\n        (2, 39, 0x02),\n        (9, 39, 0x02),\n        (23, 39, 0x02),\n        (40, 39, 0x03),\n        (2, 43, 0x02),\n        (9, 43, 0x02),\n        (23, 43, 0x02),\n        (40, 43, 0x03),\n    ],\n    // 80\n    [\n        (3, 39, 0x02),\n        (6, 39, 0x02),\n        (10, 39, 0x02),\n        (15, 39, 0x02),\n        (24, 39, 0x02),\n        (31, 39, 0x02),\n        (41, 39, 0x02),\n        (56, 39, 0x03),\n        (3, 43, 0x02),\n        (6, 43, 0x02),\n        (10, 43, 0x02),\n        (15, 43, 0x02),\n        (24, 43, 0x02),\n        (31, 43, 0x02),\n        (41, 43, 0x02),\n        (56, 43, 0x03),\n    ],\n    // 81\n    [\n        (2, 124, 0x02),\n        (9, 124, 0x02),\n        (23, 124, 0x02),\n        (40, 124, 0x03),\n        (1, 35, 0x02),\n        (22, 35, 0x03),\n        (1, 62, 0x02),\n        (22, 62, 0x03),\n        (0, 0, 0x02),\n        (0, 36, 0x02),\n        (0, 64, 0x02),\n        (0, 91, 0x02),\n        (0, 93, 0x02),\n        (0, 126, 0x02),\n        (91, 0, 0x00),\n        (92, 0, 0x00),\n    ],\n    // 82\n    [\n        (3, 124, 0x02),\n        (6, 124, 0x02),\n        (10, 124, 0x02),\n        (15, 124, 0x02),\n        (24, 124, 0x02),\n        (31, 124, 0x02),\n        (41, 124, 0x02),\n        (56, 124, 0x03),\n        (2, 35, 0x02),\n        (9, 35, 0x02),\n        (23, 35, 0x02),\n        (40, 35, 0x03),\n        (2, 62, 0x02),\n        (9, 62, 0x02),\n        (23, 62, 0x02),\n        (40, 62, 0x03),\n    ],\n    // 83\n    [\n        (3, 35, 0x02),\n        (6, 35, 0x02),\n        (10, 35, 0x02),\n        (15, 35, 0x02),\n        (24, 35, 0x02),\n        (31, 35, 0x02),\n        (41, 35, 0x02),\n        (56, 35, 0x03),\n        (3, 62, 0x02),\n        (6, 62, 0x02),\n        (10, 62, 0x02),\n        (15, 62, 0x02),\n        (24, 62, 0x02),\n        (31, 62, 0x02),\n        (41, 62, 0x02),\n        (56, 62, 0x03),\n    ],\n    // 84\n    [\n        (1, 0, 0x02),\n        (22, 0, 0x03),\n        (1, 36, 0x02),\n        (22, 36, 0x03),\n        (1, 64, 0x02),\n        (22, 64, 0x03),\n        (1, 91, 0x02),\n        (22, 91, 0x03),\n        (1, 93, 0x02),\n        (22, 93, 0x03),\n        (1, 126, 0x02),\n        (22, 126, 0x03),\n        (0, 94, 0x02),\n        (0, 125, 0x02),\n        (93, 0, 0x00),\n        (94, 0, 0x00),\n    ],\n    // 85\n    [\n        (2, 0, 0x02),\n        (9, 0, 0x02),\n        (23, 0, 0x02),\n        (40, 0, 0x03),\n        (2, 36, 0x02),\n        (9, 36, 0x02),\n        (23, 36, 0x02),\n        (40, 36, 0x03),\n        (2, 64, 0x02),\n        (9, 64, 0x02),\n        (23, 64, 0x02),\n        (40, 64, 0x03),\n        (2, 91, 0x02),\n        (9, 91, 0x02),\n        (23, 91, 0x02),\n        (40, 91, 0x03),\n    ],\n    // 86\n    [\n        (3, 0, 0x02),\n        (6, 0, 0x02),\n        (10, 0, 0x02),\n        (15, 0, 0x02),\n        (24, 0, 0x02),\n        (31, 0, 0x02),\n        (41, 0, 0x02),\n        (56, 0, 0x03),\n        (3, 36, 0x02),\n        (6, 36, 0x02),\n        (10, 36, 0x02),\n        (15, 36, 0x02),\n        (24, 36, 0x02),\n        (31, 36, 0x02),\n        (41, 36, 0x02),\n        (56, 36, 0x03),\n    ],\n    // 87\n    [\n        (3, 64, 0x02),\n        (6, 64, 0x02),\n        (10, 64, 0x02),\n        (15, 64, 0x02),\n        (24, 64, 0x02),\n        (31, 64, 0x02),\n        (41, 64, 0x02),\n        (56, 64, 0x03),\n        (3, 91, 0x02),\n        (6, 91, 0x02),\n        (10, 91, 0x02),\n        (15, 91, 0x02),\n        (24, 91, 0x02),\n        (31, 91, 0x02),\n        (41, 91, 0x02),\n        (56, 91, 0x03),\n    ],\n    // 88\n    [\n        (2, 93, 0x02),\n        (9, 93, 0x02),\n        (23, 93, 0x02),\n        (40, 93, 0x03),\n        (2, 126, 0x02),\n        (9, 126, 0x02),\n        (23, 126, 0x02),\n        (40, 126, 0x03),\n        (1, 94, 0x02),\n        (22, 94, 0x03),\n        (1, 125, 0x02),\n        (22, 125, 0x03),\n        (0, 60, 0x02),\n        (0, 96, 0x02),\n        (0, 123, 0x02),\n        (95, 0, 0x00),\n    ],\n    // 89\n    [\n        (3, 93, 0x02),\n        (6, 93, 0x02),\n        (10, 93, 0x02),\n        (15, 93, 0x02),\n        (24, 93, 0x02),\n        (31, 93, 0x02),\n        (41, 93, 0x02),\n        (56, 93, 0x03),\n        (3, 126, 0x02),\n        (6, 126, 0x02),\n        (10, 126, 0x02),\n        (15, 126, 0x02),\n        (24, 126, 0x02),\n        (31, 126, 0x02),\n        (41, 126, 0x02),\n        (56, 126, 0x03),\n    ],\n    // 90\n    [\n        (2, 94, 0x02),\n        (9, 94, 0x02),\n        (23, 94, 0x02),\n        (40, 94, 0x03),\n        (2, 125, 0x02),\n        (9, 125, 0x02),\n        (23, 125, 0x02),\n        (40, 125, 0x03),\n        (1, 60, 0x02),\n        (22, 60, 0x03),\n        (1, 96, 0x02),\n        (22, 96, 0x03),\n        (1, 123, 0x02),\n        (22, 123, 0x03),\n        (96, 0, 0x00),\n        (110, 0, 0x00),\n    ],\n    // 91\n    [\n        (3, 94, 0x02),\n        (6, 94, 0x02),\n        (10, 94, 0x02),\n        (15, 94, 0x02),\n        (24, 94, 0x02),\n        (31, 94, 0x02),\n        (41, 94, 0x02),\n        (56, 94, 0x03),\n        (3, 125, 0x02),\n        (6, 125, 0x02),\n        (10, 125, 0x02),\n        (15, 125, 0x02),\n        (24, 125, 0x02),\n        (31, 125, 0x02),\n        (41, 125, 0x02),\n        (56, 125, 0x03),\n    ],\n    // 92\n    [\n        (2, 60, 0x02),\n        (9, 60, 0x02),\n        (23, 60, 0x02),\n        (40, 60, 0x03),\n        (2, 96, 0x02),\n        (9, 96, 0x02),\n        (23, 96, 0x02),\n        (40, 96, 0x03),\n        (2, 123, 0x02),\n        (9, 123, 0x02),\n        (23, 123, 0x02),\n        (40, 123, 0x03),\n        (97, 0, 0x00),\n        (101, 0, 0x00),\n        (111, 0, 0x00),\n        (133, 0, 0x00),\n    ],\n    // 93\n    [\n        (3, 60, 0x02),\n        (6, 60, 0x02),\n        (10, 60, 0x02),\n        (15, 60, 0x02),\n        (24, 60, 0x02),\n        (31, 60, 0x02),\n        (41, 60, 0x02),\n        (56, 60, 0x03),\n        (3, 96, 0x02),\n        (6, 96, 0x02),\n        (10, 96, 0x02),\n        (15, 96, 0x02),\n        (24, 96, 0x02),\n        (31, 96, 0x02),\n        (41, 96, 0x02),\n        (56, 96, 0x03),\n    ],\n    // 94\n    [\n        (3, 123, 0x02),\n        (6, 123, 0x02),\n        (10, 123, 0x02),\n        (15, 123, 0x02),\n        (24, 123, 0x02),\n        (31, 123, 0x02),\n        (41, 123, 0x02),\n        (56, 123, 0x03),\n        (98, 0, 0x00),\n        (99, 0, 0x00),\n        (102, 0, 0x00),\n        (105, 0, 0x00),\n        (112, 0, 0x00),\n        (119, 0, 0x00),\n        (134, 0, 0x00),\n        (153, 0, 0x00),\n    ],\n    // 95\n    [\n        (0, 92, 0x02),\n        (0, 195, 0x02),\n        (0, 208, 0x02),\n        (100, 0, 0x00),\n        (103, 0, 0x00),\n        (104, 0, 0x00),\n        (106, 0, 0x00),\n        (107, 0, 0x00),\n        (113, 0, 0x00),\n        (116, 0, 0x00),\n        (120, 0, 0x00),\n        (126, 0, 0x00),\n        (135, 0, 0x00),\n        (142, 0, 0x00),\n        (154, 0, 0x00),\n        (169, 0, 0x00),\n    ],\n    // 96\n    [\n        (1, 92, 0x02),\n        (22, 92, 0x03),\n        (1, 195, 0x02),\n        (22, 195, 0x03),\n        (1, 208, 0x02),\n        (22, 208, 0x03),\n        (0, 128, 0x02),\n        (0, 130, 0x02),\n        (0, 131, 0x02),\n        (0, 162, 0x02),\n        (0, 184, 0x02),\n        (0, 194, 0x02),\n        (0, 224, 0x02),\n        (0, 226, 0x02),\n        (108, 0, 0x00),\n        (109, 0, 0x00),\n    ],\n    // 97\n    [\n        (2, 92, 0x02),\n        (9, 92, 0x02),\n        (23, 92, 0x02),\n        (40, 92, 0x03),\n        (2, 195, 0x02),\n        (9, 195, 0x02),\n        (23, 195, 0x02),\n        (40, 195, 0x03),\n        (2, 208, 0x02),\n        (9, 208, 0x02),\n        (23, 208, 0x02),\n        (40, 208, 0x03),\n        (1, 128, 0x02),\n        (22, 128, 0x03),\n        (1, 130, 0x02),\n        (22, 130, 0x03),\n    ],\n    // 98\n    [\n        (3, 92, 0x02),\n        (6, 92, 0x02),\n        (10, 92, 0x02),\n        (15, 92, 0x02),\n        (24, 92, 0x02),\n        (31, 92, 0x02),\n        (41, 92, 0x02),\n        (56, 92, 0x03),\n        (3, 195, 0x02),\n        (6, 195, 0x02),\n        (10, 195, 0x02),\n        (15, 195, 0x02),\n        (24, 195, 0x02),\n        (31, 195, 0x02),\n        (41, 195, 0x02),\n        (56, 195, 0x03),\n    ],\n    // 99\n    [\n        (3, 208, 0x02),\n        (6, 208, 0x02),\n        (10, 208, 0x02),\n        (15, 208, 0x02),\n        (24, 208, 0x02),\n        (31, 208, 0x02),\n        (41, 208, 0x02),\n        (56, 208, 0x03),\n        (2, 128, 0x02),\n        (9, 128, 0x02),\n        (23, 128, 0x02),\n        (40, 128, 0x03),\n        (2, 130, 0x02),\n        (9, 130, 0x02),\n        (23, 130, 0x02),\n        (40, 130, 0x03),\n    ],\n    // 100\n    [\n        (3, 128, 0x02),\n        (6, 128, 0x02),\n        (10, 128, 0x02),\n        (15, 128, 0x02),\n        (24, 128, 0x02),\n        (31, 128, 0x02),\n        (41, 128, 0x02),\n        (56, 128, 0x03),\n        (3, 130, 0x02),\n        (6, 130, 0x02),\n        (10, 130, 0x02),\n        (15, 130, 0x02),\n        (24, 130, 0x02),\n        (31, 130, 0x02),\n        (41, 130, 0x02),\n        (56, 130, 0x03),\n    ],\n    // 101\n    [\n        (1, 131, 0x02),\n        (22, 131, 0x03),\n        (1, 162, 0x02),\n        (22, 162, 0x03),\n        (1, 184, 0x02),\n        (22, 184, 0x03),\n        (1, 194, 0x02),\n        (22, 194, 0x03),\n        (1, 224, 0x02),\n        (22, 224, 0x03),\n        (1, 226, 0x02),\n        (22, 226, 0x03),\n        (0, 153, 0x02),\n        (0, 161, 0x02),\n        (0, 167, 0x02),\n        (0, 172, 0x02),\n    ],\n    // 102\n    [\n        (2, 131, 0x02),\n        (9, 131, 0x02),\n        (23, 131, 0x02),\n        (40, 131, 0x03),\n        (2, 162, 0x02),\n        (9, 162, 0x02),\n        (23, 162, 0x02),\n        (40, 162, 0x03),\n        (2, 184, 0x02),\n        (9, 184, 0x02),\n        (23, 184, 0x02),\n        (40, 184, 0x03),\n        (2, 194, 0x02),\n        (9, 194, 0x02),\n        (23, 194, 0x02),\n        (40, 194, 0x03),\n    ],\n    // 103\n    [\n        (3, 131, 0x02),\n        (6, 131, 0x02),\n        (10, 131, 0x02),\n        (15, 131, 0x02),\n        (24, 131, 0x02),\n        (31, 131, 0x02),\n        (41, 131, 0x02),\n        (56, 131, 0x03),\n        (3, 162, 0x02),\n        (6, 162, 0x02),\n        (10, 162, 0x02),\n        (15, 162, 0x02),\n        (24, 162, 0x02),\n        (31, 162, 0x02),\n        (41, 162, 0x02),\n        (56, 162, 0x03),\n    ],\n    // 104\n    [\n        (3, 184, 0x02),\n        (6, 184, 0x02),\n        (10, 184, 0x02),\n        (15, 184, 0x02),\n        (24, 184, 0x02),\n        (31, 184, 0x02),\n        (41, 184, 0x02),\n        (56, 184, 0x03),\n        (3, 194, 0x02),\n        (6, 194, 0x02),\n        (10, 194, 0x02),\n        (15, 194, 0x02),\n        (24, 194, 0x02),\n        (31, 194, 0x02),\n        (41, 194, 0x02),\n        (56, 194, 0x03),\n    ],\n    // 105\n    [\n        (2, 224, 0x02),\n        (9, 224, 0x02),\n        (23, 224, 0x02),\n        (40, 224, 0x03),\n        (2, 226, 0x02),\n        (9, 226, 0x02),\n        (23, 226, 0x02),\n        (40, 226, 0x03),\n        (1, 153, 0x02),\n        (22, 153, 0x03),\n        (1, 161, 0x02),\n        (22, 161, 0x03),\n        (1, 167, 0x02),\n        (22, 167, 0x03),\n        (1, 172, 0x02),\n        (22, 172, 0x03),\n    ],\n    // 106\n    [\n        (3, 224, 0x02),\n        (6, 224, 0x02),\n        (10, 224, 0x02),\n        (15, 224, 0x02),\n        (24, 224, 0x02),\n        (31, 224, 0x02),\n        (41, 224, 0x02),\n        (56, 224, 0x03),\n        (3, 226, 0x02),\n        (6, 226, 0x02),\n        (10, 226, 0x02),\n        (15, 226, 0x02),\n        (24, 226, 0x02),\n        (31, 226, 0x02),\n        (41, 226, 0x02),\n        (56, 226, 0x03),\n    ],\n    // 107\n    [\n        (2, 153, 0x02),\n        (9, 153, 0x02),\n        (23, 153, 0x02),\n        (40, 153, 0x03),\n        (2, 161, 0x02),\n        (9, 161, 0x02),\n        (23, 161, 0x02),\n        (40, 161, 0x03),\n        (2, 167, 0x02),\n        (9, 167, 0x02),\n        (23, 167, 0x02),\n        (40, 167, 0x03),\n        (2, 172, 0x02),\n        (9, 172, 0x02),\n        (23, 172, 0x02),\n        (40, 172, 0x03),\n    ],\n    // 108\n    [\n        (3, 153, 0x02),\n        (6, 153, 0x02),\n        (10, 153, 0x02),\n        (15, 153, 0x02),\n        (24, 153, 0x02),\n        (31, 153, 0x02),\n        (41, 153, 0x02),\n        (56, 153, 0x03),\n        (3, 161, 0x02),\n        (6, 161, 0x02),\n        (10, 161, 0x02),\n        (15, 161, 0x02),\n        (24, 161, 0x02),\n        (31, 161, 0x02),\n        (41, 161, 0x02),\n        (56, 161, 0x03),\n    ],\n    // 109\n    [\n        (3, 167, 0x02),\n        (6, 167, 0x02),\n        (10, 167, 0x02),\n        (15, 167, 0x02),\n        (24, 167, 0x02),\n        (31, 167, 0x02),\n        (41, 167, 0x02),\n        (56, 167, 0x03),\n        (3, 172, 0x02),\n        (6, 172, 0x02),\n        (10, 172, 0x02),\n        (15, 172, 0x02),\n        (24, 172, 0x02),\n        (31, 172, 0x02),\n        (41, 172, 0x02),\n        (56, 172, 0x03),\n    ],\n    // 110\n    [\n        (114, 0, 0x00),\n        (115, 0, 0x00),\n        (117, 0, 0x00),\n        (118, 0, 0x00),\n        (121, 0, 0x00),\n        (123, 0, 0x00),\n        (127, 0, 0x00),\n        (130, 0, 0x00),\n        (136, 0, 0x00),\n        (139, 0, 0x00),\n        (143, 0, 0x00),\n        (146, 0, 0x00),\n        (155, 0, 0x00),\n        (162, 0, 0x00),\n        (170, 0, 0x00),\n        (180, 0, 0x00),\n    ],\n    // 111\n    [\n        (0, 176, 0x02),\n        (0, 177, 0x02),\n        (0, 179, 0x02),\n        (0, 209, 0x02),\n        (0, 216, 0x02),\n        (0, 217, 0x02),\n        (0, 227, 0x02),\n        (0, 229, 0x02),\n        (0, 230, 0x02),\n        (122, 0, 0x00),\n        (124, 0, 0x00),\n        (125, 0, 0x00),\n        (128, 0, 0x00),\n        (129, 0, 0x00),\n        (131, 0, 0x00),\n        (132, 0, 0x00),\n    ],\n    // 112\n    [\n        (1, 176, 0x02),\n        (22, 176, 0x03),\n        (1, 177, 0x02),\n        (22, 177, 0x03),\n        (1, 179, 0x02),\n        (22, 179, 0x03),\n        (1, 209, 0x02),\n        (22, 209, 0x03),\n        (1, 216, 0x02),\n        (22, 216, 0x03),\n        (1, 217, 0x02),\n        (22, 217, 0x03),\n        (1, 227, 0x02),\n        (22, 227, 0x03),\n        (1, 229, 0x02),\n        (22, 229, 0x03),\n    ],\n    // 113\n    [\n        (2, 176, 0x02),\n        (9, 176, 0x02),\n        (23, 176, 0x02),\n        (40, 176, 0x03),\n        (2, 177, 0x02),\n        (9, 177, 0x02),\n        (23, 177, 0x02),\n        (40, 177, 0x03),\n        (2, 179, 0x02),\n        (9, 179, 0x02),\n        (23, 179, 0x02),\n        (40, 179, 0x03),\n        (2, 209, 0x02),\n        (9, 209, 0x02),\n        (23, 209, 0x02),\n        (40, 209, 0x03),\n    ],\n    // 114\n    [\n        (3, 176, 0x02),\n        (6, 176, 0x02),\n        (10, 176, 0x02),\n        (15, 176, 0x02),\n        (24, 176, 0x02),\n        (31, 176, 0x02),\n        (41, 176, 0x02),\n        (56, 176, 0x03),\n        (3, 177, 0x02),\n        (6, 177, 0x02),\n        (10, 177, 0x02),\n        (15, 177, 0x02),\n        (24, 177, 0x02),\n        (31, 177, 0x02),\n        (41, 177, 0x02),\n        (56, 177, 0x03),\n    ],\n    // 115\n    [\n        (3, 179, 0x02),\n        (6, 179, 0x02),\n        (10, 179, 0x02),\n        (15, 179, 0x02),\n        (24, 179, 0x02),\n        (31, 179, 0x02),\n        (41, 179, 0x02),\n        (56, 179, 0x03),\n        (3, 209, 0x02),\n        (6, 209, 0x02),\n        (10, 209, 0x02),\n        (15, 209, 0x02),\n        (24, 209, 0x02),\n        (31, 209, 0x02),\n        (41, 209, 0x02),\n        (56, 209, 0x03),\n    ],\n    // 116\n    [\n        (2, 216, 0x02),\n        (9, 216, 0x02),\n        (23, 216, 0x02),\n        (40, 216, 0x03),\n        (2, 217, 0x02),\n        (9, 217, 0x02),\n        (23, 217, 0x02),\n        (40, 217, 0x03),\n        (2, 227, 0x02),\n        (9, 227, 0x02),\n        (23, 227, 0x02),\n        (40, 227, 0x03),\n        (2, 229, 0x02),\n        (9, 229, 0x02),\n        (23, 229, 0x02),\n        (40, 229, 0x03),\n    ],\n    // 117\n    [\n        (3, 216, 0x02),\n        (6, 216, 0x02),\n        (10, 216, 0x02),\n        (15, 216, 0x02),\n        (24, 216, 0x02),\n        (31, 216, 0x02),\n        (41, 216, 0x02),\n        (56, 216, 0x03),\n        (3, 217, 0x02),\n        (6, 217, 0x02),\n        (10, 217, 0x02),\n        (15, 217, 0x02),\n        (24, 217, 0x02),\n        (31, 217, 0x02),\n        (41, 217, 0x02),\n        (56, 217, 0x03),\n    ],\n    // 118\n    [\n        (3, 227, 0x02),\n        (6, 227, 0x02),\n        (10, 227, 0x02),\n        (15, 227, 0x02),\n        (24, 227, 0x02),\n        (31, 227, 0x02),\n        (41, 227, 0x02),\n        (56, 227, 0x03),\n        (3, 229, 0x02),\n        (6, 229, 0x02),\n        (10, 229, 0x02),\n        (15, 229, 0x02),\n        (24, 229, 0x02),\n        (31, 229, 0x02),\n        (41, 229, 0x02),\n        (56, 229, 0x03),\n    ],\n    // 119\n    [\n        (1, 230, 0x02),\n        (22, 230, 0x03),\n        (0, 129, 0x02),\n        (0, 132, 0x02),\n        (0, 133, 0x02),\n        (0, 134, 0x02),\n        (0, 136, 0x02),\n        (0, 146, 0x02),\n        (0, 154, 0x02),\n        (0, 156, 0x02),\n        (0, 160, 0x02),\n        (0, 163, 0x02),\n        (0, 164, 0x02),\n        (0, 169, 0x02),\n        (0, 170, 0x02),\n        (0, 173, 0x02),\n    ],\n    // 120\n    [\n        (2, 230, 0x02),\n        (9, 230, 0x02),\n        (23, 230, 0x02),\n        (40, 230, 0x03),\n        (1, 129, 0x02),\n        (22, 129, 0x03),\n        (1, 132, 0x02),\n        (22, 132, 0x03),\n        (1, 133, 0x02),\n        (22, 133, 0x03),\n        (1, 134, 0x02),\n        (22, 134, 0x03),\n        (1, 136, 0x02),\n        (22, 136, 0x03),\n        (1, 146, 0x02),\n        (22, 146, 0x03),\n    ],\n    // 121\n    [\n        (3, 230, 0x02),\n        (6, 230, 0x02),\n        (10, 230, 0x02),\n        (15, 230, 0x02),\n        (24, 230, 0x02),\n        (31, 230, 0x02),\n        (41, 230, 0x02),\n        (56, 230, 0x03),\n        (2, 129, 0x02),\n        (9, 129, 0x02),\n        (23, 129, 0x02),\n        (40, 129, 0x03),\n        (2, 132, 0x02),\n        (9, 132, 0x02),\n        (23, 132, 0x02),\n        (40, 132, 0x03),\n    ],\n    // 122\n    [\n        (3, 129, 0x02),\n        (6, 129, 0x02),\n        (10, 129, 0x02),\n        (15, 129, 0x02),\n        (24, 129, 0x02),\n        (31, 129, 0x02),\n        (41, 129, 0x02),\n        (56, 129, 0x03),\n        (3, 132, 0x02),\n        (6, 132, 0x02),\n        (10, 132, 0x02),\n        (15, 132, 0x02),\n        (24, 132, 0x02),\n        (31, 132, 0x02),\n        (41, 132, 0x02),\n        (56, 132, 0x03),\n    ],\n    // 123\n    [\n        (2, 133, 0x02),\n        (9, 133, 0x02),\n        (23, 133, 0x02),\n        (40, 133, 0x03),\n        (2, 134, 0x02),\n        (9, 134, 0x02),\n        (23, 134, 0x02),\n        (40, 134, 0x03),\n        (2, 136, 0x02),\n        (9, 136, 0x02),\n        (23, 136, 0x02),\n        (40, 136, 0x03),\n        (2, 146, 0x02),\n        (9, 146, 0x02),\n        (23, 146, 0x02),\n        (40, 146, 0x03),\n    ],\n    // 124\n    [\n        (3, 133, 0x02),\n        (6, 133, 0x02),\n        (10, 133, 0x02),\n        (15, 133, 0x02),\n        (24, 133, 0x02),\n        (31, 133, 0x02),\n        (41, 133, 0x02),\n        (56, 133, 0x03),\n        (3, 134, 0x02),\n        (6, 134, 0x02),\n        (10, 134, 0x02),\n        (15, 134, 0x02),\n        (24, 134, 0x02),\n        (31, 134, 0x02),\n        (41, 134, 0x02),\n        (56, 134, 0x03),\n    ],\n    // 125\n    [\n        (3, 136, 0x02),\n        (6, 136, 0x02),\n        (10, 136, 0x02),\n        (15, 136, 0x02),\n        (24, 136, 0x02),\n        (31, 136, 0x02),\n        (41, 136, 0x02),\n        (56, 136, 0x03),\n        (3, 146, 0x02),\n        (6, 146, 0x02),\n        (10, 146, 0x02),\n        (15, 146, 0x02),\n        (24, 146, 0x02),\n        (31, 146, 0x02),\n        (41, 146, 0x02),\n        (56, 146, 0x03),\n    ],\n    // 126\n    [\n        (1, 154, 0x02),\n        (22, 154, 0x03),\n        (1, 156, 0x02),\n        (22, 156, 0x03),\n        (1, 160, 0x02),\n        (22, 160, 0x03),\n        (1, 163, 0x02),\n        (22, 163, 0x03),\n        (1, 164, 0x02),\n        (22, 164, 0x03),\n        (1, 169, 0x02),\n        (22, 169, 0x03),\n        (1, 170, 0x02),\n        (22, 170, 0x03),\n        (1, 173, 0x02),\n        (22, 173, 0x03),\n    ],\n    // 127\n    [\n        (2, 154, 0x02),\n        (9, 154, 0x02),\n        (23, 154, 0x02),\n        (40, 154, 0x03),\n        (2, 156, 0x02),\n        (9, 156, 0x02),\n        (23, 156, 0x02),\n        (40, 156, 0x03),\n        (2, 160, 0x02),\n        (9, 160, 0x02),\n        (23, 160, 0x02),\n        (40, 160, 0x03),\n        (2, 163, 0x02),\n        (9, 163, 0x02),\n        (23, 163, 0x02),\n        (40, 163, 0x03),\n    ],\n    // 128\n    [\n        (3, 154, 0x02),\n        (6, 154, 0x02),\n        (10, 154, 0x02),\n        (15, 154, 0x02),\n        (24, 154, 0x02),\n        (31, 154, 0x02),\n        (41, 154, 0x02),\n        (56, 154, 0x03),\n        (3, 156, 0x02),\n        (6, 156, 0x02),\n        (10, 156, 0x02),\n        (15, 156, 0x02),\n        (24, 156, 0x02),\n        (31, 156, 0x02),\n        (41, 156, 0x02),\n        (56, 156, 0x03),\n    ],\n    // 129\n    [\n        (3, 160, 0x02),\n        (6, 160, 0x02),\n        (10, 160, 0x02),\n        (15, 160, 0x02),\n        (24, 160, 0x02),\n        (31, 160, 0x02),\n        (41, 160, 0x02),\n        (56, 160, 0x03),\n        (3, 163, 0x02),\n        (6, 163, 0x02),\n        (10, 163, 0x02),\n        (15, 163, 0x02),\n        (24, 163, 0x02),\n        (31, 163, 0x02),\n        (41, 163, 0x02),\n        (56, 163, 0x03),\n    ],\n    // 130\n    [\n        (2, 164, 0x02),\n        (9, 164, 0x02),\n        (23, 164, 0x02),\n        (40, 164, 0x03),\n        (2, 169, 0x02),\n        (9, 169, 0x02),\n        (23, 169, 0x02),\n        (40, 169, 0x03),\n        (2, 170, 0x02),\n        (9, 170, 0x02),\n        (23, 170, 0x02),\n        (40, 170, 0x03),\n        (2, 173, 0x02),\n        (9, 173, 0x02),\n        (23, 173, 0x02),\n        (40, 173, 0x03),\n    ],\n    // 131\n    [\n        (3, 164, 0x02),\n        (6, 164, 0x02),\n        (10, 164, 0x02),\n        (15, 164, 0x02),\n        (24, 164, 0x02),\n        (31, 164, 0x02),\n        (41, 164, 0x02),\n        (56, 164, 0x03),\n        (3, 169, 0x02),\n        (6, 169, 0x02),\n        (10, 169, 0x02),\n        (15, 169, 0x02),\n        (24, 169, 0x02),\n        (31, 169, 0x02),\n        (41, 169, 0x02),\n        (56, 169, 0x03),\n    ],\n    // 132\n    [\n        (3, 170, 0x02),\n        (6, 170, 0x02),\n        (10, 170, 0x02),\n        (15, 170, 0x02),\n        (24, 170, 0x02),\n        (31, 170, 0x02),\n        (41, 170, 0x02),\n        (56, 170, 0x03),\n        (3, 173, 0x02),\n        (6, 173, 0x02),\n        (10, 173, 0x02),\n        (15, 173, 0x02),\n        (24, 173, 0x02),\n        (31, 173, 0x02),\n        (41, 173, 0x02),\n        (56, 173, 0x03),\n    ],\n    // 133\n    [\n        (137, 0, 0x00),\n        (138, 0, 0x00),\n        (140, 0, 0x00),\n        (141, 0, 0x00),\n        (144, 0, 0x00),\n        (145, 0, 0x00),\n        (147, 0, 0x00),\n        (150, 0, 0x00),\n        (156, 0, 0x00),\n        (159, 0, 0x00),\n        (163, 0, 0x00),\n        (166, 0, 0x00),\n        (171, 0, 0x00),\n        (174, 0, 0x00),\n        (181, 0, 0x00),\n        (190, 0, 0x00),\n    ],\n    // 134\n    [\n        (0, 178, 0x02),\n        (0, 181, 0x02),\n        (0, 185, 0x02),\n        (0, 186, 0x02),\n        (0, 187, 0x02),\n        (0, 189, 0x02),\n        (0, 190, 0x02),\n        (0, 196, 0x02),\n        (0, 198, 0x02),\n        (0, 228, 0x02),\n        (0, 232, 0x02),\n        (0, 233, 0x02),\n        (148, 0, 0x00),\n        (149, 0, 0x00),\n        (151, 0, 0x00),\n        (152, 0, 0x00),\n    ],\n    // 135\n    [\n        (1, 178, 0x02),\n        (22, 178, 0x03),\n        (1, 181, 0x02),\n        (22, 181, 0x03),\n        (1, 185, 0x02),\n        (22, 185, 0x03),\n        (1, 186, 0x02),\n        (22, 186, 0x03),\n        (1, 187, 0x02),\n        (22, 187, 0x03),\n        (1, 189, 0x02),\n        (22, 189, 0x03),\n        (1, 190, 0x02),\n        (22, 190, 0x03),\n        (1, 196, 0x02),\n        (22, 196, 0x03),\n    ],\n    // 136\n    [\n        (2, 178, 0x02),\n        (9, 178, 0x02),\n        (23, 178, 0x02),\n        (40, 178, 0x03),\n        (2, 181, 0x02),\n        (9, 181, 0x02),\n        (23, 181, 0x02),\n        (40, 181, 0x03),\n        (2, 185, 0x02),\n        (9, 185, 0x02),\n        (23, 185, 0x02),\n        (40, 185, 0x03),\n        (2, 186, 0x02),\n        (9, 186, 0x02),\n        (23, 186, 0x02),\n        (40, 186, 0x03),\n    ],\n    // 137\n    [\n        (3, 178, 0x02),\n        (6, 178, 0x02),\n        (10, 178, 0x02),\n        (15, 178, 0x02),\n        (24, 178, 0x02),\n        (31, 178, 0x02),\n        (41, 178, 0x02),\n        (56, 178, 0x03),\n        (3, 181, 0x02),\n        (6, 181, 0x02),\n        (10, 181, 0x02),\n        (15, 181, 0x02),\n        (24, 181, 0x02),\n        (31, 181, 0x02),\n        (41, 181, 0x02),\n        (56, 181, 0x03),\n    ],\n    // 138\n    [\n        (3, 185, 0x02),\n        (6, 185, 0x02),\n        (10, 185, 0x02),\n        (15, 185, 0x02),\n        (24, 185, 0x02),\n        (31, 185, 0x02),\n        (41, 185, 0x02),\n        (56, 185, 0x03),\n        (3, 186, 0x02),\n        (6, 186, 0x02),\n        (10, 186, 0x02),\n        (15, 186, 0x02),\n        (24, 186, 0x02),\n        (31, 186, 0x02),\n        (41, 186, 0x02),\n        (56, 186, 0x03),\n    ],\n    // 139\n    [\n        (2, 187, 0x02),\n        (9, 187, 0x02),\n        (23, 187, 0x02),\n        (40, 187, 0x03),\n        (2, 189, 0x02),\n        (9, 189, 0x02),\n        (23, 189, 0x02),\n        (40, 189, 0x03),\n        (2, 190, 0x02),\n        (9, 190, 0x02),\n        (23, 190, 0x02),\n        (40, 190, 0x03),\n        (2, 196, 0x02),\n        (9, 196, 0x02),\n        (23, 196, 0x02),\n        (40, 196, 0x03),\n    ],\n    // 140\n    [\n        (3, 187, 0x02),\n        (6, 187, 0x02),\n        (10, 187, 0x02),\n        (15, 187, 0x02),\n        (24, 187, 0x02),\n        (31, 187, 0x02),\n        (41, 187, 0x02),\n        (56, 187, 0x03),\n        (3, 189, 0x02),\n        (6, 189, 0x02),\n        (10, 189, 0x02),\n        (15, 189, 0x02),\n        (24, 189, 0x02),\n        (31, 189, 0x02),\n        (41, 189, 0x02),\n        (56, 189, 0x03),\n    ],\n    // 141\n    [\n        (3, 190, 0x02),\n        (6, 190, 0x02),\n        (10, 190, 0x02),\n        (15, 190, 0x02),\n        (24, 190, 0x02),\n        (31, 190, 0x02),\n        (41, 190, 0x02),\n        (56, 190, 0x03),\n        (3, 196, 0x02),\n        (6, 196, 0x02),\n        (10, 196, 0x02),\n        (15, 196, 0x02),\n        (24, 196, 0x02),\n        (31, 196, 0x02),\n        (41, 196, 0x02),\n        (56, 196, 0x03),\n    ],\n    // 142\n    [\n        (1, 198, 0x02),\n        (22, 198, 0x03),\n        (1, 228, 0x02),\n        (22, 228, 0x03),\n        (1, 232, 0x02),\n        (22, 232, 0x03),\n        (1, 233, 0x02),\n        (22, 233, 0x03),\n        (0, 1, 0x02),\n        (0, 135, 0x02),\n        (0, 137, 0x02),\n        (0, 138, 0x02),\n        (0, 139, 0x02),\n        (0, 140, 0x02),\n        (0, 141, 0x02),\n        (0, 143, 0x02),\n    ],\n    // 143\n    [\n        (2, 198, 0x02),\n        (9, 198, 0x02),\n        (23, 198, 0x02),\n        (40, 198, 0x03),\n        (2, 228, 0x02),\n        (9, 228, 0x02),\n        (23, 228, 0x02),\n        (40, 228, 0x03),\n        (2, 232, 0x02),\n        (9, 232, 0x02),\n        (23, 232, 0x02),\n        (40, 232, 0x03),\n        (2, 233, 0x02),\n        (9, 233, 0x02),\n        (23, 233, 0x02),\n        (40, 233, 0x03),\n    ],\n    // 144\n    [\n        (3, 198, 0x02),\n        (6, 198, 0x02),\n        (10, 198, 0x02),\n        (15, 198, 0x02),\n        (24, 198, 0x02),\n        (31, 198, 0x02),\n        (41, 198, 0x02),\n        (56, 198, 0x03),\n        (3, 228, 0x02),\n        (6, 228, 0x02),\n        (10, 228, 0x02),\n        (15, 228, 0x02),\n        (24, 228, 0x02),\n        (31, 228, 0x02),\n        (41, 228, 0x02),\n        (56, 228, 0x03),\n    ],\n    // 145\n    [\n        (3, 232, 0x02),\n        (6, 232, 0x02),\n        (10, 232, 0x02),\n        (15, 232, 0x02),\n        (24, 232, 0x02),\n        (31, 232, 0x02),\n        (41, 232, 0x02),\n        (56, 232, 0x03),\n        (3, 233, 0x02),\n        (6, 233, 0x02),\n        (10, 233, 0x02),\n        (15, 233, 0x02),\n        (24, 233, 0x02),\n        (31, 233, 0x02),\n        (41, 233, 0x02),\n        (56, 233, 0x03),\n    ],\n    // 146\n    [\n        (1, 1, 0x02),\n        (22, 1, 0x03),\n        (1, 135, 0x02),\n        (22, 135, 0x03),\n        (1, 137, 0x02),\n        (22, 137, 0x03),\n        (1, 138, 0x02),\n        (22, 138, 0x03),\n        (1, 139, 0x02),\n        (22, 139, 0x03),\n        (1, 140, 0x02),\n        (22, 140, 0x03),\n        (1, 141, 0x02),\n        (22, 141, 0x03),\n        (1, 143, 0x02),\n        (22, 143, 0x03),\n    ],\n    // 147\n    [\n        (2, 1, 0x02),\n        (9, 1, 0x02),\n        (23, 1, 0x02),\n        (40, 1, 0x03),\n        (2, 135, 0x02),\n        (9, 135, 0x02),\n        (23, 135, 0x02),\n        (40, 135, 0x03),\n        (2, 137, 0x02),\n        (9, 137, 0x02),\n        (23, 137, 0x02),\n        (40, 137, 0x03),\n        (2, 138, 0x02),\n        (9, 138, 0x02),\n        (23, 138, 0x02),\n        (40, 138, 0x03),\n    ],\n    // 148\n    [\n        (3, 1, 0x02),\n        (6, 1, 0x02),\n        (10, 1, 0x02),\n        (15, 1, 0x02),\n        (24, 1, 0x02),\n        (31, 1, 0x02),\n        (41, 1, 0x02),\n        (56, 1, 0x03),\n        (3, 135, 0x02),\n        (6, 135, 0x02),\n        (10, 135, 0x02),\n        (15, 135, 0x02),\n        (24, 135, 0x02),\n        (31, 135, 0x02),\n        (41, 135, 0x02),\n        (56, 135, 0x03),\n    ],\n    // 149\n    [\n        (3, 137, 0x02),\n        (6, 137, 0x02),\n        (10, 137, 0x02),\n        (15, 137, 0x02),\n        (24, 137, 0x02),\n        (31, 137, 0x02),\n        (41, 137, 0x02),\n        (56, 137, 0x03),\n        (3, 138, 0x02),\n        (6, 138, 0x02),\n        (10, 138, 0x02),\n        (15, 138, 0x02),\n        (24, 138, 0x02),\n        (31, 138, 0x02),\n        (41, 138, 0x02),\n        (56, 138, 0x03),\n    ],\n    // 150\n    [\n        (2, 139, 0x02),\n        (9, 139, 0x02),\n        (23, 139, 0x02),\n        (40, 139, 0x03),\n        (2, 140, 0x02),\n        (9, 140, 0x02),\n        (23, 140, 0x02),\n        (40, 140, 0x03),\n        (2, 141, 0x02),\n        (9, 141, 0x02),\n        (23, 141, 0x02),\n        (40, 141, 0x03),\n        (2, 143, 0x02),\n        (9, 143, 0x02),\n        (23, 143, 0x02),\n        (40, 143, 0x03),\n    ],\n    // 151\n    [\n        (3, 139, 0x02),\n        (6, 139, 0x02),\n        (10, 139, 0x02),\n        (15, 139, 0x02),\n        (24, 139, 0x02),\n        (31, 139, 0x02),\n        (41, 139, 0x02),\n        (56, 139, 0x03),\n        (3, 140, 0x02),\n        (6, 140, 0x02),\n        (10, 140, 0x02),\n        (15, 140, 0x02),\n        (24, 140, 0x02),\n        (31, 140, 0x02),\n        (41, 140, 0x02),\n        (56, 140, 0x03),\n    ],\n    // 152\n    [\n        (3, 141, 0x02),\n        (6, 141, 0x02),\n        (10, 141, 0x02),\n        (15, 141, 0x02),\n        (24, 141, 0x02),\n        (31, 141, 0x02),\n        (41, 141, 0x02),\n        (56, 141, 0x03),\n        (3, 143, 0x02),\n        (6, 143, 0x02),\n        (10, 143, 0x02),\n        (15, 143, 0x02),\n        (24, 143, 0x02),\n        (31, 143, 0x02),\n        (41, 143, 0x02),\n        (56, 143, 0x03),\n    ],\n    // 153\n    [\n        (157, 0, 0x00),\n        (158, 0, 0x00),\n        (160, 0, 0x00),\n        (161, 0, 0x00),\n        (164, 0, 0x00),\n        (165, 0, 0x00),\n        (167, 0, 0x00),\n        (168, 0, 0x00),\n        (172, 0, 0x00),\n        (173, 0, 0x00),\n        (175, 0, 0x00),\n        (177, 0, 0x00),\n        (182, 0, 0x00),\n        (185, 0, 0x00),\n        (191, 0, 0x00),\n        (207, 0, 0x00),\n    ],\n    // 154\n    [\n        (0, 147, 0x02),\n        (0, 149, 0x02),\n        (0, 150, 0x02),\n        (0, 151, 0x02),\n        (0, 152, 0x02),\n        (0, 155, 0x02),\n        (0, 157, 0x02),\n        (0, 158, 0x02),\n        (0, 165, 0x02),\n        (0, 166, 0x02),\n        (0, 168, 0x02),\n        (0, 174, 0x02),\n        (0, 175, 0x02),\n        (0, 180, 0x02),\n        (0, 182, 0x02),\n        (0, 183, 0x02),\n    ],\n    // 155\n    [\n        (1, 147, 0x02),\n        (22, 147, 0x03),\n        (1, 149, 0x02),\n        (22, 149, 0x03),\n        (1, 150, 0x02),\n        (22, 150, 0x03),\n        (1, 151, 0x02),\n        (22, 151, 0x03),\n        (1, 152, 0x02),\n        (22, 152, 0x03),\n        (1, 155, 0x02),\n        (22, 155, 0x03),\n        (1, 157, 0x02),\n        (22, 157, 0x03),\n        (1, 158, 0x02),\n        (22, 158, 0x03),\n    ],\n    // 156\n    [\n        (2, 147, 0x02),\n        (9, 147, 0x02),\n        (23, 147, 0x02),\n        (40, 147, 0x03),\n        (2, 149, 0x02),\n        (9, 149, 0x02),\n        (23, 149, 0x02),\n        (40, 149, 0x03),\n        (2, 150, 0x02),\n        (9, 150, 0x02),\n        (23, 150, 0x02),\n        (40, 150, 0x03),\n        (2, 151, 0x02),\n        (9, 151, 0x02),\n        (23, 151, 0x02),\n        (40, 151, 0x03),\n    ],\n    // 157\n    [\n        (3, 147, 0x02),\n        (6, 147, 0x02),\n        (10, 147, 0x02),\n        (15, 147, 0x02),\n        (24, 147, 0x02),\n        (31, 147, 0x02),\n        (41, 147, 0x02),\n        (56, 147, 0x03),\n        (3, 149, 0x02),\n        (6, 149, 0x02),\n        (10, 149, 0x02),\n        (15, 149, 0x02),\n        (24, 149, 0x02),\n        (31, 149, 0x02),\n        (41, 149, 0x02),\n        (56, 149, 0x03),\n    ],\n    // 158\n    [\n        (3, 150, 0x02),\n        (6, 150, 0x02),\n        (10, 150, 0x02),\n        (15, 150, 0x02),\n        (24, 150, 0x02),\n        (31, 150, 0x02),\n        (41, 150, 0x02),\n        (56, 150, 0x03),\n        (3, 151, 0x02),\n        (6, 151, 0x02),\n        (10, 151, 0x02),\n        (15, 151, 0x02),\n        (24, 151, 0x02),\n        (31, 151, 0x02),\n        (41, 151, 0x02),\n        (56, 151, 0x03),\n    ],\n    // 159\n    [\n        (2, 152, 0x02),\n        (9, 152, 0x02),\n        (23, 152, 0x02),\n        (40, 152, 0x03),\n        (2, 155, 0x02),\n        (9, 155, 0x02),\n        (23, 155, 0x02),\n        (40, 155, 0x03),\n        (2, 157, 0x02),\n        (9, 157, 0x02),\n        (23, 157, 0x02),\n        (40, 157, 0x03),\n        (2, 158, 0x02),\n        (9, 158, 0x02),\n        (23, 158, 0x02),\n        (40, 158, 0x03),\n    ],\n    // 160\n    [\n        (3, 152, 0x02),\n        (6, 152, 0x02),\n        (10, 152, 0x02),\n        (15, 152, 0x02),\n        (24, 152, 0x02),\n        (31, 152, 0x02),\n        (41, 152, 0x02),\n        (56, 152, 0x03),\n        (3, 155, 0x02),\n        (6, 155, 0x02),\n        (10, 155, 0x02),\n        (15, 155, 0x02),\n        (24, 155, 0x02),\n        (31, 155, 0x02),\n        (41, 155, 0x02),\n        (56, 155, 0x03),\n    ],\n    // 161\n    [\n        (3, 157, 0x02),\n        (6, 157, 0x02),\n        (10, 157, 0x02),\n        (15, 157, 0x02),\n        (24, 157, 0x02),\n        (31, 157, 0x02),\n        (41, 157, 0x02),\n        (56, 157, 0x03),\n        (3, 158, 0x02),\n        (6, 158, 0x02),\n        (10, 158, 0x02),\n        (15, 158, 0x02),\n        (24, 158, 0x02),\n        (31, 158, 0x02),\n        (41, 158, 0x02),\n        (56, 158, 0x03),\n    ],\n    // 162\n    [\n        (1, 165, 0x02),\n        (22, 165, 0x03),\n        (1, 166, 0x02),\n        (22, 166, 0x03),\n        (1, 168, 0x02),\n        (22, 168, 0x03),\n        (1, 174, 0x02),\n        (22, 174, 0x03),\n        (1, 175, 0x02),\n        (22, 175, 0x03),\n        (1, 180, 0x02),\n        (22, 180, 0x03),\n        (1, 182, 0x02),\n        (22, 182, 0x03),\n        (1, 183, 0x02),\n        (22, 183, 0x03),\n    ],\n    // 163\n    [\n        (2, 165, 0x02),\n        (9, 165, 0x02),\n        (23, 165, 0x02),\n        (40, 165, 0x03),\n        (2, 166, 0x02),\n        (9, 166, 0x02),\n        (23, 166, 0x02),\n        (40, 166, 0x03),\n        (2, 168, 0x02),\n        (9, 168, 0x02),\n        (23, 168, 0x02),\n        (40, 168, 0x03),\n        (2, 174, 0x02),\n        (9, 174, 0x02),\n        (23, 174, 0x02),\n        (40, 174, 0x03),\n    ],\n    // 164\n    [\n        (3, 165, 0x02),\n        (6, 165, 0x02),\n        (10, 165, 0x02),\n        (15, 165, 0x02),\n        (24, 165, 0x02),\n        (31, 165, 0x02),\n        (41, 165, 0x02),\n        (56, 165, 0x03),\n        (3, 166, 0x02),\n        (6, 166, 0x02),\n        (10, 166, 0x02),\n        (15, 166, 0x02),\n        (24, 166, 0x02),\n        (31, 166, 0x02),\n        (41, 166, 0x02),\n        (56, 166, 0x03),\n    ],\n    // 165\n    [\n        (3, 168, 0x02),\n        (6, 168, 0x02),\n        (10, 168, 0x02),\n        (15, 168, 0x02),\n        (24, 168, 0x02),\n        (31, 168, 0x02),\n        (41, 168, 0x02),\n        (56, 168, 0x03),\n        (3, 174, 0x02),\n        (6, 174, 0x02),\n        (10, 174, 0x02),\n        (15, 174, 0x02),\n        (24, 174, 0x02),\n        (31, 174, 0x02),\n        (41, 174, 0x02),\n        (56, 174, 0x03),\n    ],\n    // 166\n    [\n        (2, 175, 0x02),\n        (9, 175, 0x02),\n        (23, 175, 0x02),\n        (40, 175, 0x03),\n        (2, 180, 0x02),\n        (9, 180, 0x02),\n        (23, 180, 0x02),\n        (40, 180, 0x03),\n        (2, 182, 0x02),\n        (9, 182, 0x02),\n        (23, 182, 0x02),\n        (40, 182, 0x03),\n        (2, 183, 0x02),\n        (9, 183, 0x02),\n        (23, 183, 0x02),\n        (40, 183, 0x03),\n    ],\n    // 167\n    [\n        (3, 175, 0x02),\n        (6, 175, 0x02),\n        (10, 175, 0x02),\n        (15, 175, 0x02),\n        (24, 175, 0x02),\n        (31, 175, 0x02),\n        (41, 175, 0x02),\n        (56, 175, 0x03),\n        (3, 180, 0x02),\n        (6, 180, 0x02),\n        (10, 180, 0x02),\n        (15, 180, 0x02),\n        (24, 180, 0x02),\n        (31, 180, 0x02),\n        (41, 180, 0x02),\n        (56, 180, 0x03),\n    ],\n    // 168\n    [\n        (3, 182, 0x02),\n        (6, 182, 0x02),\n        (10, 182, 0x02),\n        (15, 182, 0x02),\n        (24, 182, 0x02),\n        (31, 182, 0x02),\n        (41, 182, 0x02),\n        (56, 182, 0x03),\n        (3, 183, 0x02),\n        (6, 183, 0x02),\n        (10, 183, 0x02),\n        (15, 183, 0x02),\n        (24, 183, 0x02),\n        (31, 183, 0x02),\n        (41, 183, 0x02),\n        (56, 183, 0x03),\n    ],\n    // 169\n    [\n        (0, 188, 0x02),\n        (0, 191, 0x02),\n        (0, 197, 0x02),\n        (0, 231, 0x02),\n        (0, 239, 0x02),\n        (176, 0, 0x00),\n        (178, 0, 0x00),\n        (179, 0, 0x00),\n        (183, 0, 0x00),\n        (184, 0, 0x00),\n        (186, 0, 0x00),\n        (187, 0, 0x00),\n        (192, 0, 0x00),\n        (199, 0, 0x00),\n        (208, 0, 0x00),\n        (223, 0, 0x00),\n    ],\n    // 170\n    [\n        (1, 188, 0x02),\n        (22, 188, 0x03),\n        (1, 191, 0x02),\n        (22, 191, 0x03),\n        (1, 197, 0x02),\n        (22, 197, 0x03),\n        (1, 231, 0x02),\n        (22, 231, 0x03),\n        (1, 239, 0x02),\n        (22, 239, 0x03),\n        (0, 9, 0x02),\n        (0, 142, 0x02),\n        (0, 144, 0x02),\n        (0, 145, 0x02),\n        (0, 148, 0x02),\n        (0, 159, 0x02),\n    ],\n    // 171\n    [\n        (2, 188, 0x02),\n        (9, 188, 0x02),\n        (23, 188, 0x02),\n        (40, 188, 0x03),\n        (2, 191, 0x02),\n        (9, 191, 0x02),\n        (23, 191, 0x02),\n        (40, 191, 0x03),\n        (2, 197, 0x02),\n        (9, 197, 0x02),\n        (23, 197, 0x02),\n        (40, 197, 0x03),\n        (2, 231, 0x02),\n        (9, 231, 0x02),\n        (23, 231, 0x02),\n        (40, 231, 0x03),\n    ],\n    // 172\n    [\n        (3, 188, 0x02),\n        (6, 188, 0x02),\n        (10, 188, 0x02),\n        (15, 188, 0x02),\n        (24, 188, 0x02),\n        (31, 188, 0x02),\n        (41, 188, 0x02),\n        (56, 188, 0x03),\n        (3, 191, 0x02),\n        (6, 191, 0x02),\n        (10, 191, 0x02),\n        (15, 191, 0x02),\n        (24, 191, 0x02),\n        (31, 191, 0x02),\n        (41, 191, 0x02),\n        (56, 191, 0x03),\n    ],\n    // 173\n    [\n        (3, 197, 0x02),\n        (6, 197, 0x02),\n        (10, 197, 0x02),\n        (15, 197, 0x02),\n        (24, 197, 0x02),\n        (31, 197, 0x02),\n        (41, 197, 0x02),\n        (56, 197, 0x03),\n        (3, 231, 0x02),\n        (6, 231, 0x02),\n        (10, 231, 0x02),\n        (15, 231, 0x02),\n        (24, 231, 0x02),\n        (31, 231, 0x02),\n        (41, 231, 0x02),\n        (56, 231, 0x03),\n    ],\n    // 174\n    [\n        (2, 239, 0x02),\n        (9, 239, 0x02),\n        (23, 239, 0x02),\n        (40, 239, 0x03),\n        (1, 9, 0x02),\n        (22, 9, 0x03),\n        (1, 142, 0x02),\n        (22, 142, 0x03),\n        (1, 144, 0x02),\n        (22, 144, 0x03),\n        (1, 145, 0x02),\n        (22, 145, 0x03),\n        (1, 148, 0x02),\n        (22, 148, 0x03),\n        (1, 159, 0x02),\n        (22, 159, 0x03),\n    ],\n    // 175\n    [\n        (3, 239, 0x02),\n        (6, 239, 0x02),\n        (10, 239, 0x02),\n        (15, 239, 0x02),\n        (24, 239, 0x02),\n        (31, 239, 0x02),\n        (41, 239, 0x02),\n        (56, 239, 0x03),\n        (2, 9, 0x02),\n        (9, 9, 0x02),\n        (23, 9, 0x02),\n        (40, 9, 0x03),\n        (2, 142, 0x02),\n        (9, 142, 0x02),\n        (23, 142, 0x02),\n        (40, 142, 0x03),\n    ],\n    // 176\n    [\n        (3, 9, 0x02),\n        (6, 9, 0x02),\n        (10, 9, 0x02),\n        (15, 9, 0x02),\n        (24, 9, 0x02),\n        (31, 9, 0x02),\n        (41, 9, 0x02),\n        (56, 9, 0x03),\n        (3, 142, 0x02),\n        (6, 142, 0x02),\n        (10, 142, 0x02),\n        (15, 142, 0x02),\n        (24, 142, 0x02),\n        (31, 142, 0x02),\n        (41, 142, 0x02),\n        (56, 142, 0x03),\n    ],\n    // 177\n    [\n        (2, 144, 0x02),\n        (9, 144, 0x02),\n        (23, 144, 0x02),\n        (40, 144, 0x03),\n        (2, 145, 0x02),\n        (9, 145, 0x02),\n        (23, 145, 0x02),\n        (40, 145, 0x03),\n        (2, 148, 0x02),\n        (9, 148, 0x02),\n        (23, 148, 0x02),\n        (40, 148, 0x03),\n        (2, 159, 0x02),\n        (9, 159, 0x02),\n        (23, 159, 0x02),\n        (40, 159, 0x03),\n    ],\n    // 178\n    [\n        (3, 144, 0x02),\n        (6, 144, 0x02),\n        (10, 144, 0x02),\n        (15, 144, 0x02),\n        (24, 144, 0x02),\n        (31, 144, 0x02),\n        (41, 144, 0x02),\n        (56, 144, 0x03),\n        (3, 145, 0x02),\n        (6, 145, 0x02),\n        (10, 145, 0x02),\n        (15, 145, 0x02),\n        (24, 145, 0x02),\n        (31, 145, 0x02),\n        (41, 145, 0x02),\n        (56, 145, 0x03),\n    ],\n    // 179\n    [\n        (3, 148, 0x02),\n        (6, 148, 0x02),\n        (10, 148, 0x02),\n        (15, 148, 0x02),\n        (24, 148, 0x02),\n        (31, 148, 0x02),\n        (41, 148, 0x02),\n        (56, 148, 0x03),\n        (3, 159, 0x02),\n        (6, 159, 0x02),\n        (10, 159, 0x02),\n        (15, 159, 0x02),\n        (24, 159, 0x02),\n        (31, 159, 0x02),\n        (41, 159, 0x02),\n        (56, 159, 0x03),\n    ],\n    // 180\n    [\n        (0, 171, 0x02),\n        (0, 206, 0x02),\n        (0, 215, 0x02),\n        (0, 225, 0x02),\n        (0, 236, 0x02),\n        (0, 237, 0x02),\n        (188, 0, 0x00),\n        (189, 0, 0x00),\n        (193, 0, 0x00),\n        (196, 0, 0x00),\n        (200, 0, 0x00),\n        (203, 0, 0x00),\n        (209, 0, 0x00),\n        (216, 0, 0x00),\n        (224, 0, 0x00),\n        (238, 0, 0x00),\n    ],\n    // 181\n    [\n        (1, 171, 0x02),\n        (22, 171, 0x03),\n        (1, 206, 0x02),\n        (22, 206, 0x03),\n        (1, 215, 0x02),\n        (22, 215, 0x03),\n        (1, 225, 0x02),\n        (22, 225, 0x03),\n        (1, 236, 0x02),\n        (22, 236, 0x03),\n        (1, 237, 0x02),\n        (22, 237, 0x03),\n        (0, 199, 0x02),\n        (0, 207, 0x02),\n        (0, 234, 0x02),\n        (0, 235, 0x02),\n    ],\n    // 182\n    [\n        (2, 171, 0x02),\n        (9, 171, 0x02),\n        (23, 171, 0x02),\n        (40, 171, 0x03),\n        (2, 206, 0x02),\n        (9, 206, 0x02),\n        (23, 206, 0x02),\n        (40, 206, 0x03),\n        (2, 215, 0x02),\n        (9, 215, 0x02),\n        (23, 215, 0x02),\n        (40, 215, 0x03),\n        (2, 225, 0x02),\n        (9, 225, 0x02),\n        (23, 225, 0x02),\n        (40, 225, 0x03),\n    ],\n    // 183\n    [\n        (3, 171, 0x02),\n        (6, 171, 0x02),\n        (10, 171, 0x02),\n        (15, 171, 0x02),\n        (24, 171, 0x02),\n        (31, 171, 0x02),\n        (41, 171, 0x02),\n        (56, 171, 0x03),\n        (3, 206, 0x02),\n        (6, 206, 0x02),\n        (10, 206, 0x02),\n        (15, 206, 0x02),\n        (24, 206, 0x02),\n        (31, 206, 0x02),\n        (41, 206, 0x02),\n        (56, 206, 0x03),\n    ],\n    // 184\n    [\n        (3, 215, 0x02),\n        (6, 215, 0x02),\n        (10, 215, 0x02),\n        (15, 215, 0x02),\n        (24, 215, 0x02),\n        (31, 215, 0x02),\n        (41, 215, 0x02),\n        (56, 215, 0x03),\n        (3, 225, 0x02),\n        (6, 225, 0x02),\n        (10, 225, 0x02),\n        (15, 225, 0x02),\n        (24, 225, 0x02),\n        (31, 225, 0x02),\n        (41, 225, 0x02),\n        (56, 225, 0x03),\n    ],\n    // 185\n    [\n        (2, 236, 0x02),\n        (9, 236, 0x02),\n        (23, 236, 0x02),\n        (40, 236, 0x03),\n        (2, 237, 0x02),\n        (9, 237, 0x02),\n        (23, 237, 0x02),\n        (40, 237, 0x03),\n        (1, 199, 0x02),\n        (22, 199, 0x03),\n        (1, 207, 0x02),\n        (22, 207, 0x03),\n        (1, 234, 0x02),\n        (22, 234, 0x03),\n        (1, 235, 0x02),\n        (22, 235, 0x03),\n    ],\n    // 186\n    [\n        (3, 236, 0x02),\n        (6, 236, 0x02),\n        (10, 236, 0x02),\n        (15, 236, 0x02),\n        (24, 236, 0x02),\n        (31, 236, 0x02),\n        (41, 236, 0x02),\n        (56, 236, 0x03),\n        (3, 237, 0x02),\n        (6, 237, 0x02),\n        (10, 237, 0x02),\n        (15, 237, 0x02),\n        (24, 237, 0x02),\n        (31, 237, 0x02),\n        (41, 237, 0x02),\n        (56, 237, 0x03),\n    ],\n    // 187\n    [\n        (2, 199, 0x02),\n        (9, 199, 0x02),\n        (23, 199, 0x02),\n        (40, 199, 0x03),\n        (2, 207, 0x02),\n        (9, 207, 0x02),\n        (23, 207, 0x02),\n        (40, 207, 0x03),\n        (2, 234, 0x02),\n        (9, 234, 0x02),\n        (23, 234, 0x02),\n        (40, 234, 0x03),\n        (2, 235, 0x02),\n        (9, 235, 0x02),\n        (23, 235, 0x02),\n        (40, 235, 0x03),\n    ],\n    // 188\n    [\n        (3, 199, 0x02),\n        (6, 199, 0x02),\n        (10, 199, 0x02),\n        (15, 199, 0x02),\n        (24, 199, 0x02),\n        (31, 199, 0x02),\n        (41, 199, 0x02),\n        (56, 199, 0x03),\n        (3, 207, 0x02),\n        (6, 207, 0x02),\n        (10, 207, 0x02),\n        (15, 207, 0x02),\n        (24, 207, 0x02),\n        (31, 207, 0x02),\n        (41, 207, 0x02),\n        (56, 207, 0x03),\n    ],\n    // 189\n    [\n        (3, 234, 0x02),\n        (6, 234, 0x02),\n        (10, 234, 0x02),\n        (15, 234, 0x02),\n        (24, 234, 0x02),\n        (31, 234, 0x02),\n        (41, 234, 0x02),\n        (56, 234, 0x03),\n        (3, 235, 0x02),\n        (6, 235, 0x02),\n        (10, 235, 0x02),\n        (15, 235, 0x02),\n        (24, 235, 0x02),\n        (31, 235, 0x02),\n        (41, 235, 0x02),\n        (56, 235, 0x03),\n    ],\n    // 190\n    [\n        (194, 0, 0x00),\n        (195, 0, 0x00),\n        (197, 0, 0x00),\n        (198, 0, 0x00),\n        (201, 0, 0x00),\n        (202, 0, 0x00),\n        (204, 0, 0x00),\n        (205, 0, 0x00),\n        (210, 0, 0x00),\n        (213, 0, 0x00),\n        (217, 0, 0x00),\n        (220, 0, 0x00),\n        (225, 0, 0x00),\n        (231, 0, 0x00),\n        (239, 0, 0x00),\n        (246, 0, 0x00),\n    ],\n    // 191\n    [\n        (0, 192, 0x02),\n        (0, 193, 0x02),\n        (0, 200, 0x02),\n        (0, 201, 0x02),\n        (0, 202, 0x02),\n        (0, 205, 0x02),\n        (0, 210, 0x02),\n        (0, 213, 0x02),\n        (0, 218, 0x02),\n        (0, 219, 0x02),\n        (0, 238, 0x02),\n        (0, 240, 0x02),\n        (0, 242, 0x02),\n        (0, 243, 0x02),\n        (0, 255, 0x02),\n        (206, 0, 0x00),\n    ],\n    // 192\n    [\n        (1, 192, 0x02),\n        (22, 192, 0x03),\n        (1, 193, 0x02),\n        (22, 193, 0x03),\n        (1, 200, 0x02),\n        (22, 200, 0x03),\n        (1, 201, 0x02),\n        (22, 201, 0x03),\n        (1, 202, 0x02),\n        (22, 202, 0x03),\n        (1, 205, 0x02),\n        (22, 205, 0x03),\n        (1, 210, 0x02),\n        (22, 210, 0x03),\n        (1, 213, 0x02),\n        (22, 213, 0x03),\n    ],\n    // 193\n    [\n        (2, 192, 0x02),\n        (9, 192, 0x02),\n        (23, 192, 0x02),\n        (40, 192, 0x03),\n        (2, 193, 0x02),\n        (9, 193, 0x02),\n        (23, 193, 0x02),\n        (40, 193, 0x03),\n        (2, 200, 0x02),\n        (9, 200, 0x02),\n        (23, 200, 0x02),\n        (40, 200, 0x03),\n        (2, 201, 0x02),\n        (9, 201, 0x02),\n        (23, 201, 0x02),\n        (40, 201, 0x03),\n    ],\n    // 194\n    [\n        (3, 192, 0x02),\n        (6, 192, 0x02),\n        (10, 192, 0x02),\n        (15, 192, 0x02),\n        (24, 192, 0x02),\n        (31, 192, 0x02),\n        (41, 192, 0x02),\n        (56, 192, 0x03),\n        (3, 193, 0x02),\n        (6, 193, 0x02),\n        (10, 193, 0x02),\n        (15, 193, 0x02),\n        (24, 193, 0x02),\n        (31, 193, 0x02),\n        (41, 193, 0x02),\n        (56, 193, 0x03),\n    ],\n    // 195\n    [\n        (3, 200, 0x02),\n        (6, 200, 0x02),\n        (10, 200, 0x02),\n        (15, 200, 0x02),\n        (24, 200, 0x02),\n        (31, 200, 0x02),\n        (41, 200, 0x02),\n        (56, 200, 0x03),\n        (3, 201, 0x02),\n        (6, 201, 0x02),\n        (10, 201, 0x02),\n        (15, 201, 0x02),\n        (24, 201, 0x02),\n        (31, 201, 0x02),\n        (41, 201, 0x02),\n        (56, 201, 0x03),\n    ],\n    // 196\n    [\n        (2, 202, 0x02),\n        (9, 202, 0x02),\n        (23, 202, 0x02),\n        (40, 202, 0x03),\n        (2, 205, 0x02),\n        (9, 205, 0x02),\n        (23, 205, 0x02),\n        (40, 205, 0x03),\n        (2, 210, 0x02),\n        (9, 210, 0x02),\n        (23, 210, 0x02),\n        (40, 210, 0x03),\n        (2, 213, 0x02),\n        (9, 213, 0x02),\n        (23, 213, 0x02),\n        (40, 213, 0x03),\n    ],\n    // 197\n    [\n        (3, 202, 0x02),\n        (6, 202, 0x02),\n        (10, 202, 0x02),\n        (15, 202, 0x02),\n        (24, 202, 0x02),\n        (31, 202, 0x02),\n        (41, 202, 0x02),\n        (56, 202, 0x03),\n        (3, 205, 0x02),\n        (6, 205, 0x02),\n        (10, 205, 0x02),\n        (15, 205, 0x02),\n        (24, 205, 0x02),\n        (31, 205, 0x02),\n        (41, 205, 0x02),\n        (56, 205, 0x03),\n    ],\n    // 198\n    [\n        (3, 210, 0x02),\n        (6, 210, 0x02),\n        (10, 210, 0x02),\n        (15, 210, 0x02),\n        (24, 210, 0x02),\n        (31, 210, 0x02),\n        (41, 210, 0x02),\n        (56, 210, 0x03),\n        (3, 213, 0x02),\n        (6, 213, 0x02),\n        (10, 213, 0x02),\n        (15, 213, 0x02),\n        (24, 213, 0x02),\n        (31, 213, 0x02),\n        (41, 213, 0x02),\n        (56, 213, 0x03),\n    ],\n    // 199\n    [\n        (1, 218, 0x02),\n        (22, 218, 0x03),\n        (1, 219, 0x02),\n        (22, 219, 0x03),\n        (1, 238, 0x02),\n        (22, 238, 0x03),\n        (1, 240, 0x02),\n        (22, 240, 0x03),\n        (1, 242, 0x02),\n        (22, 242, 0x03),\n        (1, 243, 0x02),\n        (22, 243, 0x03),\n        (1, 255, 0x02),\n        (22, 255, 0x03),\n        (0, 203, 0x02),\n        (0, 204, 0x02),\n    ],\n    // 200\n    [\n        (2, 218, 0x02),\n        (9, 218, 0x02),\n        (23, 218, 0x02),\n        (40, 218, 0x03),\n        (2, 219, 0x02),\n        (9, 219, 0x02),\n        (23, 219, 0x02),\n        (40, 219, 0x03),\n        (2, 238, 0x02),\n        (9, 238, 0x02),\n        (23, 238, 0x02),\n        (40, 238, 0x03),\n        (2, 240, 0x02),\n        (9, 240, 0x02),\n        (23, 240, 0x02),\n        (40, 240, 0x03),\n    ],\n    // 201\n    [\n        (3, 218, 0x02),\n        (6, 218, 0x02),\n        (10, 218, 0x02),\n        (15, 218, 0x02),\n        (24, 218, 0x02),\n        (31, 218, 0x02),\n        (41, 218, 0x02),\n        (56, 218, 0x03),\n        (3, 219, 0x02),\n        (6, 219, 0x02),\n        (10, 219, 0x02),\n        (15, 219, 0x02),\n        (24, 219, 0x02),\n        (31, 219, 0x02),\n        (41, 219, 0x02),\n        (56, 219, 0x03),\n    ],\n    // 202\n    [\n        (3, 238, 0x02),\n        (6, 238, 0x02),\n        (10, 238, 0x02),\n        (15, 238, 0x02),\n        (24, 238, 0x02),\n        (31, 238, 0x02),\n        (41, 238, 0x02),\n        (56, 238, 0x03),\n        (3, 240, 0x02),\n        (6, 240, 0x02),\n        (10, 240, 0x02),\n        (15, 240, 0x02),\n        (24, 240, 0x02),\n        (31, 240, 0x02),\n        (41, 240, 0x02),\n        (56, 240, 0x03),\n    ],\n    // 203\n    [\n        (2, 242, 0x02),\n        (9, 242, 0x02),\n        (23, 242, 0x02),\n        (40, 242, 0x03),\n        (2, 243, 0x02),\n        (9, 243, 0x02),\n        (23, 243, 0x02),\n        (40, 243, 0x03),\n        (2, 255, 0x02),\n        (9, 255, 0x02),\n        (23, 255, 0x02),\n        (40, 255, 0x03),\n        (1, 203, 0x02),\n        (22, 203, 0x03),\n        (1, 204, 0x02),\n        (22, 204, 0x03),\n    ],\n    // 204\n    [\n        (3, 242, 0x02),\n        (6, 242, 0x02),\n        (10, 242, 0x02),\n        (15, 242, 0x02),\n        (24, 242, 0x02),\n        (31, 242, 0x02),\n        (41, 242, 0x02),\n        (56, 242, 0x03),\n        (3, 243, 0x02),\n        (6, 243, 0x02),\n        (10, 243, 0x02),\n        (15, 243, 0x02),\n        (24, 243, 0x02),\n        (31, 243, 0x02),\n        (41, 243, 0x02),\n        (56, 243, 0x03),\n    ],\n    // 205\n    [\n        (3, 255, 0x02),\n        (6, 255, 0x02),\n        (10, 255, 0x02),\n        (15, 255, 0x02),\n        (24, 255, 0x02),\n        (31, 255, 0x02),\n        (41, 255, 0x02),\n        (56, 255, 0x03),\n        (2, 203, 0x02),\n        (9, 203, 0x02),\n        (23, 203, 0x02),\n        (40, 203, 0x03),\n        (2, 204, 0x02),\n        (9, 204, 0x02),\n        (23, 204, 0x02),\n        (40, 204, 0x03),\n    ],\n    // 206\n    [\n        (3, 203, 0x02),\n        (6, 203, 0x02),\n        (10, 203, 0x02),\n        (15, 203, 0x02),\n        (24, 203, 0x02),\n        (31, 203, 0x02),\n        (41, 203, 0x02),\n        (56, 203, 0x03),\n        (3, 204, 0x02),\n        (6, 204, 0x02),\n        (10, 204, 0x02),\n        (15, 204, 0x02),\n        (24, 204, 0x02),\n        (31, 204, 0x02),\n        (41, 204, 0x02),\n        (56, 204, 0x03),\n    ],\n    // 207\n    [\n        (211, 0, 0x00),\n        (212, 0, 0x00),\n        (214, 0, 0x00),\n        (215, 0, 0x00),\n        (218, 0, 0x00),\n        (219, 0, 0x00),\n        (221, 0, 0x00),\n        (222, 0, 0x00),\n        (226, 0, 0x00),\n        (228, 0, 0x00),\n        (232, 0, 0x00),\n        (235, 0, 0x00),\n        (240, 0, 0x00),\n        (243, 0, 0x00),\n        (247, 0, 0x00),\n        (250, 0, 0x00),\n    ],\n    // 208\n    [\n        (0, 211, 0x02),\n        (0, 212, 0x02),\n        (0, 214, 0x02),\n        (0, 221, 0x02),\n        (0, 222, 0x02),\n        (0, 223, 0x02),\n        (0, 241, 0x02),\n        (0, 244, 0x02),\n        (0, 245, 0x02),\n        (0, 246, 0x02),\n        (0, 247, 0x02),\n        (0, 248, 0x02),\n        (0, 250, 0x02),\n        (0, 251, 0x02),\n        (0, 252, 0x02),\n        (0, 253, 0x02),\n    ],\n    // 209\n    [\n        (1, 211, 0x02),\n        (22, 211, 0x03),\n        (1, 212, 0x02),\n        (22, 212, 0x03),\n        (1, 214, 0x02),\n        (22, 214, 0x03),\n        (1, 221, 0x02),\n        (22, 221, 0x03),\n        (1, 222, 0x02),\n        (22, 222, 0x03),\n        (1, 223, 0x02),\n        (22, 223, 0x03),\n        (1, 241, 0x02),\n        (22, 241, 0x03),\n        (1, 244, 0x02),\n        (22, 244, 0x03),\n    ],\n    // 210\n    [\n        (2, 211, 0x02),\n        (9, 211, 0x02),\n        (23, 211, 0x02),\n        (40, 211, 0x03),\n        (2, 212, 0x02),\n        (9, 212, 0x02),\n        (23, 212, 0x02),\n        (40, 212, 0x03),\n        (2, 214, 0x02),\n        (9, 214, 0x02),\n        (23, 214, 0x02),\n        (40, 214, 0x03),\n        (2, 221, 0x02),\n        (9, 221, 0x02),\n        (23, 221, 0x02),\n        (40, 221, 0x03),\n    ],\n    // 211\n    [\n        (3, 211, 0x02),\n        (6, 211, 0x02),\n        (10, 211, 0x02),\n        (15, 211, 0x02),\n        (24, 211, 0x02),\n        (31, 211, 0x02),\n        (41, 211, 0x02),\n        (56, 211, 0x03),\n        (3, 212, 0x02),\n        (6, 212, 0x02),\n        (10, 212, 0x02),\n        (15, 212, 0x02),\n        (24, 212, 0x02),\n        (31, 212, 0x02),\n        (41, 212, 0x02),\n        (56, 212, 0x03),\n    ],\n    // 212\n    [\n        (3, 214, 0x02),\n        (6, 214, 0x02),\n        (10, 214, 0x02),\n        (15, 214, 0x02),\n        (24, 214, 0x02),\n        (31, 214, 0x02),\n        (41, 214, 0x02),\n        (56, 214, 0x03),\n        (3, 221, 0x02),\n        (6, 221, 0x02),\n        (10, 221, 0x02),\n        (15, 221, 0x02),\n        (24, 221, 0x02),\n        (31, 221, 0x02),\n        (41, 221, 0x02),\n        (56, 221, 0x03),\n    ],\n    // 213\n    [\n        (2, 222, 0x02),\n        (9, 222, 0x02),\n        (23, 222, 0x02),\n        (40, 222, 0x03),\n        (2, 223, 0x02),\n        (9, 223, 0x02),\n        (23, 223, 0x02),\n        (40, 223, 0x03),\n        (2, 241, 0x02),\n        (9, 241, 0x02),\n        (23, 241, 0x02),\n        (40, 241, 0x03),\n        (2, 244, 0x02),\n        (9, 244, 0x02),\n        (23, 244, 0x02),\n        (40, 244, 0x03),\n    ],\n    // 214\n    [\n        (3, 222, 0x02),\n        (6, 222, 0x02),\n        (10, 222, 0x02),\n        (15, 222, 0x02),\n        (24, 222, 0x02),\n        (31, 222, 0x02),\n        (41, 222, 0x02),\n        (56, 222, 0x03),\n        (3, 223, 0x02),\n        (6, 223, 0x02),\n        (10, 223, 0x02),\n        (15, 223, 0x02),\n        (24, 223, 0x02),\n        (31, 223, 0x02),\n        (41, 223, 0x02),\n        (56, 223, 0x03),\n    ],\n    // 215\n    [\n        (3, 241, 0x02),\n        (6, 241, 0x02),\n        (10, 241, 0x02),\n        (15, 241, 0x02),\n        (24, 241, 0x02),\n        (31, 241, 0x02),\n        (41, 241, 0x02),\n        (56, 241, 0x03),\n        (3, 244, 0x02),\n        (6, 244, 0x02),\n        (10, 244, 0x02),\n        (15, 244, 0x02),\n        (24, 244, 0x02),\n        (31, 244, 0x02),\n        (41, 244, 0x02),\n        (56, 244, 0x03),\n    ],\n    // 216\n    [\n        (1, 245, 0x02),\n        (22, 245, 0x03),\n        (1, 246, 0x02),\n        (22, 246, 0x03),\n        (1, 247, 0x02),\n        (22, 247, 0x03),\n        (1, 248, 0x02),\n        (22, 248, 0x03),\n        (1, 250, 0x02),\n        (22, 250, 0x03),\n        (1, 251, 0x02),\n        (22, 251, 0x03),\n        (1, 252, 0x02),\n        (22, 252, 0x03),\n        (1, 253, 0x02),\n        (22, 253, 0x03),\n    ],\n    // 217\n    [\n        (2, 245, 0x02),\n        (9, 245, 0x02),\n        (23, 245, 0x02),\n        (40, 245, 0x03),\n        (2, 246, 0x02),\n        (9, 246, 0x02),\n        (23, 246, 0x02),\n        (40, 246, 0x03),\n        (2, 247, 0x02),\n        (9, 247, 0x02),\n        (23, 247, 0x02),\n        (40, 247, 0x03),\n        (2, 248, 0x02),\n        (9, 248, 0x02),\n        (23, 248, 0x02),\n        (40, 248, 0x03),\n    ],\n    // 218\n    [\n        (3, 245, 0x02),\n        (6, 245, 0x02),\n        (10, 245, 0x02),\n        (15, 245, 0x02),\n        (24, 245, 0x02),\n        (31, 245, 0x02),\n        (41, 245, 0x02),\n        (56, 245, 0x03),\n        (3, 246, 0x02),\n        (6, 246, 0x02),\n        (10, 246, 0x02),\n        (15, 246, 0x02),\n        (24, 246, 0x02),\n        (31, 246, 0x02),\n        (41, 246, 0x02),\n        (56, 246, 0x03),\n    ],\n    // 219\n    [\n        (3, 247, 0x02),\n        (6, 247, 0x02),\n        (10, 247, 0x02),\n        (15, 247, 0x02),\n        (24, 247, 0x02),\n        (31, 247, 0x02),\n        (41, 247, 0x02),\n        (56, 247, 0x03),\n        (3, 248, 0x02),\n        (6, 248, 0x02),\n        (10, 248, 0x02),\n        (15, 248, 0x02),\n        (24, 248, 0x02),\n        (31, 248, 0x02),\n        (41, 248, 0x02),\n        (56, 248, 0x03),\n    ],\n    // 220\n    [\n        (2, 250, 0x02),\n        (9, 250, 0x02),\n        (23, 250, 0x02),\n        (40, 250, 0x03),\n        (2, 251, 0x02),\n        (9, 251, 0x02),\n        (23, 251, 0x02),\n        (40, 251, 0x03),\n        (2, 252, 0x02),\n        (9, 252, 0x02),\n        (23, 252, 0x02),\n        (40, 252, 0x03),\n        (2, 253, 0x02),\n        (9, 253, 0x02),\n        (23, 253, 0x02),\n        (40, 253, 0x03),\n    ],\n    // 221\n    [\n        (3, 250, 0x02),\n        (6, 250, 0x02),\n        (10, 250, 0x02),\n        (15, 250, 0x02),\n        (24, 250, 0x02),\n        (31, 250, 0x02),\n        (41, 250, 0x02),\n        (56, 250, 0x03),\n        (3, 251, 0x02),\n        (6, 251, 0x02),\n        (10, 251, 0x02),\n        (15, 251, 0x02),\n        (24, 251, 0x02),\n        (31, 251, 0x02),\n        (41, 251, 0x02),\n        (56, 251, 0x03),\n    ],\n    // 222\n    [\n        (3, 252, 0x02),\n        (6, 252, 0x02),\n        (10, 252, 0x02),\n        (15, 252, 0x02),\n        (24, 252, 0x02),\n        (31, 252, 0x02),\n        (41, 252, 0x02),\n        (56, 252, 0x03),\n        (3, 253, 0x02),\n        (6, 253, 0x02),\n        (10, 253, 0x02),\n        (15, 253, 0x02),\n        (24, 253, 0x02),\n        (31, 253, 0x02),\n        (41, 253, 0x02),\n        (56, 253, 0x03),\n    ],\n    // 223\n    [\n        (0, 254, 0x02),\n        (227, 0, 0x00),\n        (229, 0, 0x00),\n        (230, 0, 0x00),\n        (233, 0, 0x00),\n        (234, 0, 0x00),\n        (236, 0, 0x00),\n        (237, 0, 0x00),\n        (241, 0, 0x00),\n        (242, 0, 0x00),\n        (244, 0, 0x00),\n        (245, 0, 0x00),\n        (248, 0, 0x00),\n        (249, 0, 0x00),\n        (251, 0, 0x00),\n        (252, 0, 0x00),\n    ],\n    // 224\n    [\n        (1, 254, 0x02),\n        (22, 254, 0x03),\n        (0, 2, 0x02),\n        (0, 3, 0x02),\n        (0, 4, 0x02),\n        (0, 5, 0x02),\n        (0, 6, 0x02),\n        (0, 7, 0x02),\n        (0, 8, 0x02),\n        (0, 11, 0x02),\n        (0, 12, 0x02),\n        (0, 14, 0x02),\n        (0, 15, 0x02),\n        (0, 16, 0x02),\n        (0, 17, 0x02),\n        (0, 18, 0x02),\n    ],\n    // 225\n    [\n        (2, 254, 0x02),\n        (9, 254, 0x02),\n        (23, 254, 0x02),\n        (40, 254, 0x03),\n        (1, 2, 0x02),\n        (22, 2, 0x03),\n        (1, 3, 0x02),\n        (22, 3, 0x03),\n        (1, 4, 0x02),\n        (22, 4, 0x03),\n        (1, 5, 0x02),\n        (22, 5, 0x03),\n        (1, 6, 0x02),\n        (22, 6, 0x03),\n        (1, 7, 0x02),\n        (22, 7, 0x03),\n    ],\n    // 226\n    [\n        (3, 254, 0x02),\n        (6, 254, 0x02),\n        (10, 254, 0x02),\n        (15, 254, 0x02),\n        (24, 254, 0x02),\n        (31, 254, 0x02),\n        (41, 254, 0x02),\n        (56, 254, 0x03),\n        (2, 2, 0x02),\n        (9, 2, 0x02),\n        (23, 2, 0x02),\n        (40, 2, 0x03),\n        (2, 3, 0x02),\n        (9, 3, 0x02),\n        (23, 3, 0x02),\n        (40, 3, 0x03),\n    ],\n    // 227\n    [\n        (3, 2, 0x02),\n        (6, 2, 0x02),\n        (10, 2, 0x02),\n        (15, 2, 0x02),\n        (24, 2, 0x02),\n        (31, 2, 0x02),\n        (41, 2, 0x02),\n        (56, 2, 0x03),\n        (3, 3, 0x02),\n        (6, 3, 0x02),\n        (10, 3, 0x02),\n        (15, 3, 0x02),\n        (24, 3, 0x02),\n        (31, 3, 0x02),\n        (41, 3, 0x02),\n        (56, 3, 0x03),\n    ],\n    // 228\n    [\n        (2, 4, 0x02),\n        (9, 4, 0x02),\n        (23, 4, 0x02),\n        (40, 4, 0x03),\n        (2, 5, 0x02),\n        (9, 5, 0x02),\n        (23, 5, 0x02),\n        (40, 5, 0x03),\n        (2, 6, 0x02),\n        (9, 6, 0x02),\n        (23, 6, 0x02),\n        (40, 6, 0x03),\n        (2, 7, 0x02),\n        (9, 7, 0x02),\n        (23, 7, 0x02),\n        (40, 7, 0x03),\n    ],\n    // 229\n    [\n        (3, 4, 0x02),\n        (6, 4, 0x02),\n        (10, 4, 0x02),\n        (15, 4, 0x02),\n        (24, 4, 0x02),\n        (31, 4, 0x02),\n        (41, 4, 0x02),\n        (56, 4, 0x03),\n        (3, 5, 0x02),\n        (6, 5, 0x02),\n        (10, 5, 0x02),\n        (15, 5, 0x02),\n        (24, 5, 0x02),\n        (31, 5, 0x02),\n        (41, 5, 0x02),\n        (56, 5, 0x03),\n    ],\n    // 230\n    [\n        (3, 6, 0x02),\n        (6, 6, 0x02),\n        (10, 6, 0x02),\n        (15, 6, 0x02),\n        (24, 6, 0x02),\n        (31, 6, 0x02),\n        (41, 6, 0x02),\n        (56, 6, 0x03),\n        (3, 7, 0x02),\n        (6, 7, 0x02),\n        (10, 7, 0x02),\n        (15, 7, 0x02),\n        (24, 7, 0x02),\n        (31, 7, 0x02),\n        (41, 7, 0x02),\n        (56, 7, 0x03),\n    ],\n    // 231\n    [\n        (1, 8, 0x02),\n        (22, 8, 0x03),\n        (1, 11, 0x02),\n        (22, 11, 0x03),\n        (1, 12, 0x02),\n        (22, 12, 0x03),\n        (1, 14, 0x02),\n        (22, 14, 0x03),\n        (1, 15, 0x02),\n        (22, 15, 0x03),\n        (1, 16, 0x02),\n        (22, 16, 0x03),\n        (1, 17, 0x02),\n        (22, 17, 0x03),\n        (1, 18, 0x02),\n        (22, 18, 0x03),\n    ],\n    // 232\n    [\n        (2, 8, 0x02),\n        (9, 8, 0x02),\n        (23, 8, 0x02),\n        (40, 8, 0x03),\n        (2, 11, 0x02),\n        (9, 11, 0x02),\n        (23, 11, 0x02),\n        (40, 11, 0x03),\n        (2, 12, 0x02),\n        (9, 12, 0x02),\n        (23, 12, 0x02),\n        (40, 12, 0x03),\n        (2, 14, 0x02),\n        (9, 14, 0x02),\n        (23, 14, 0x02),\n        (40, 14, 0x03),\n    ],\n    // 233\n    [\n        (3, 8, 0x02),\n        (6, 8, 0x02),\n        (10, 8, 0x02),\n        (15, 8, 0x02),\n        (24, 8, 0x02),\n        (31, 8, 0x02),\n        (41, 8, 0x02),\n        (56, 8, 0x03),\n        (3, 11, 0x02),\n        (6, 11, 0x02),\n        (10, 11, 0x02),\n        (15, 11, 0x02),\n        (24, 11, 0x02),\n        (31, 11, 0x02),\n        (41, 11, 0x02),\n        (56, 11, 0x03),\n    ],\n    // 234\n    [\n        (3, 12, 0x02),\n        (6, 12, 0x02),\n        (10, 12, 0x02),\n        (15, 12, 0x02),\n        (24, 12, 0x02),\n        (31, 12, 0x02),\n        (41, 12, 0x02),\n        (56, 12, 0x03),\n        (3, 14, 0x02),\n        (6, 14, 0x02),\n        (10, 14, 0x02),\n        (15, 14, 0x02),\n        (24, 14, 0x02),\n        (31, 14, 0x02),\n        (41, 14, 0x02),\n        (56, 14, 0x03),\n    ],\n    // 235\n    [\n        (2, 15, 0x02),\n        (9, 15, 0x02),\n        (23, 15, 0x02),\n        (40, 15, 0x03),\n        (2, 16, 0x02),\n        (9, 16, 0x02),\n        (23, 16, 0x02),\n        (40, 16, 0x03),\n        (2, 17, 0x02),\n        (9, 17, 0x02),\n        (23, 17, 0x02),\n        (40, 17, 0x03),\n        (2, 18, 0x02),\n        (9, 18, 0x02),\n        (23, 18, 0x02),\n        (40, 18, 0x03),\n    ],\n    // 236\n    [\n        (3, 15, 0x02),\n        (6, 15, 0x02),\n        (10, 15, 0x02),\n        (15, 15, 0x02),\n        (24, 15, 0x02),\n        (31, 15, 0x02),\n        (41, 15, 0x02),\n        (56, 15, 0x03),\n        (3, 16, 0x02),\n        (6, 16, 0x02),\n        (10, 16, 0x02),\n        (15, 16, 0x02),\n        (24, 16, 0x02),\n        (31, 16, 0x02),\n        (41, 16, 0x02),\n        (56, 16, 0x03),\n    ],\n    // 237\n    [\n        (3, 17, 0x02),\n        (6, 17, 0x02),\n        (10, 17, 0x02),\n        (15, 17, 0x02),\n        (24, 17, 0x02),\n        (31, 17, 0x02),\n        (41, 17, 0x02),\n        (56, 17, 0x03),\n        (3, 18, 0x02),\n        (6, 18, 0x02),\n        (10, 18, 0x02),\n        (15, 18, 0x02),\n        (24, 18, 0x02),\n        (31, 18, 0x02),\n        (41, 18, 0x02),\n        (56, 18, 0x03),\n    ],\n    // 238\n    [\n        (0, 19, 0x02),\n        (0, 20, 0x02),\n        (0, 21, 0x02),\n        (0, 23, 0x02),\n        (0, 24, 0x02),\n        (0, 25, 0x02),\n        (0, 26, 0x02),\n        (0, 27, 0x02),\n        (0, 28, 0x02),\n        (0, 29, 0x02),\n        (0, 30, 0x02),\n        (0, 31, 0x02),\n        (0, 127, 0x02),\n        (0, 220, 0x02),\n        (0, 249, 0x02),\n        (253, 0, 0x00),\n    ],\n    // 239\n    [\n        (1, 19, 0x02),\n        (22, 19, 0x03),\n        (1, 20, 0x02),\n        (22, 20, 0x03),\n        (1, 21, 0x02),\n        (22, 21, 0x03),\n        (1, 23, 0x02),\n        (22, 23, 0x03),\n        (1, 24, 0x02),\n        (22, 24, 0x03),\n        (1, 25, 0x02),\n        (22, 25, 0x03),\n        (1, 26, 0x02),\n        (22, 26, 0x03),\n        (1, 27, 0x02),\n        (22, 27, 0x03),\n    ],\n    // 240\n    [\n        (2, 19, 0x02),\n        (9, 19, 0x02),\n        (23, 19, 0x02),\n        (40, 19, 0x03),\n        (2, 20, 0x02),\n        (9, 20, 0x02),\n        (23, 20, 0x02),\n        (40, 20, 0x03),\n        (2, 21, 0x02),\n        (9, 21, 0x02),\n        (23, 21, 0x02),\n        (40, 21, 0x03),\n        (2, 23, 0x02),\n        (9, 23, 0x02),\n        (23, 23, 0x02),\n        (40, 23, 0x03),\n    ],\n    // 241\n    [\n        (3, 19, 0x02),\n        (6, 19, 0x02),\n        (10, 19, 0x02),\n        (15, 19, 0x02),\n        (24, 19, 0x02),\n        (31, 19, 0x02),\n        (41, 19, 0x02),\n        (56, 19, 0x03),\n        (3, 20, 0x02),\n        (6, 20, 0x02),\n        (10, 20, 0x02),\n        (15, 20, 0x02),\n        (24, 20, 0x02),\n        (31, 20, 0x02),\n        (41, 20, 0x02),\n        (56, 20, 0x03),\n    ],\n    // 242\n    [\n        (3, 21, 0x02),\n        (6, 21, 0x02),\n        (10, 21, 0x02),\n        (15, 21, 0x02),\n        (24, 21, 0x02),\n        (31, 21, 0x02),\n        (41, 21, 0x02),\n        (56, 21, 0x03),\n        (3, 23, 0x02),\n        (6, 23, 0x02),\n        (10, 23, 0x02),\n        (15, 23, 0x02),\n        (24, 23, 0x02),\n        (31, 23, 0x02),\n        (41, 23, 0x02),\n        (56, 23, 0x03),\n    ],\n    // 243\n    [\n        (2, 24, 0x02),\n        (9, 24, 0x02),\n        (23, 24, 0x02),\n        (40, 24, 0x03),\n        (2, 25, 0x02),\n        (9, 25, 0x02),\n        (23, 25, 0x02),\n        (40, 25, 0x03),\n        (2, 26, 0x02),\n        (9, 26, 0x02),\n        (23, 26, 0x02),\n        (40, 26, 0x03),\n        (2, 27, 0x02),\n        (9, 27, 0x02),\n        (23, 27, 0x02),\n        (40, 27, 0x03),\n    ],\n    // 244\n    [\n        (3, 24, 0x02),\n        (6, 24, 0x02),\n        (10, 24, 0x02),\n        (15, 24, 0x02),\n        (24, 24, 0x02),\n        (31, 24, 0x02),\n        (41, 24, 0x02),\n        (56, 24, 0x03),\n        (3, 25, 0x02),\n        (6, 25, 0x02),\n        (10, 25, 0x02),\n        (15, 25, 0x02),\n        (24, 25, 0x02),\n        (31, 25, 0x02),\n        (41, 25, 0x02),\n        (56, 25, 0x03),\n    ],\n    // 245\n    [\n        (3, 26, 0x02),\n        (6, 26, 0x02),\n        (10, 26, 0x02),\n        (15, 26, 0x02),\n        (24, 26, 0x02),\n        (31, 26, 0x02),\n        (41, 26, 0x02),\n        (56, 26, 0x03),\n        (3, 27, 0x02),\n        (6, 27, 0x02),\n        (10, 27, 0x02),\n        (15, 27, 0x02),\n        (24, 27, 0x02),\n        (31, 27, 0x02),\n        (41, 27, 0x02),\n        (56, 27, 0x03),\n    ],\n    // 246\n    [\n        (1, 28, 0x02),\n        (22, 28, 0x03),\n        (1, 29, 0x02),\n        (22, 29, 0x03),\n        (1, 30, 0x02),\n        (22, 30, 0x03),\n        (1, 31, 0x02),\n        (22, 31, 0x03),\n        (1, 127, 0x02),\n        (22, 127, 0x03),\n        (1, 220, 0x02),\n        (22, 220, 0x03),\n        (1, 249, 0x02),\n        (22, 249, 0x03),\n        (254, 0, 0x00),\n        (255, 0, 0x00),\n    ],\n    // 247\n    [\n        (2, 28, 0x02),\n        (9, 28, 0x02),\n        (23, 28, 0x02),\n        (40, 28, 0x03),\n        (2, 29, 0x02),\n        (9, 29, 0x02),\n        (23, 29, 0x02),\n        (40, 29, 0x03),\n        (2, 30, 0x02),\n        (9, 30, 0x02),\n        (23, 30, 0x02),\n        (40, 30, 0x03),\n        (2, 31, 0x02),\n        (9, 31, 0x02),\n        (23, 31, 0x02),\n        (40, 31, 0x03),\n    ],\n    // 248\n    [\n        (3, 28, 0x02),\n        (6, 28, 0x02),\n        (10, 28, 0x02),\n        (15, 28, 0x02),\n        (24, 28, 0x02),\n        (31, 28, 0x02),\n        (41, 28, 0x02),\n        (56, 28, 0x03),\n        (3, 29, 0x02),\n        (6, 29, 0x02),\n        (10, 29, 0x02),\n        (15, 29, 0x02),\n        (24, 29, 0x02),\n        (31, 29, 0x02),\n        (41, 29, 0x02),\n        (56, 29, 0x03),\n    ],\n    // 249\n    [\n        (3, 30, 0x02),\n        (6, 30, 0x02),\n        (10, 30, 0x02),\n        (15, 30, 0x02),\n        (24, 30, 0x02),\n        (31, 30, 0x02),\n        (41, 30, 0x02),\n        (56, 30, 0x03),\n        (3, 31, 0x02),\n        (6, 31, 0x02),\n        (10, 31, 0x02),\n        (15, 31, 0x02),\n        (24, 31, 0x02),\n        (31, 31, 0x02),\n        (41, 31, 0x02),\n        (56, 31, 0x03),\n    ],\n    // 250\n    [\n        (2, 127, 0x02),\n        (9, 127, 0x02),\n        (23, 127, 0x02),\n        (40, 127, 0x03),\n        (2, 220, 0x02),\n        (9, 220, 0x02),\n        (23, 220, 0x02),\n        (40, 220, 0x03),\n        (2, 249, 0x02),\n        (9, 249, 0x02),\n        (23, 249, 0x02),\n        (40, 249, 0x03),\n        (0, 10, 0x02),\n        (0, 13, 0x02),\n        (0, 22, 0x02),\n        (0, 0, 0x04),\n    ],\n    // 251\n    [\n        (3, 127, 0x02),\n        (6, 127, 0x02),\n        (10, 127, 0x02),\n        (15, 127, 0x02),\n        (24, 127, 0x02),\n        (31, 127, 0x02),\n        (41, 127, 0x02),\n        (56, 127, 0x03),\n        (3, 220, 0x02),\n        (6, 220, 0x02),\n        (10, 220, 0x02),\n        (15, 220, 0x02),\n        (24, 220, 0x02),\n        (31, 220, 0x02),\n        (41, 220, 0x02),\n        (56, 220, 0x03),\n    ],\n    // 252\n    [\n        (3, 249, 0x02),\n        (6, 249, 0x02),\n        (10, 249, 0x02),\n        (15, 249, 0x02),\n        (24, 249, 0x02),\n        (31, 249, 0x02),\n        (41, 249, 0x02),\n        (56, 249, 0x03),\n        (1, 10, 0x02),\n        (22, 10, 0x03),\n        (1, 13, 0x02),\n        (22, 13, 0x03),\n        (1, 22, 0x02),\n        (22, 22, 0x03),\n        (0, 0, 0x04),\n        (0, 0, 0x05),\n    ],\n    // 253\n    [\n        (2, 10, 0x02),\n        (9, 10, 0x02),\n        (23, 10, 0x02),\n        (40, 10, 0x03),\n        (2, 13, 0x02),\n        (9, 13, 0x02),\n        (23, 13, 0x02),\n        (40, 13, 0x03),\n        (2, 22, 0x02),\n        (9, 22, 0x02),\n        (23, 22, 0x02),\n        (40, 22, 0x03),\n        (0, 0, 0x04),\n        (0, 0, 0x04),\n        (0, 0, 0x04),\n        (0, 0, 0x05),\n    ],\n    // 254\n    [\n        (3, 10, 0x02),\n        (6, 10, 0x02),\n        (10, 10, 0x02),\n        (15, 10, 0x02),\n        (24, 10, 0x02),\n        (31, 10, 0x02),\n        (41, 10, 0x02),\n        (56, 10, 0x03),\n        (3, 13, 0x02),\n        (6, 13, 0x02),\n        (10, 13, 0x02),\n        (15, 13, 0x02),\n        (24, 13, 0x02),\n        (31, 13, 0x02),\n        (41, 13, 0x02),\n        (56, 13, 0x03),\n    ],\n    // 255\n    [\n        (3, 22, 0x02),\n        (6, 22, 0x02),\n        (10, 22, 0x02),\n        (15, 22, 0x02),\n        (24, 22, 0x02),\n        (31, 22, 0x02),\n        (41, 22, 0x02),\n        (56, 22, 0x03),\n        (0, 0, 0x04),\n        (0, 0, 0x04),\n        (0, 0, 0x04),\n        (0, 0, 0x04),\n        (0, 0, 0x04),\n        (0, 0, 0x04),\n        (0, 0, 0x04),\n        (0, 0, 0x05),\n    ],\n];\n"
  },
  {
    "path": "octets/src/lib.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n/// Zero-copy abstraction for parsing and constructing network packets.\nuse std::mem;\nuse std::ptr;\n\n/// Maximum value that can be encoded via varint.\npub const MAX_VAR_INT: u64 = 4_611_686_018_427_387_903;\n\n/// A specialized [`Result`] type for [`OctetsMut`] operations.\n///\n/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html\n/// [`OctetsMut`]: struct.OctetsMut.html\npub type Result<T> = std::result::Result<T, BufferTooShortError>;\n\n/// An error indicating that the provided [`OctetsMut`] is not big enough.\n///\n/// [`OctetsMut`]: struct.OctetsMut.html\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct BufferTooShortError;\n\nimpl std::fmt::Display for BufferTooShortError {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"BufferTooShortError\")\n    }\n}\n\nimpl std::error::Error for BufferTooShortError {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        None\n    }\n}\n\n/// Helper macro that asserts at compile time. It requires that\n/// `cond` is a const expression.\nmacro_rules! static_assert {\n    ($cond:expr) => {{\n        const _: () = assert!($cond);\n    }};\n}\n\nmacro_rules! peek_u {\n    ($b:expr, $ty:ty, $len:expr) => {{\n        let len = $len;\n        let src = &$b.buf[$b.off..];\n\n        if src.len() < len {\n            return Err(BufferTooShortError);\n        }\n\n        static_assert!($len <= mem::size_of::<$ty>());\n        let mut out: $ty = 0;\n        unsafe {\n            let dst = &mut out as *mut $ty as *mut u8;\n            let off = (mem::size_of::<$ty>() - len) as isize;\n\n            ptr::copy_nonoverlapping(src.as_ptr(), dst.offset(off), len);\n        };\n\n        Ok(<$ty>::from_be(out))\n    }};\n}\n\nmacro_rules! get_u {\n    ($b:expr, $ty:ty, $len:expr) => {{\n        let out = peek_u!($b, $ty, $len);\n\n        $b.off += $len;\n\n        out\n    }};\n}\n\nmacro_rules! put_u {\n    ($b:expr, $ty:ty, $v:expr, $len:expr) => {{\n        let len = $len;\n\n        if $b.buf.len() < $b.off + len {\n            return Err(BufferTooShortError);\n        }\n\n        let v = $v;\n\n        let dst = &mut $b.buf[$b.off..($b.off + len)];\n\n        static_assert!($len <= mem::size_of::<$ty>());\n        unsafe {\n            let src = &<$ty>::to_be(v) as *const $ty as *const u8;\n            let off = (mem::size_of::<$ty>() - len) as isize;\n\n            ptr::copy_nonoverlapping(src.offset(off), dst.as_mut_ptr(), len);\n        }\n\n        $b.off += $len;\n\n        Ok(dst)\n    }};\n}\n\n/// A zero-copy immutable byte buffer.\n///\n/// `Octets` wraps an in-memory buffer of bytes and provides utility functions\n/// for manipulating it. The underlying buffer is provided by the user and is\n/// not copied when creating an `Octets`. Operations are panic-free and will\n/// avoid indexing the buffer past its end.\n///\n/// Additionally, an offset (initially set to the start of the buffer) is\n/// incremented as bytes are read from / written to the buffer, to allow for\n/// sequential operations.\n#[derive(Debug, PartialEq, Eq)]\npub struct Octets<'a> {\n    buf: &'a [u8],\n    off: usize,\n}\n\nimpl<'a> Octets<'a> {\n    /// Creates an `Octets` from the given slice, without copying.\n    ///\n    /// Since the `Octets` is immutable, the input slice needs to be\n    /// immutable.\n    pub fn with_slice(buf: &'a [u8]) -> Self {\n        Octets { buf, off: 0 }\n    }\n\n    /// Reads an unsigned 8-bit integer from the current offset and advances\n    /// the buffer.\n    pub fn get_u8(&mut self) -> Result<u8> {\n        get_u!(self, u8, 1)\n    }\n\n    /// Reads an unsigned 8-bit integer from the current offset without\n    /// advancing the buffer.\n    pub fn peek_u8(&mut self) -> Result<u8> {\n        peek_u!(self, u8, 1)\n    }\n\n    /// Reads an unsigned 16-bit integer in network byte-order from the current\n    /// offset and advances the buffer.\n    pub fn get_u16(&mut self) -> Result<u16> {\n        get_u!(self, u16, 2)\n    }\n\n    /// Reads an unsigned 24-bit integer in network byte-order from the current\n    /// offset and advances the buffer.\n    pub fn get_u24(&mut self) -> Result<u32> {\n        get_u!(self, u32, 3)\n    }\n\n    /// Reads an unsigned 32-bit integer in network byte-order from the current\n    /// offset and advances the buffer.\n    pub fn get_u32(&mut self) -> Result<u32> {\n        get_u!(self, u32, 4)\n    }\n\n    /// Reads an unsigned 64-bit integer in network byte-order from the current\n    /// offset and advances the buffer.\n    pub fn get_u64(&mut self) -> Result<u64> {\n        get_u!(self, u64, 8)\n    }\n\n    /// Reads an unsigned variable-length integer in network byte-order from\n    /// the current offset and advances the buffer.\n    pub fn get_varint(&mut self) -> Result<u64> {\n        let first = self.peek_u8()?;\n\n        let len = varint_parse_len(first);\n\n        if len > self.cap() {\n            return Err(BufferTooShortError);\n        }\n\n        let out = match len {\n            1 => u64::from(self.get_u8()?),\n\n            2 => u64::from(self.get_u16()? & 0x3fff),\n\n            4 => u64::from(self.get_u32()? & 0x3fffffff),\n\n            8 => self.get_u64()? & 0x3fffffffffffffff,\n\n            _ => unreachable!(),\n        };\n\n        Ok(out)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer.\n    pub fn get_bytes(&mut self, len: usize) -> Result<Octets<'a>> {\n        if self.cap() < len {\n            return Err(BufferTooShortError);\n        }\n\n        let out = Octets {\n            buf: &self.buf[self.off..self.off + len],\n            off: 0,\n        };\n\n        self.off += len;\n\n        Ok(out)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer, where `len` is an unsigned 8-bit integer prefix.\n    pub fn get_bytes_with_u8_length(&mut self) -> Result<Octets<'a>> {\n        let len = self.get_u8()?;\n        self.get_bytes(len as usize)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer, where `len` is an unsigned 16-bit integer prefix in network\n    /// byte-order.\n    pub fn get_bytes_with_u16_length(&mut self) -> Result<Octets<'a>> {\n        let len = self.get_u16()?;\n        self.get_bytes(len as usize)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer, where `len` is an unsigned variable-length integer prefix\n    /// in network byte-order.\n    pub fn get_bytes_with_varint_length(&mut self) -> Result<Octets<'a>> {\n        let len = self.get_varint()?;\n        self.get_bytes(len as usize)\n    }\n\n    /// Decodes a Huffman-encoded value from the current offset.\n    ///\n    /// The Huffman code implemented is the one defined for HPACK (RFC7541).\n    #[cfg(feature = \"huffman_hpack\")]\n    pub fn get_huffman_decoded(&mut self) -> Result<Vec<u8>> {\n        use self::huffman_table::DECODE_TABLE;\n\n        const FLAG_END: u8 = 1;\n        const FLAG_SYM: u8 = 2;\n        const FLAG_ERR: u8 = 4;\n\n        // Max compression ratio is >= 0.5.\n        let mut out = Vec::with_capacity(self.cap() << 1);\n\n        let mut state = 0;\n        let mut eos = false;\n\n        while let Ok(byte) = self.get_u8() {\n            for data in [byte >> 4, byte & 0xf] {\n                let (next, sym, flags) = DECODE_TABLE[state][(data) as usize];\n\n                if flags & FLAG_ERR == FLAG_ERR {\n                    // Data followed the \"end\" marker.\n                    return Err(BufferTooShortError);\n                } else if flags & FLAG_SYM == FLAG_SYM {\n                    out.push(sym);\n                }\n\n                state = next;\n\n                // `eos` only correct when handling the byte & 0xf case; ignored\n                // and overwritten in the byte >> 4 case.\n                eos = flags & FLAG_END == FLAG_END;\n            }\n        }\n\n        if state != 0 && !eos {\n            return Err(BufferTooShortError);\n        }\n\n        Ok(out)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and without\n    /// advancing the buffer.\n    pub fn peek_bytes(&self, len: usize) -> Result<Octets<'a>> {\n        if self.cap() < len {\n            return Err(BufferTooShortError);\n        }\n\n        let out = Octets {\n            buf: &self.buf[self.off..self.off + len],\n            off: 0,\n        };\n\n        Ok(out)\n    }\n\n    /// Rewinds the buffer offset by `len` elements.\n    pub fn rewind(&mut self, len: usize) -> Result<()> {\n        if self.off() < len {\n            return Err(BufferTooShortError);\n        }\n\n        self.off -= len;\n\n        Ok(())\n    }\n\n    /// Returns a slice of `len` elements from the current offset.\n    pub fn slice(&self, len: usize) -> Result<&'a [u8]> {\n        if len > self.cap() {\n            return Err(BufferTooShortError);\n        }\n\n        Ok(&self.buf[self.off..self.off + len])\n    }\n\n    /// Returns a slice of `len` elements from the end of the buffer.\n    pub fn slice_last(&self, len: usize) -> Result<&'a [u8]> {\n        if len > self.cap() {\n            return Err(BufferTooShortError);\n        }\n\n        let end = self.buf.len();\n        Ok(&self.buf[end - len..end])\n    }\n\n    /// Advances the buffer's offset.\n    pub fn skip(&mut self, skip: usize) -> Result<()> {\n        if skip > self.cap() {\n            return Err(BufferTooShortError);\n        }\n\n        self.off += skip;\n\n        Ok(())\n    }\n\n    /// Returns the remaining capacity in the buffer.\n    pub fn cap(&self) -> usize {\n        self.buf.len() - self.off\n    }\n\n    /// Returns the total length of the buffer.\n    pub fn len(&self) -> usize {\n        self.buf.len()\n    }\n\n    /// Returns `true` if the buffer is empty.\n    pub fn is_empty(&self) -> bool {\n        self.buf.len() == 0\n    }\n\n    /// Returns the current offset of the buffer.\n    pub fn off(&self) -> usize {\n        self.off\n    }\n\n    /// Returns a reference to the internal buffer.\n    pub fn buf(&self) -> &'a [u8] {\n        self.buf\n    }\n\n    /// Copies the buffer from the current offset into a new `Vec<u8>`.\n    pub fn to_vec(&self) -> Vec<u8> {\n        self.as_ref().to_vec()\n    }\n}\n\nimpl AsRef<[u8]> for Octets<'_> {\n    fn as_ref(&self) -> &[u8] {\n        &self.buf[self.off..]\n    }\n}\n\n/// A zero-copy mutable byte buffer.\n///\n/// Like `Octets` but mutable.\n#[derive(Debug, PartialEq, Eq)]\npub struct OctetsMut<'a> {\n    buf: &'a mut [u8],\n    off: usize,\n}\n\nimpl<'a> OctetsMut<'a> {\n    /// Creates an `OctetsMut` from the given slice, without copying.\n    ///\n    /// Since there's no copy, the input slice needs to be mutable to allow\n    /// modifications.\n    pub fn with_slice(buf: &'a mut [u8]) -> Self {\n        OctetsMut { buf, off: 0 }\n    }\n\n    /// Reads an unsigned 8-bit integer from the current offset and advances\n    /// the buffer.\n    pub fn get_u8(&mut self) -> Result<u8> {\n        get_u!(self, u8, 1)\n    }\n\n    /// Reads an unsigned 8-bit integer from the current offset without\n    /// advancing the buffer.\n    pub fn peek_u8(&mut self) -> Result<u8> {\n        peek_u!(self, u8, 1)\n    }\n\n    /// Writes an unsigned 8-bit integer at the current offset and advances\n    /// the buffer.\n    pub fn put_u8(&mut self, v: u8) -> Result<&mut [u8]> {\n        put_u!(self, u8, v, 1)\n    }\n\n    /// Reads an unsigned 16-bit integer in network byte-order from the current\n    /// offset and advances the buffer.\n    pub fn get_u16(&mut self) -> Result<u16> {\n        get_u!(self, u16, 2)\n    }\n\n    /// Writes an unsigned 16-bit integer in network byte-order at the current\n    /// offset and advances the buffer.\n    pub fn put_u16(&mut self, v: u16) -> Result<&mut [u8]> {\n        put_u!(self, u16, v, 2)\n    }\n\n    /// Reads an unsigned 24-bit integer in network byte-order from the current\n    /// offset and advances the buffer.\n    pub fn get_u24(&mut self) -> Result<u32> {\n        get_u!(self, u32, 3)\n    }\n\n    /// Writes an unsigned 24-bit integer in network byte-order at the current\n    /// offset and advances the buffer.\n    pub fn put_u24(&mut self, v: u32) -> Result<&mut [u8]> {\n        put_u!(self, u32, v, 3)\n    }\n\n    /// Reads an unsigned 32-bit integer in network byte-order from the current\n    /// offset and advances the buffer.\n    pub fn get_u32(&mut self) -> Result<u32> {\n        get_u!(self, u32, 4)\n    }\n\n    /// Writes an unsigned 32-bit integer in network byte-order at the current\n    /// offset and advances the buffer.\n    pub fn put_u32(&mut self, v: u32) -> Result<&mut [u8]> {\n        put_u!(self, u32, v, 4)\n    }\n\n    /// Reads an unsigned 64-bit integer in network byte-order from the current\n    /// offset and advances the buffer.\n    pub fn get_u64(&mut self) -> Result<u64> {\n        get_u!(self, u64, 8)\n    }\n\n    /// Writes an unsigned 64-bit integer in network byte-order at the current\n    /// offset and advances the buffer.\n    pub fn put_u64(&mut self, v: u64) -> Result<&mut [u8]> {\n        put_u!(self, u64, v, 8)\n    }\n\n    /// Reads an unsigned variable-length integer in network byte-order from\n    /// the current offset and advances the buffer.\n    pub fn get_varint(&mut self) -> Result<u64> {\n        let first = self.peek_u8()?;\n\n        let len = varint_parse_len(first);\n\n        if len > self.cap() {\n            return Err(BufferTooShortError);\n        }\n\n        let out = match len {\n            1 => u64::from(self.get_u8()?),\n\n            2 => u64::from(self.get_u16()? & 0x3fff),\n\n            4 => u64::from(self.get_u32()? & 0x3fffffff),\n\n            8 => self.get_u64()? & 0x3fffffffffffffff,\n\n            _ => unreachable!(),\n        };\n\n        Ok(out)\n    }\n\n    /// Writes an unsigned variable-length integer in network byte-order at the\n    /// current offset and advances the buffer.\n    pub fn put_varint(&mut self, v: u64) -> Result<&mut [u8]> {\n        self.put_varint_with_len(v, varint_len(v))\n    }\n\n    /// Writes an unsigned variable-length integer of the specified length, in\n    /// network byte-order at the current offset and advances the buffer.\n    pub fn put_varint_with_len(\n        &mut self, v: u64, len: usize,\n    ) -> Result<&mut [u8]> {\n        if self.cap() < len {\n            return Err(BufferTooShortError);\n        }\n\n        let buf = match len {\n            1 => self.put_u8(v as u8)?,\n\n            2 => {\n                let buf = self.put_u16(v as u16)?;\n                buf[0] |= 0x40;\n                buf\n            },\n\n            4 => {\n                let buf = self.put_u32(v as u32)?;\n                buf[0] |= 0x80;\n                buf\n            },\n\n            8 => {\n                let buf = self.put_u64(v)?;\n                buf[0] |= 0xc0;\n                buf\n            },\n\n            _ => panic!(\"value is too large for varint\"),\n        };\n\n        Ok(buf)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer.\n    pub fn get_bytes(&mut self, len: usize) -> Result<Octets<'_>> {\n        if self.cap() < len {\n            return Err(BufferTooShortError);\n        }\n\n        let out = Octets {\n            buf: &self.buf[self.off..self.off + len],\n            off: 0,\n        };\n\n        self.off += len;\n\n        Ok(out)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer.\n    pub fn get_bytes_mut(&mut self, len: usize) -> Result<OctetsMut<'_>> {\n        if self.cap() < len {\n            return Err(BufferTooShortError);\n        }\n\n        let out = OctetsMut {\n            buf: &mut self.buf[self.off..self.off + len],\n            off: 0,\n        };\n\n        self.off += len;\n\n        Ok(out)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer, where `len` is an unsigned 8-bit integer prefix.\n    pub fn get_bytes_with_u8_length(&mut self) -> Result<Octets<'_>> {\n        let len = self.get_u8()?;\n        self.get_bytes(len as usize)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer, where `len` is an unsigned 16-bit integer prefix in network\n    /// byte-order.\n    pub fn get_bytes_with_u16_length(&mut self) -> Result<Octets<'_>> {\n        let len = self.get_u16()?;\n        self.get_bytes(len as usize)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and advances\n    /// the buffer, where `len` is an unsigned variable-length integer prefix\n    /// in network byte-order.\n    pub fn get_bytes_with_varint_length(&mut self) -> Result<Octets<'_>> {\n        let len = self.get_varint()?;\n        self.get_bytes(len as usize)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and without\n    /// advancing the buffer.\n    pub fn peek_bytes(&mut self, len: usize) -> Result<Octets<'_>> {\n        if self.cap() < len {\n            return Err(BufferTooShortError);\n        }\n\n        let out = Octets {\n            buf: &self.buf[self.off..self.off + len],\n            off: 0,\n        };\n\n        Ok(out)\n    }\n\n    /// Reads `len` bytes from the current offset without copying and without\n    /// advancing the buffer.\n    pub fn peek_bytes_mut(&mut self, len: usize) -> Result<OctetsMut<'_>> {\n        if self.cap() < len {\n            return Err(BufferTooShortError);\n        }\n\n        let out = OctetsMut {\n            buf: &mut self.buf[self.off..self.off + len],\n            off: 0,\n        };\n\n        Ok(out)\n    }\n\n    /// Writes `v` to the current offset.\n    pub fn put_bytes(&mut self, v: &[u8]) -> Result<()> {\n        let len = v.len();\n\n        if self.cap() < len {\n            return Err(BufferTooShortError);\n        }\n\n        if len == 0 {\n            return Ok(());\n        }\n\n        self.as_mut()[..len].copy_from_slice(v);\n\n        self.off += len;\n\n        Ok(())\n    }\n\n    /// Writes `v` to the current offset after Huffman-encoding it.\n    ///\n    /// The Huffman code implemented is the one defined for HPACK (RFC7541).\n    #[cfg(feature = \"huffman_hpack\")]\n    pub fn put_huffman_encoded<const LOWER_CASE: bool>(\n        &mut self, v: &[u8],\n    ) -> Result<()> {\n        use self::huffman_table::ENCODE_TABLE;\n\n        let mut bits: u64 = 0;\n        let mut pending = 0;\n\n        for &b in v {\n            let b = if LOWER_CASE {\n                b.to_ascii_lowercase()\n            } else {\n                b\n            };\n            let (nbits, code) = ENCODE_TABLE[b as usize];\n\n            pending += nbits;\n\n            if pending < 64 {\n                // Have room for the new token\n                bits |= code << (64 - pending);\n                continue;\n            }\n\n            pending -= 64;\n            // Take only the bits that fit\n            bits |= code >> pending;\n            self.put_u64(bits)?;\n\n            bits = if pending == 0 {\n                0\n            } else {\n                code << (64 - pending)\n            };\n        }\n\n        if pending == 0 {\n            return Ok(());\n        }\n\n        bits |= u64::MAX >> pending;\n        // TODO: replace with `next_multiple_of(8)` when stable\n        pending = (pending + 7) & !7; // Round up to a byte\n        bits >>= 64 - pending;\n\n        if pending >= 32 {\n            pending -= 32;\n            self.put_u32((bits >> pending) as u32)?;\n        }\n\n        while pending > 0 {\n            pending -= 8;\n            self.put_u8((bits >> pending) as u8)?;\n        }\n\n        Ok(())\n    }\n\n    /// Rewinds the buffer offset by `len` elements.\n    pub fn rewind(&mut self, len: usize) -> Result<()> {\n        if self.off() < len {\n            return Err(BufferTooShortError);\n        }\n\n        self.off -= len;\n\n        Ok(())\n    }\n\n    /// Splits the buffer in two at the given absolute offset.\n    pub fn split_at(\n        &mut self, off: usize,\n    ) -> Result<(OctetsMut<'_>, OctetsMut<'_>)> {\n        if self.len() < off {\n            return Err(BufferTooShortError);\n        }\n\n        let (left, right) = self.buf.split_at_mut(off);\n\n        let first = OctetsMut { buf: left, off: 0 };\n\n        let last = OctetsMut { buf: right, off: 0 };\n\n        Ok((first, last))\n    }\n\n    /// Returns a slice of `len` elements from the current offset.\n    pub fn slice(&'a mut self, len: usize) -> Result<&'a mut [u8]> {\n        if len > self.cap() {\n            return Err(BufferTooShortError);\n        }\n\n        Ok(&mut self.buf[self.off..self.off + len])\n    }\n\n    /// Returns a slice of `len` elements from the end of the buffer.\n    pub fn slice_last(&'a mut self, len: usize) -> Result<&'a mut [u8]> {\n        if len > self.cap() {\n            return Err(BufferTooShortError);\n        }\n\n        let end = self.buf.len();\n        Ok(&mut self.buf[end - len..end])\n    }\n\n    /// Advances the buffer's offset.\n    pub fn skip(&mut self, skip: usize) -> Result<()> {\n        if skip > self.cap() {\n            return Err(BufferTooShortError);\n        }\n\n        self.off += skip;\n\n        Ok(())\n    }\n\n    /// Returns the remaining capacity in the buffer.\n    pub fn cap(&self) -> usize {\n        self.buf.len() - self.off\n    }\n\n    /// Returns the total length of the buffer.\n    pub fn len(&self) -> usize {\n        self.buf.len()\n    }\n\n    /// Returns `true` if the buffer is empty.\n    pub fn is_empty(&self) -> bool {\n        self.buf.len() == 0\n    }\n\n    /// Returns the current offset of the buffer.\n    pub fn off(&self) -> usize {\n        self.off\n    }\n\n    /// Returns a reference to the internal buffer.\n    pub fn buf(&self) -> &[u8] {\n        self.buf\n    }\n\n    /// Copies the buffer from the current offset into a new `Vec<u8>`.\n    pub fn to_vec(&self) -> Vec<u8> {\n        self.as_ref().to_vec()\n    }\n}\n\nimpl AsRef<[u8]> for OctetsMut<'_> {\n    fn as_ref(&self) -> &[u8] {\n        &self.buf[self.off..]\n    }\n}\n\nimpl AsMut<[u8]> for OctetsMut<'_> {\n    fn as_mut(&mut self) -> &mut [u8] {\n        &mut self.buf[self.off..]\n    }\n}\n\n/// Returns how many bytes it would take to encode `v` as a variable-length\n/// integer.\npub const fn varint_len(v: u64) -> usize {\n    if v <= 63 {\n        1\n    } else if v <= 16383 {\n        2\n    } else if v <= 1_073_741_823 {\n        4\n    } else if v <= MAX_VAR_INT {\n        8\n    } else {\n        unreachable!()\n    }\n}\n\n/// Returns how long the variable-length integer is, given its first byte.\npub const fn varint_parse_len(first: u8) -> usize {\n    match first >> 6 {\n        0 => 1,\n        1 => 2,\n        2 => 4,\n        3 => 8,\n        _ => unreachable!(),\n    }\n}\n\n/// Returns how long the Huffman encoding of the given buffer will be.\n///\n/// The Huffman code implemented is the one defined for HPACK (RFC7541).\n#[cfg(feature = \"huffman_hpack\")]\npub fn huffman_encoding_len<const LOWER_CASE: bool>(src: &[u8]) -> Result<usize> {\n    use self::huffman_table::ENCODE_TABLE;\n\n    let mut bits: usize = 0;\n\n    for &b in src {\n        let b = if LOWER_CASE {\n            b.to_ascii_lowercase()\n        } else {\n            b\n        };\n\n        let (nbits, _) = ENCODE_TABLE[b as usize];\n        bits += nbits;\n    }\n\n    let mut len = bits / 8;\n\n    if bits & 7 != 0 {\n        len += 1;\n    }\n\n    if len > src.len() {\n        return Err(BufferTooShortError);\n    }\n\n    Ok(len)\n}\n\n/// The functions in this mod test the compile time assertions in the\n/// `put_u` and `peek_u` macros. If you compile this crate with\n/// `--cfg test_invalid_len_compilation_fail`, e.g., by using\n/// `cargo rustc  -- --cfg test_invalid_len_compilation_fail`\n/// You will get two compiler errors\n#[cfg(test_invalid_len_compilation_fail)]\npub mod fails_to_compile {\n    use super::*;\n    pub fn peek_invalid_fails_to_compile(b: &mut Octets) -> Result<u8> {\n        peek_u!(b, u8, 2)\n    }\n\n    pub fn put_invalid_fails_to_compile<'a>(\n        b: &'a mut OctetsMut, v: u8,\n    ) -> Result<&'a mut [u8]> {\n        put_u!(b, u8, v, 2)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn get_u() {\n        let d = [\n            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,\n        ];\n\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.cap(), 18);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.get_u8().unwrap(), 1);\n        assert_eq!(b.cap(), 17);\n        assert_eq!(b.off(), 1);\n\n        assert_eq!(b.get_u16().unwrap(), 0x203);\n        assert_eq!(b.cap(), 15);\n        assert_eq!(b.off(), 3);\n\n        assert_eq!(b.get_u24().unwrap(), 0x40506);\n        assert_eq!(b.cap(), 12);\n        assert_eq!(b.off(), 6);\n\n        assert_eq!(b.get_u32().unwrap(), 0x0708090a);\n        assert_eq!(b.cap(), 8);\n        assert_eq!(b.off(), 10);\n\n        assert_eq!(b.get_u64().unwrap(), 0x0b0c0d0e0f101112);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 18);\n\n        assert!(b.get_u8().is_err());\n        assert!(b.get_u16().is_err());\n        assert!(b.get_u24().is_err());\n        assert!(b.get_u32().is_err());\n        assert!(b.get_u64().is_err());\n    }\n\n    #[test]\n    fn get_u_mut() {\n        let mut d = [\n            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,\n        ];\n\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.cap(), 18);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.get_u8().unwrap(), 1);\n        assert_eq!(b.cap(), 17);\n        assert_eq!(b.off(), 1);\n\n        assert_eq!(b.get_u16().unwrap(), 0x203);\n        assert_eq!(b.cap(), 15);\n        assert_eq!(b.off(), 3);\n\n        assert_eq!(b.get_u24().unwrap(), 0x40506);\n        assert_eq!(b.cap(), 12);\n        assert_eq!(b.off(), 6);\n\n        assert_eq!(b.get_u32().unwrap(), 0x0708090a);\n        assert_eq!(b.cap(), 8);\n        assert_eq!(b.off(), 10);\n\n        assert_eq!(b.get_u64().unwrap(), 0x0b0c0d0e0f101112);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 18);\n\n        assert!(b.get_u8().is_err());\n        assert!(b.get_u16().is_err());\n        assert!(b.get_u24().is_err());\n        assert!(b.get_u32().is_err());\n        assert!(b.get_u64().is_err());\n    }\n\n    #[test]\n    fn peek_u() {\n        let d = [1, 2];\n\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.peek_u8().unwrap(), 1);\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.peek_u8().unwrap(), 1);\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 0);\n\n        b.get_u16().unwrap();\n\n        assert!(b.peek_u8().is_err());\n    }\n\n    #[test]\n    fn peek_u_mut() {\n        let mut d = [1, 2];\n\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.peek_u8().unwrap(), 1);\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.peek_u8().unwrap(), 1);\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 0);\n\n        b.get_u16().unwrap();\n\n        assert!(b.peek_u8().is_err());\n    }\n\n    #[test]\n    fn get_bytes() {\n        let d = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.get_bytes(5).unwrap().as_ref(), [1, 2, 3, 4, 5]);\n        assert_eq!(b.cap(), 5);\n        assert_eq!(b.off(), 5);\n\n        assert_eq!(b.get_bytes(3).unwrap().as_ref(), [6, 7, 8]);\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 8);\n\n        assert!(b.get_bytes(3).is_err());\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 8);\n\n        assert_eq!(b.get_bytes(2).unwrap().as_ref(), [9, 10]);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 10);\n\n        assert!(b.get_bytes(2).is_err());\n    }\n\n    #[test]\n    fn get_bytes_mut() {\n        let mut d = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.get_bytes(5).unwrap().as_ref(), [1, 2, 3, 4, 5]);\n        assert_eq!(b.cap(), 5);\n        assert_eq!(b.off(), 5);\n\n        assert_eq!(b.get_bytes(3).unwrap().as_ref(), [6, 7, 8]);\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 8);\n\n        assert!(b.get_bytes(3).is_err());\n        assert_eq!(b.cap(), 2);\n        assert_eq!(b.off(), 8);\n\n        assert_eq!(b.get_bytes(2).unwrap().as_ref(), [9, 10]);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 10);\n\n        assert!(b.get_bytes(2).is_err());\n    }\n\n    #[test]\n    fn peek_bytes() {\n        let d = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.peek_bytes(5).unwrap().as_ref(), [1, 2, 3, 4, 5]);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.peek_bytes(5).unwrap().as_ref(), [1, 2, 3, 4, 5]);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n\n        b.get_bytes(5).unwrap();\n    }\n\n    #[test]\n    fn peek_bytes_mut() {\n        let mut d = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.peek_bytes(5).unwrap().as_ref(), [1, 2, 3, 4, 5]);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n\n        assert_eq!(b.peek_bytes(5).unwrap().as_ref(), [1, 2, 3, 4, 5]);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n\n        b.get_bytes(5).unwrap();\n    }\n\n    #[test]\n    fn get_varint() {\n        let d = [0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c];\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.get_varint().unwrap(), 151288809941952652);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 8);\n\n        let d = [0x9d, 0x7f, 0x3e, 0x7d];\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.get_varint().unwrap(), 494878333);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 4);\n\n        let d = [0x7b, 0xbd];\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.get_varint().unwrap(), 15293);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 2);\n\n        let d = [0x40, 0x25];\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.get_varint().unwrap(), 37);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 2);\n\n        let d = [0x25];\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.get_varint().unwrap(), 37);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 1);\n    }\n\n    #[test]\n    fn get_varint_mut() {\n        let mut d = [0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.get_varint().unwrap(), 151288809941952652);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 8);\n\n        let mut d = [0x9d, 0x7f, 0x3e, 0x7d];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.get_varint().unwrap(), 494878333);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 4);\n\n        let mut d = [0x7b, 0xbd];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.get_varint().unwrap(), 15293);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 2);\n\n        let mut d = [0x40, 0x25];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.get_varint().unwrap(), 37);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 2);\n\n        let mut d = [0x25];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.get_varint().unwrap(), 37);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 1);\n    }\n\n    #[cfg(feature = \"huffman_hpack\")]\n    #[test]\n    fn invalid_huffman() {\n        // Extra non-zero padding byte at the end.\n        let mut b = Octets::with_slice(\n            b\"\\x00\\x85\\xf2\\xb2\\x4a\\x84\\xff\\x84\\x49\\x50\\x9f\\xff\",\n        );\n        assert!(b.get_huffman_decoded().is_err());\n\n        // Zero padded.\n        let mut b =\n            Octets::with_slice(b\"\\x00\\x85\\xf2\\xb2\\x4a\\x84\\xff\\x83\\x49\\x50\\x90\");\n        assert!(b.get_huffman_decoded().is_err());\n\n        // Non-final EOS symbol.\n        let mut b = Octets::with_slice(\n            b\"\\x00\\x85\\xf2\\xb2\\x4a\\x84\\xff\\x87\\x49\\x51\\xff\\xff\\xff\\xfa\\x7f\",\n        );\n        assert!(b.get_huffman_decoded().is_err());\n    }\n\n    #[test]\n    fn put_varint() {\n        let mut d = [0; 8];\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert!(b.put_varint(151288809941952652).is_ok());\n            assert_eq!(b.cap(), 0);\n            assert_eq!(b.off(), 8);\n        }\n        let exp = [0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c];\n        assert_eq!(&d, &exp);\n\n        let mut d = [0; 4];\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert!(b.put_varint(494878333).is_ok());\n            assert_eq!(b.cap(), 0);\n            assert_eq!(b.off(), 4);\n        }\n        let exp = [0x9d, 0x7f, 0x3e, 0x7d];\n        assert_eq!(&d, &exp);\n\n        let mut d = [0; 2];\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert!(b.put_varint(15293).is_ok());\n            assert_eq!(b.cap(), 0);\n            assert_eq!(b.off(), 2);\n        }\n        let exp = [0x7b, 0xbd];\n        assert_eq!(&d, &exp);\n\n        let mut d = [0; 1];\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert!(b.put_varint(37).is_ok());\n            assert_eq!(b.cap(), 0);\n            assert_eq!(b.off(), 1);\n        }\n        let exp = [0x25];\n        assert_eq!(&d, &exp);\n\n        let mut d = [0; 3];\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert!(b.put_varint(151288809941952652).is_err());\n            assert_eq!(b.cap(), 3);\n            assert_eq!(b.off(), 0);\n        }\n        let exp = [0; 3];\n        assert_eq!(&d, &exp);\n    }\n\n    #[test]\n    #[should_panic]\n    fn varint_too_large() {\n        let mut d = [0; 3];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert!(b.put_varint(u64::MAX).is_err());\n    }\n\n    #[test]\n    fn put_u() {\n        let mut d = [0; 18];\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert_eq!(b.cap(), 18);\n            assert_eq!(b.off(), 0);\n\n            assert!(b.put_u8(1).is_ok());\n            assert_eq!(b.cap(), 17);\n            assert_eq!(b.off(), 1);\n\n            assert!(b.put_u16(0x203).is_ok());\n            assert_eq!(b.cap(), 15);\n            assert_eq!(b.off(), 3);\n\n            assert!(b.put_u24(0x40506).is_ok());\n            assert_eq!(b.cap(), 12);\n            assert_eq!(b.off(), 6);\n\n            assert!(b.put_u32(0x0708090a).is_ok());\n            assert_eq!(b.cap(), 8);\n            assert_eq!(b.off(), 10);\n\n            assert!(b.put_u64(0x0b0c0d0e0f101112).is_ok());\n            assert_eq!(b.cap(), 0);\n            assert_eq!(b.off(), 18);\n\n            assert!(b.put_u8(1).is_err());\n        }\n\n        let exp = [\n            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,\n        ];\n        assert_eq!(&d, &exp);\n    }\n\n    #[test]\n    fn put_bytes() {\n        let mut d = [0; 5];\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert_eq!(b.cap(), 5);\n            assert_eq!(b.off(), 0);\n\n            let p = [0x0a, 0x0b, 0x0c, 0x0d, 0x0e];\n            assert!(b.put_bytes(&p).is_ok());\n            assert_eq!(b.cap(), 0);\n            assert_eq!(b.off(), 5);\n\n            assert!(b.put_u8(1).is_err());\n        }\n\n        let exp = [0xa, 0xb, 0xc, 0xd, 0xe];\n        assert_eq!(&d, &exp);\n    }\n\n    #[test]\n    fn rewind() {\n        let d = [0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c];\n        let mut b = Octets::with_slice(&d);\n        assert_eq!(b.get_varint().unwrap(), 151288809941952652);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 8);\n\n        assert_eq!(b.rewind(4), Ok(()));\n        assert_eq!(b.cap(), 4);\n        assert_eq!(b.off(), 4);\n\n        assert_eq!(b.get_u8().unwrap(), 0xff);\n        assert_eq!(b.cap(), 3);\n        assert_eq!(b.off(), 5);\n\n        assert!(b.rewind(6).is_err());\n\n        assert_eq!(b.rewind(5), Ok(()));\n        assert_eq!(b.cap(), 8);\n        assert_eq!(b.off(), 0);\n\n        assert!(b.rewind(1).is_err());\n    }\n\n    #[test]\n    fn rewind_mut() {\n        let mut d = [0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c];\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.get_varint().unwrap(), 151288809941952652);\n        assert_eq!(b.cap(), 0);\n        assert_eq!(b.off(), 8);\n\n        assert_eq!(b.rewind(4), Ok(()));\n        assert_eq!(b.cap(), 4);\n        assert_eq!(b.off(), 4);\n\n        assert_eq!(b.get_u8().unwrap(), 0xff);\n        assert_eq!(b.cap(), 3);\n        assert_eq!(b.off(), 5);\n\n        assert!(b.rewind(6).is_err());\n\n        assert_eq!(b.rewind(5), Ok(()));\n        assert_eq!(b.cap(), 8);\n        assert_eq!(b.off(), 0);\n\n        assert!(b.rewind(1).is_err());\n    }\n\n    #[test]\n    fn split() {\n        let mut d = b\"helloworld\".to_vec();\n\n        let mut b = OctetsMut::with_slice(&mut d);\n        assert_eq!(b.cap(), 10);\n        assert_eq!(b.off(), 0);\n        assert_eq!(b.as_ref(), b\"helloworld\");\n\n        assert!(b.get_bytes(5).is_ok());\n        assert_eq!(b.cap(), 5);\n        assert_eq!(b.off(), 5);\n        assert_eq!(b.as_ref(), b\"world\");\n\n        let off = b.off();\n\n        let (first, last) = b.split_at(off).unwrap();\n        assert_eq!(first.cap(), 5);\n        assert_eq!(first.off(), 0);\n        assert_eq!(first.as_ref(), b\"hello\");\n\n        assert_eq!(last.cap(), 5);\n        assert_eq!(last.off(), 0);\n        assert_eq!(last.as_ref(), b\"world\");\n    }\n\n    #[test]\n    fn split_at() {\n        let mut d = b\"helloworld\".to_vec();\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let (first, second) = b.split_at(5).unwrap();\n\n            let mut exp1 = b\"hello\".to_vec();\n            assert_eq!(first.as_ref(), &mut exp1[..]);\n\n            let mut exp2 = b\"world\".to_vec();\n            assert_eq!(second.as_ref(), &mut exp2[..]);\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let (first, second) = b.split_at(10).unwrap();\n\n            let mut exp1 = b\"helloworld\".to_vec();\n            assert_eq!(first.as_ref(), &mut exp1[..]);\n\n            let mut exp2 = b\"\".to_vec();\n            assert_eq!(second.as_ref(), &mut exp2[..]);\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let (first, second) = b.split_at(9).unwrap();\n\n            let mut exp1 = b\"helloworl\".to_vec();\n            assert_eq!(first.as_ref(), &mut exp1[..]);\n\n            let mut exp2 = b\"d\".to_vec();\n            assert_eq!(second.as_ref(), &mut exp2[..]);\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert!(b.split_at(11).is_err());\n        }\n    }\n\n    #[test]\n    fn slice() {\n        let d = b\"helloworld\".to_vec();\n\n        {\n            let b = Octets::with_slice(&d);\n            let exp = b\"hello\".to_vec();\n            assert_eq!(b.slice(5), Ok(&exp[..]));\n        }\n\n        {\n            let b = Octets::with_slice(&d);\n            let exp = b\"\".to_vec();\n            assert_eq!(b.slice(0), Ok(&exp[..]));\n        }\n\n        {\n            let mut b = Octets::with_slice(&d);\n            b.get_bytes(5).unwrap();\n\n            let exp = b\"world\".to_vec();\n            assert_eq!(b.slice(5), Ok(&exp[..]));\n        }\n\n        {\n            let b = Octets::with_slice(&d);\n            assert!(b.slice(11).is_err());\n        }\n    }\n\n    #[test]\n    fn slice_mut() {\n        let mut d = b\"helloworld\".to_vec();\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let mut exp = b\"hello\".to_vec();\n            assert_eq!(b.slice(5), Ok(&mut exp[..]));\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let mut exp = b\"\".to_vec();\n            assert_eq!(b.slice(0), Ok(&mut exp[..]));\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            b.get_bytes(5).unwrap();\n\n            let mut exp = b\"world\".to_vec();\n            assert_eq!(b.slice(5), Ok(&mut exp[..]));\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert!(b.slice(11).is_err());\n        }\n    }\n\n    #[test]\n    fn slice_last() {\n        let d = b\"helloworld\".to_vec();\n\n        {\n            let b = Octets::with_slice(&d);\n            let exp = b\"orld\".to_vec();\n            assert_eq!(b.slice_last(4), Ok(&exp[..]));\n        }\n\n        {\n            let mut b = Octets::with_slice(&d);\n            b.get_bytes(5).unwrap();\n            let exp = b\"orld\".to_vec();\n            assert_eq!(b.slice_last(4), Ok(&exp[..]));\n        }\n\n        {\n            let b = Octets::with_slice(&d);\n            let exp = b\"d\".to_vec();\n            assert_eq!(b.slice_last(1), Ok(&exp[..]));\n        }\n\n        {\n            let b = Octets::with_slice(&d);\n            let exp = b\"\".to_vec();\n            assert_eq!(b.slice_last(0), Ok(&exp[..]));\n        }\n\n        {\n            let b = Octets::with_slice(&d);\n            let exp = b\"helloworld\".to_vec();\n            assert_eq!(b.slice_last(10), Ok(&exp[..]));\n        }\n\n        {\n            let b = Octets::with_slice(&d);\n            assert!(b.slice_last(11).is_err());\n        }\n    }\n\n    #[test]\n    fn slice_last_mut() {\n        let mut d = b\"helloworld\".to_vec();\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let mut exp = b\"orld\".to_vec();\n            assert_eq!(b.slice_last(4), Ok(&mut exp[..]));\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            b.get_bytes(5).unwrap();\n            let mut exp = b\"orld\".to_vec();\n            assert_eq!(b.slice_last(4), Ok(&mut exp[..]));\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let mut exp = b\"d\".to_vec();\n            assert_eq!(b.slice_last(1), Ok(&mut exp[..]));\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let mut exp = b\"\".to_vec();\n            assert_eq!(b.slice_last(0), Ok(&mut exp[..]));\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            let mut exp = b\"helloworld\".to_vec();\n            assert_eq!(b.slice_last(10), Ok(&mut exp[..]));\n        }\n\n        {\n            let mut b = OctetsMut::with_slice(&mut d);\n            assert!(b.slice_last(11).is_err());\n        }\n    }\n}\n\n#[cfg(feature = \"huffman_hpack\")]\nmod huffman_table;\n"
  },
  {
    "path": "qlog/AGENTS.md",
    "content": "# qlog/\n\nqlog data model for QUIC and HTTP/3 per IETF drafts (`draft-ietf-quic-qlog-main-schema`, `draft-ietf-quic-qlog-quic-events`, `draft-ietf-quic-qlog-h3-events`). Pure data types + serde serialization; no IO (deferred to consumers).\n\n## STRUCTURE\n\n```\nsrc/\n  lib.rs          # Core types: Qlog, QlogSeq, Trace, TraceSeq, VantagePoint, Configuration, Error\n  streamer.rs     # QlogStreamer -- streaming JSON-SEQ writer with state machine\n  reader.rs       # QlogSeqReader -- streaming JSON-SEQ reader/iterator\n  events/\n    mod.rs        # Event, EventData (giant enum), EventType, Eventable trait, EventImportance\n    quic.rs       # QUIC event types (packet_sent, packet_received, frames, etc.)\n    h3.rs         # HTTP/3 event types\n    qpack.rs      # QPACK event types\n    connectivity.rs  # Connection-level events (state changes, path updates)\n    security.rs   # TLS/crypto events\n  testing/\n    mod.rs        # Test helpers\n    event_tests.rs\n    trace_tests.rs\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n|------|------|-------|\n| Add new event variant | `events/mod.rs` `EventData` enum | Add to relevant `events/*.rs`, wire into `EventData` |\n| Modify serialization | `lib.rs` | Heavy `serde_with` usage; `#[serde(rename)]` everywhere |\n| Streaming output | `streamer.rs` | `QlogStreamer` writes JSON-SEQ (RFC 7464) via `Write` trait |\n| Parse qlog files | `reader.rs` | `QlogSeqReader` iterates events from `BufRead` |\n| `Eventable` trait | `events/mod.rs:310` | Requires `importance()` and event name; impl on all event enums |\n| Two output modes | `lib.rs` | Buffered (`Qlog`/`Trace`) vs streaming (`QlogSeq`/`TraceSeq`) |\n\n## NOTES\n\n- Deps: `serde`, `serde_json` (preserve_order), `serde_with`, `smallvec`. No async, no IO beyond `Write`/`BufRead`.\n- `EventData` is a massive enum (~200 variants) spanning all protocol categories. Grep, don't scroll.\n- JSON field names follow IETF draft conventions (`snake_case`), mapped via `#[serde(rename)]`.\n- `serde_json::preserve_order` keeps field insertion order in output.\n- `HexSlice` helper for hex-encoding byte arrays in JSON.\n- No feature flags.\n"
  },
  {
    "path": "qlog/Cargo.toml",
    "content": "[package]\nname = \"qlog\"\nversion = \"0.16.0\"\ndescription = \"qlog data model for QUIC and HTTP/3\"\nrepository = { workspace = true }\nauthors = [\"Lucas Pardue <lucas@lucaspardue.com>\"]\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\nreadme = \"README.md\"\n\n[dependencies]\nserde = { workspace = true, features = [\"derive\"] }\nserde_json = { workspace = true, features = [\"preserve_order\"] }\nserde_with = { workspace = true, features = [\"macros\"] }\nsmallvec = { workspace = true, features = [\"serde\"] }\n\n[dev-dependencies]\npretty_assertions = \"1.4\""
  },
  {
    "path": "qlog/README.md",
    "content": "The qlog crate is an implementation of the qlog [main logging schema],\n[QUIC event definitions], and [HTTP/3 and QPACK event definitions].\nThe crate provides a qlog data model that can be used for traces with\nevents. It supports serialization and deserialization but defers logging IO\nchoices to applications.\n\nThe crate uses Serde for conversion between Rust and JSON.\n\n[main logging schema]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema\n[QUIC event definitions]:\nhttps://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html\n[HTTP/3 and QPACK event definitions]:\nhttps://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-h3-events.html\n\nOverview\n--------\nqlog is a hierarchical logging format, with a rough structure of:\n\n* Log\n  * Trace(s)\n    * Event(s)\n\nIn practice, a single QUIC connection maps to a single Trace file with one\nor more Events. Applications can decide whether to combine Traces from\ndifferent connections into the same Log.\n\n## Buffered Traces with standard JSON\n\nA [`Trace`] is a single JSON object. It contains metadata such as the\n[`VantagePoint`] of capture and the [`Configuration`], and protocol event\ndata in the [`Event`] array.\n\nJSON Traces allow applications to appends events to them before eventually\nbeing serialized as a complete JSON object.\n\n### Creating a Trace\n\n```rust\nlet mut trace = qlog::Trace::new(\n    qlog::VantagePoint {\n        name: Some(\"Example client\".to_string()),\n        ty: qlog::VantagePointType::Client,\n        flow: None,\n    },\n    Some(\"Example qlog trace\".to_string()),\n    Some(\"Example qlog trace description\".to_string()),\n    Some(qlog::Configuration {\n        time_offset: Some(0.0),\n        original_uris: None,\n    }),\n    None,\n);\n```\n\n### Adding events to a Trace\n\nQlog `Event` objects are added to `qlog::Trace.events`.\n\nThe following example demonstrates how to log a qlog QUIC `packet_sent` event\ncontaining a single Crypto frame. It constructs the necessary elements of the\n[`Event`], then appends it to the trace with [`push_event()`].\n\n```rust\nlet scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];\nlet dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];\n\nlet pkt_hdr = qlog::events::quic::PacketHeader::new(\n    qlog::events::quic::PacketType::Initial,\n    0,                         // packet_number\n    None,                      // flags\n    None,                      // token\n    None,                      // length\n    Some(0x00000001),          // version\n    Some(&scid),\n    Some(&dcid),\n);\n\nlet frames = vec![qlog::events::quic::QuicFrame::Crypto {\n    offset: 0,\n    length: 0,\n}];\n\nlet raw = qlog::events::RawInfo {\n    length: Some(1251),\n    payload_length: Some(1224),\n    data: None,\n};\n\nlet event_data =\n     qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {\n         header: pkt_hdr,\n         frames: Some(frames.into()),\n         is_coalesced: None,\n         retry_token: None,\n         stateless_reset_token: None,\n         supported_versions: None,\n         raw: Some(raw),\n         datagram_id: None,\n     });\n\ntrace.push_event(qlog::events::Event::with_time(0.0, event_data));\n```\n\n### Serializing\n\nThe qlog crate has only been tested with `serde_json`, however other serializer\ntargets might work.\n\nFor example, serializing the trace created above:\n\n```rust\nserde_json::to_string_pretty(&trace).unwrap();\n```\n\nwould generate the following:\n\n```\n{\n  \"vantage_point\": {\n    \"name\": \"Example client\",\n    \"type\": \"client\"\n  },\n  \"title\": \"Example qlog trace\",\n  \"description\": \"Example qlog trace description\",\n  \"configuration\": {\n    \"time_offset\": 0.0\n  },\n  \"events\": [\n    {\n      \"time\": 0.0,\n      \"name\": \"transport:packet_sent\",\n      \"data\": {\n        \"header\": {\n          \"packet_type\": \"initial\",\n          \"packet_number\": 0,\n          \"version\": \"1\",\n          \"scil\": 8,\n          \"dcil\": 8,\n          \"scid\": \"7e37e4dcc6682da8\",\n          \"dcid\": \"36ce104eee50101c\"\n        },\n        \"raw\": {\n          \"length\": 1251,\n          \"payload_length\": 1224\n        },\n        \"frames\": [\n          {\n            \"frame_type\": \"crypto\",\n            \"offset\": 0,\n            \"length\": 0\n          }\n        ]\n      }\n    }\n  ]\n}\n```\n\n## Streaming Traces JSON Text Sequences (JSON-SEQ)\n\nTo help support streaming serialization of qlogs,\ndraft-ietf-quic-qlog-main-schema-01 introduced support for RFC 7464 JSON\nText Sequences (JSON-SEQ). The qlog crate supports this format and provides\nutilities that aid streaming.\n\nA [`TraceSeq`] contains metadata such as the [`VantagePoint`] of capture and\nthe [`Configuration`]. However, protocol event data is handled as separate\nlines containing a record separator character, a serialized [`Event`], and a\nnewline.\n\n### Creating a TraceSeq\n\n``` rust\nlet mut trace = qlog::TraceSeq::new(\n    qlog::VantagePoint {\n        name: Some(\"Example client\".to_string()),\n        ty: qlog::VantagePointType::Client,\n        flow: None,\n    },\n    Some(\"Example qlog trace\".to_string()),\n    Some(\"Example qlog trace description\".to_string()),\n    Some(qlog::Configuration {\n        time_offset: Some(0.0),\n        original_uris: None,\n    }),\n    None,\n);\n```\n\nCreate an object with the [`Write`] trait:\n```\nlet mut file = std::fs::File::create(\"foo.sqlog\").unwrap();\n```\n\nCreate a [`QlogStreamer`] and start serialization to foo.sqlog\nusing [`start_log()`]:\n\n```rust\nlet mut streamer = qlog::QlogStreamer::new(\n    qlog::QLOG_VERSION.to_string(),\n    Some(\"Example qlog\".to_string()),\n    Some(\"Example qlog description\".to_string()),\n    None,\n    std::time::Instant::now(),\n    trace,\n    qlog::EventImportance::Base,\n    Box::new(file),\n);\n\nstreamer.start_log().ok();\n```\n\n### Adding simple events\n\nOnce logging has started you can stream events. Simple events can be written in\none step using [`add_event()`]:\n\n```rust\nlet event_data = qlog::events::EventData::MetricsUpdated(\n    qlog::events::quic::MetricsUpdated {\n        min_rtt: Some(1.0),\n        smoothed_rtt: Some(1.0),\n        latest_rtt: Some(1.0),\n        rtt_variance: Some(1.0),\n        pto_count: Some(1),\n        congestion_window: Some(1234),\n        bytes_in_flight: Some(5678),\n        ssthresh: None,\n        packets_in_flight: None,\n        pacing_rate: None,\n    },\n);\n\nlet event = qlog::events::Event::with_time(0.0, event_data);\nstreamer.add_event(event).ok();\n```\n\n### Adding events with frames\nSome events contain optional arrays of QUIC frames. If the event has\n`Some(Vec<QuicFrame>)`, even if it is empty, the streamer enters a frame\nserializing mode that must be finalized before other events can be logged.\n\nIn this example, a `PacketSent` event is created with an empty frame array and\nframes are written out later:\n\n```rust\nlet scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];\nlet dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];\n\nlet pkt_hdr = qlog::events::quic::PacketHeader::with_type(\n    qlog::events::quic::PacketType::OneRtt,\n    0,\n    Some(0x00000001),\n    Some(&scid),\n    Some(&dcid),\n);\n\nlet event_data =\n    qlog::events::EventData::PacketSent(qlog::events::quic::PacketSent {\n      header: pkt_hdr,\n      frames: Some(vec![]),\n      is_coalesced: None,\n      retry_token: None,\n      stateless_reset_token: None,\n      supported_versions: None,\n      raw: None,\n      datagram_id: None,\n};\n\nlet event = qlog::events::Event::with_time(0.0, event_data);\n\nstreamer.add_event(event).ok();\n```\n\nIn this example, the frames contained in the QUIC packet\nare PING and PADDING. Each frame is written using the\n[`add_frame()`] method. Frame writing is concluded with\n[`finish_frames()`].\n\n```rust\nlet ping = qlog::events::quic::QuicFrame::Ping;\nlet padding = qlog::events::quic::QuicFrame::Padding;\n\nstreamer.add_frame(ping, false).ok();\nstreamer.add_frame(padding, false).ok();\n\nstreamer.finish_frames().ok();\n```\n\nOnce all events have been written, the log\ncan be finalized with [`finish_log()`]:\n\n```rust\nstreamer.finish_log().ok();\n```\n\n### Serializing\n\nSerialization to JSON occurs as methods on the [`QlogStreamer`]\nare called. No additional steps are required.\n\n[`Trace`]: struct.Trace.html\n[`TraceSeq`]: struct.TraceSeq.html\n[`VantagePoint`]: struct.VantagePoint.html\n[`Configuration`]: struct.Configuration.html\n[`qlog::Trace.events`]: struct.Trace.html#structfield.events\n[`push_event()`]: struct.Trace.html#method.push_event\n[`packet_sent_min()`]: event/struct.Event.html#method.packet_sent_min\n[`QuicFrame::crypto()`]: enum.QuicFrame.html#variant.Crypto\n[`QlogStreamer`]: struct.QlogStreamer.html\n[`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html\n[`start_log()`]: struct.QlogStreamer.html#method.start_log\n[`add_event()`]: struct.QlogStreamer.html#method.add_event\n[`add_frame()`]: struct.QlogStreamer.html#method.add_frame\n[`finish_frames()`]: struct.QlogStreamer.html#method.finish_frames\n[`finish_log()`]: struct.QlogStreamer.html#method.finish_log"
  },
  {
    "path": "qlog/src/events/http3.rs",
    "content": "// Copyright (C) 2021, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse crate::events::RawInfo;\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum Initiator {\n    Local,\n    Remote,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum StreamType {\n    Request,\n    Control,\n    Push,\n    Reserved,\n    #[default]\n    Unknown,\n    QpackEncode,\n    QpackDecode,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PushDecision {\n    Claimed,\n    Abandoned,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PriorityTargetStreamType {\n    Request,\n    Push,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum Http3EventType {\n    ParametersSet,\n    ParametersRestored,\n    StreamTypeSet,\n    PriorityUpdated,\n    FrameCreated,\n    FrameParsed,\n    DatagramCreated,\n    DatagramParsed,\n    PushResolved,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct HttpHeader {\n    pub name: Option<String>,\n    pub name_bytes: Option<String>,\n    pub value: Option<String>,\n    pub value_bytes: Option<String>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct Setting {\n    pub name: Option<String>,\n    pub name_bytes: Option<u64>,\n    pub value: u64,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum Http3FrameTypeName {\n    Data,\n    Headers,\n    CancelPush,\n    Settings,\n    PushPromise,\n    Goaway,\n    MaxPushId,\n    DuplicatePush,\n    Reserved,\n    Unknown,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(tag = \"frame_type\")]\n#[serde(rename_all = \"snake_case\")]\n// Strictly, the qlog spec says that all these frame types have a frame_type\n// field. But instead of making that a rust object property, just use serde to\n// ensure it goes out on the wire. This means that deserialization of frames\n// also works automatically.\npub enum Http3Frame {\n    Data {\n        raw: Option<RawInfo>,\n    },\n\n    Headers {\n        headers: Vec<HttpHeader>,\n    },\n\n    CancelPush {\n        push_id: u64,\n    },\n\n    Settings {\n        settings: Vec<Setting>,\n    },\n\n    PushPromise {\n        push_id: u64,\n        headers: Vec<HttpHeader>,\n    },\n\n    Goaway {\n        id: u64,\n    },\n\n    MaxPushId {\n        push_id: u64,\n    },\n\n    PriorityUpdate {\n        target_stream_type: PriorityTargetStreamType,\n        prioritized_element_id: u64,\n        priority_field_value: String,\n    },\n\n    Reserved {\n        length: Option<u64>,\n    },\n\n    Unknown {\n        frame_type_value: u64,\n        raw: Option<RawInfo>,\n    },\n}\n\nimpl Default for Http3Frame {\n    fn default() -> Self {\n        Self::Unknown {\n            frame_type_value: 0,\n            raw: None,\n        }\n    }\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct ParametersSet {\n    pub initiator: Option<Initiator>,\n\n    #[serde(alias = \"max_header_list_size\")]\n    pub max_field_section_size: Option<u64>,\n    pub max_table_capacity: Option<u64>,\n    pub blocked_streams_count: Option<u64>,\n    pub extended_connect: Option<u16>,\n    pub h3_datagram: Option<u16>,\n\n    // qlog-defined\n    pub waits_for_settings: Option<bool>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct ParametersRestored {\n    #[serde(alias = \"max_header_list_size\")]\n    pub max_field_section_size: Option<u64>,\n    pub max_table_capacity: Option<u64>,\n    pub blocked_streams_count: Option<u64>,\n    pub extended_connect: Option<u16>,\n    pub h3_datagram: Option<u16>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct StreamTypeSet {\n    pub initiator: Option<Initiator>,\n    pub stream_id: u64,\n    pub stream_type: StreamType,\n    pub stream_type_bytes: Option<u64>,\n    pub associated_push_id: Option<u64>,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PriorityUpdatedTrigger {\n    ClientSignalReceived,\n    Local,\n    Other,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PriorityUpdatedReason {\n    ClientSignalOnly,\n    ClientServerMerged,\n    LocalPolicy,\n    Other,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct PriorityUpdated {\n    pub stream_id: Option<u64>,\n    pub push_id: Option<u64>,\n    pub old: Option<String>,\n    pub new: String,\n    pub trigger: Option<PriorityUpdatedTrigger>,\n    pub reason: Option<PriorityUpdatedReason>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct FrameCreated {\n    pub stream_id: u64,\n    pub length: Option<u64>,\n    pub frame: Http3Frame,\n\n    pub raw: Option<RawInfo>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct FrameParsed {\n    pub stream_id: u64,\n    pub length: Option<u64>,\n    pub frame: Http3Frame,\n\n    pub raw: Option<RawInfo>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct DatagramCreated {\n    pub quarter_stream_id: u64,\n    pub datagram: Option<Vec<String>>,\n    pub raw: Option<RawInfo>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct DatagramParsed {\n    pub quarter_stream_id: u64,\n    pub datagram: Option<Vec<String>>,\n    pub raw: Option<RawInfo>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct PushResolved {\n    pub push_id: Option<u64>,\n    pub stream_id: Option<u64>,\n\n    pub decision: PushDecision,\n}\n"
  },
  {
    "path": "qlog/src/events/mod.rs",
    "content": "// Copyright (C) 2021, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::Bytes;\nuse crate::Token;\nuse http3::*;\nuse quic::*;\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse std::collections::BTreeMap;\n\npub type ExData = BTreeMap<String, serde_json::Value>;\n\npub const LOGLEVEL_URI: &str = \"urn:ietf:params:qlog:events:loglevel-13\";\n\npub const QUIC_URI: &str = \"urn:ietf:params:qlog:events:quic-12\";\npub const HTTP3_URI: &str = \"urn:ietf:params:qlog:events:http3-12\";\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Default)]\n#[serde(untagged)]\npub enum EventType {\n    QuicEventType(QuicEventType),\n\n    Http3EventType(Http3EventType),\n\n    LogLevelEventType(LogLevelEventType),\n\n    #[default]\n    None,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum TimeFormat {\n    #[default]\n    RelativeToEpoch,\n    RelativeToPreviousEvent,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, Debug)]\npub struct Event {\n    pub time: f64,\n\n    // Strictly, the qlog 02 spec says we should have a name field in the\n    // `Event` structure. However, serde's autogenerated Deserialize code\n    // struggles to read Events properly because the `EventData` types often\n    // alias. In order to work around that, we use can use a trick that will\n    // give serde autogen all the information that it needs while also produced\n    // a legal qlog. Specifically, strongly linking an EventData enum variant\n    // with the wire-format name.\n    //\n    // The trick is to use Adjacent Tagging\n    // (https://serde.rs/enum-representations.html#adjacently-tagged) with\n    // Struct flattening (https://serde.rs/attr-flatten.html). At a high level\n    // this first creates an `EventData` JSON object:\n    //\n    // {name: <enum variant name>, data: enum variant data }\n    //\n    // and then flattens those fields into the `Event` object.\n    #[serde(flatten)]\n    pub data: EventData,\n\n    #[serde(flatten)]\n    pub ex_data: ExData,\n\n    pub protocol_type: Option<String>,\n    pub group_id: Option<String>,\n\n    pub time_format: Option<TimeFormat>,\n\n    #[serde(skip)]\n    ty: EventType,\n}\n\nimpl Event {\n    /// Returns a new `Event` object with the provided time and data.\n    pub fn with_time(time: f64, data: EventData) -> Self {\n        Self::with_time_ex(time, data, Default::default())\n    }\n\n    /// Returns a new `Event` object with the provided time, data and ex_data.\n    pub fn with_time_ex(time: f64, data: EventData, ex_data: ExData) -> Self {\n        let ty = EventType::from(&data);\n        Event {\n            time,\n            data,\n            ex_data,\n            protocol_type: Default::default(),\n            group_id: Default::default(),\n            time_format: Default::default(),\n            ty,\n        }\n    }\n}\n\nimpl Eventable for Event {\n    fn importance(&self) -> EventImportance {\n        self.ty.into()\n    }\n\n    fn set_time(&mut self, time: f64) {\n        self.time = time;\n    }\n}\n\nimpl PartialEq for Event {\n    // custom comparison to skip over the `ty` field\n    fn eq(&self, other: &Event) -> bool {\n        self.time == other.time &&\n            self.data == other.data &&\n            self.ex_data == other.ex_data &&\n            self.protocol_type == other.protocol_type &&\n            self.group_id == other.group_id &&\n            self.time_format == other.time_format\n    }\n}\n\n#[derive(Serialize, Deserialize, Clone, Debug)]\npub struct JsonEvent {\n    pub time: f64,\n\n    #[serde(skip)]\n    pub importance: EventImportance,\n\n    pub name: String,\n    pub data: serde_json::Value,\n}\n\nimpl Eventable for JsonEvent {\n    fn importance(&self) -> EventImportance {\n        self.importance\n    }\n\n    fn set_time(&mut self, time: f64) {\n        self.time = time;\n    }\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub enum EventImportance {\n    #[default]\n    Core,\n    Base,\n    Extra,\n}\n\nimpl EventImportance {\n    /// Returns true if this importance level is included by `other`.\n    pub fn is_contained_in(&self, other: &EventImportance) -> bool {\n        match (other, self) {\n            (EventImportance::Core, EventImportance::Core) => true,\n\n            (EventImportance::Base, EventImportance::Core) |\n            (EventImportance::Base, EventImportance::Base) => true,\n\n            (EventImportance::Extra, EventImportance::Core) |\n            (EventImportance::Extra, EventImportance::Base) |\n            (EventImportance::Extra, EventImportance::Extra) => true,\n\n            (..) => false,\n        }\n    }\n}\n\nimpl From<EventType> for EventImportance {\n    fn from(ty: EventType) -> Self {\n        match ty {\n            EventType::QuicEventType(QuicEventType::ServerListening) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::ConnectionStarted) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::ConnectionClosed) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::ConnectionIdUpdated) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::SpinBitUpdated) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::ConnectionStateUpdated) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::TupleAssigned) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::MtuUpdated) =>\n                EventImportance::Extra,\n\n            EventType::QuicEventType(QuicEventType::VersionInformation) =>\n                EventImportance::Core,\n            EventType::QuicEventType(QuicEventType::AlpnInformation) =>\n                EventImportance::Core,\n            EventType::QuicEventType(QuicEventType::ParametersSet) =>\n                EventImportance::Core,\n            EventType::QuicEventType(QuicEventType::ParametersRestored) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::PacketSent) =>\n                EventImportance::Core,\n            EventType::QuicEventType(QuicEventType::PacketReceived) =>\n                EventImportance::Core,\n            EventType::QuicEventType(QuicEventType::PacketDropped) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::PacketBuffered) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::PacketsAcked) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::UdpDatagramsSent) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::UdpDatagramsReceived) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::UdpDatagramDropped) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::StreamStateUpdated) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::FramesProcessed) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::StreamDataMoved) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::DatagramDataMoved) =>\n                EventImportance::Base,\n            EventType::QuicEventType(\n                QuicEventType::ConnectionDataBlockedUpdated,\n            ) => EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::StreamDataBlockedUpdated) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(\n                QuicEventType::DatagramDataBlockedUpdated,\n            ) => EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::MigrationStateUpdated) =>\n                EventImportance::Base,\n\n            EventType::QuicEventType(QuicEventType::KeyUpdated) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::KeyDiscarded) =>\n                EventImportance::Base,\n\n            EventType::QuicEventType(QuicEventType::RecoveryParametersSet) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::RecoveryMetricsUpdated) =>\n                EventImportance::Core,\n            EventType::QuicEventType(QuicEventType::CongestionStateUpdated) =>\n                EventImportance::Base,\n            EventType::QuicEventType(QuicEventType::TimerUpdated) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::PacketLost) =>\n                EventImportance::Core,\n            EventType::QuicEventType(QuicEventType::MarkedForRetransmit) =>\n                EventImportance::Extra,\n            EventType::QuicEventType(QuicEventType::EcnStateUpdated) =>\n                EventImportance::Extra,\n\n            EventType::Http3EventType(Http3EventType::ParametersSet) =>\n                EventImportance::Base,\n            EventType::Http3EventType(Http3EventType::StreamTypeSet) =>\n                EventImportance::Base,\n            EventType::Http3EventType(Http3EventType::PriorityUpdated) =>\n                EventImportance::Base,\n            EventType::Http3EventType(Http3EventType::FrameCreated) =>\n                EventImportance::Core,\n            EventType::Http3EventType(Http3EventType::FrameParsed) =>\n                EventImportance::Core,\n            EventType::Http3EventType(Http3EventType::DatagramCreated) =>\n                EventImportance::Base,\n            EventType::Http3EventType(Http3EventType::DatagramParsed) =>\n                EventImportance::Base,\n            EventType::Http3EventType(Http3EventType::PushResolved) =>\n                EventImportance::Extra,\n\n            _ => unimplemented!(),\n        }\n    }\n}\n\npub trait Eventable {\n    fn importance(&self) -> EventImportance;\n\n    fn set_time(&mut self, time: f64);\n}\n\nimpl From<&EventData> for EventType {\n    fn from(event_data: &EventData) -> Self {\n        match event_data {\n            EventData::QuicServerListening { .. } =>\n                EventType::QuicEventType(QuicEventType::ServerListening),\n            EventData::QuicConnectionStarted { .. } =>\n                EventType::QuicEventType(QuicEventType::ConnectionStarted),\n            EventData::QuicConnectionClosed { .. } =>\n                EventType::QuicEventType(QuicEventType::ConnectionClosed),\n            EventData::QuicConnectionIdUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::ConnectionIdUpdated),\n            EventData::QuicSpinBitUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::SpinBitUpdated),\n            EventData::QuicConnectionStateUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::ConnectionStateUpdated),\n            EventData::QuicTupleAssigned { .. } =>\n                EventType::QuicEventType(QuicEventType::TupleAssigned),\n            EventData::QuicMtuUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::MtuUpdated),\n\n            EventData::QuicVersionInformation { .. } =>\n                EventType::QuicEventType(QuicEventType::VersionInformation),\n            EventData::QuicAlpnInformation { .. } =>\n                EventType::QuicEventType(QuicEventType::AlpnInformation),\n            EventData::QuicParametersSet { .. } =>\n                EventType::QuicEventType(QuicEventType::ParametersSet),\n            EventData::QuicParametersRestored { .. } =>\n                EventType::QuicEventType(QuicEventType::ParametersRestored),\n            EventData::QuicPacketSent { .. } =>\n                EventType::QuicEventType(QuicEventType::PacketSent),\n            EventData::QuicPacketReceived { .. } =>\n                EventType::QuicEventType(QuicEventType::PacketReceived),\n            EventData::QuicPacketDropped { .. } =>\n                EventType::QuicEventType(QuicEventType::PacketDropped),\n            EventData::QuicPacketBuffered { .. } =>\n                EventType::QuicEventType(QuicEventType::PacketBuffered),\n            EventData::QuicPacketsAcked { .. } =>\n                EventType::QuicEventType(QuicEventType::PacketsAcked),\n            EventData::QuicUdpDatagramsSent { .. } =>\n                EventType::QuicEventType(QuicEventType::UdpDatagramsSent),\n            EventData::QuicUdpDatagramsReceived { .. } =>\n                EventType::QuicEventType(QuicEventType::UdpDatagramsReceived),\n            EventData::QuicUdpDatagramDropped { .. } =>\n                EventType::QuicEventType(QuicEventType::UdpDatagramDropped),\n            EventData::QuicStreamStateUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::StreamStateUpdated),\n            EventData::QuicFramesProcessed { .. } =>\n                EventType::QuicEventType(QuicEventType::FramesProcessed),\n            EventData::QuicStreamDataMoved { .. } =>\n                EventType::QuicEventType(QuicEventType::StreamDataMoved),\n            EventData::QuicDatagramDataMoved { .. } =>\n                EventType::QuicEventType(QuicEventType::DatagramDataMoved),\n            EventData::QuicConnectionDataBlockedUpdated { .. } =>\n                EventType::QuicEventType(\n                    QuicEventType::ConnectionDataBlockedUpdated,\n                ),\n            EventData::QuicStreamDataBlockedUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::StreamDataBlockedUpdated),\n            EventData::QuicDatagramDataBlockedUpdated { .. } =>\n                EventType::QuicEventType(\n                    QuicEventType::DatagramDataBlockedUpdated,\n                ),\n            EventData::QuicMigrationStateUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::MigrationStateUpdated),\n\n            EventData::QuicKeyUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::KeyUpdated),\n            EventData::QuicKeyDiscarded { .. } =>\n                EventType::QuicEventType(QuicEventType::KeyDiscarded),\n\n            EventData::QuicRecoveryParametersSet { .. } =>\n                EventType::QuicEventType(QuicEventType::RecoveryParametersSet),\n            EventData::QuicMetricsUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::RecoveryMetricsUpdated),\n            EventData::QuicCongestionStateUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::CongestionStateUpdated),\n            EventData::QuicTimerUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::TimerUpdated),\n            EventData::QuicPacketLost { .. } =>\n                EventType::QuicEventType(QuicEventType::PacketLost),\n            EventData::QuicMarkedForRetransmit { .. } =>\n                EventType::QuicEventType(QuicEventType::MarkedForRetransmit),\n            EventData::QuicEcnStateUpdated { .. } =>\n                EventType::QuicEventType(QuicEventType::EcnStateUpdated),\n\n            EventData::Http3ParametersSet { .. } =>\n                EventType::Http3EventType(Http3EventType::ParametersSet),\n            EventData::Http3ParametersRestored { .. } =>\n                EventType::Http3EventType(Http3EventType::ParametersRestored),\n            EventData::Http3StreamTypeSet { .. } =>\n                EventType::Http3EventType(Http3EventType::StreamTypeSet),\n            EventData::Http3PriorityUpdated { .. } =>\n                EventType::Http3EventType(Http3EventType::PriorityUpdated),\n            EventData::Http3FrameCreated { .. } =>\n                EventType::Http3EventType(Http3EventType::FrameCreated),\n            EventData::Http3FrameParsed { .. } =>\n                EventType::Http3EventType(Http3EventType::FrameParsed),\n            EventData::Http3DatagramCreated { .. } =>\n                EventType::Http3EventType(Http3EventType::DatagramCreated),\n            EventData::Http3DatagramParsed { .. } =>\n                EventType::Http3EventType(Http3EventType::DatagramParsed),\n            EventData::Http3PushResolved { .. } =>\n                EventType::Http3EventType(Http3EventType::PushResolved),\n\n            EventData::LogLevelError { .. } =>\n                EventType::LogLevelEventType(LogLevelEventType::Error),\n            EventData::LogLevelWarning { .. } =>\n                EventType::LogLevelEventType(LogLevelEventType::Warning),\n            EventData::LogLevelInfo { .. } =>\n                EventType::LogLevelEventType(LogLevelEventType::Info),\n            EventData::LogLevelDebug { .. } =>\n                EventType::LogLevelEventType(LogLevelEventType::Debug),\n            EventData::LogLevelVerbose { .. } =>\n                EventType::LogLevelEventType(LogLevelEventType::Verbose),\n            //_ => EventType::None,\n        }\n    }\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum DataRecipient {\n    User,\n    Application,\n    Transport,\n    Network,\n    Dropped,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct RawInfo {\n    pub length: Option<u64>,\n    pub payload_length: Option<u64>,\n\n    pub data: Option<Bytes>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]\n#[serde(tag = \"name\", content = \"data\")]\n#[allow(clippy::large_enum_variant)]\npub enum EventData {\n    // QUIC\n    #[serde(rename = \"quic:server_listening\")]\n    QuicServerListening(quic::ServerListening),\n\n    #[serde(rename = \"quic:connection_started\")]\n    QuicConnectionStarted(quic::ConnectionStarted),\n\n    #[serde(rename = \"quic:connection_closed\")]\n    QuicConnectionClosed(quic::ConnectionClosed),\n\n    #[serde(rename = \"quic:connection_id_updated\")]\n    QuicConnectionIdUpdated(quic::ConnectionIdUpdated),\n\n    #[serde(rename = \"quic:spin_bit_updated\")]\n    QuicSpinBitUpdated(quic::SpinBitUpdated),\n\n    #[serde(rename = \"quic:connection_state_updated\")]\n    QuicConnectionStateUpdated(quic::ConnectionStateUpdated),\n\n    #[serde(rename = \"quic:tuple_assigned\")]\n    QuicTupleAssigned(quic::TupleAssigned),\n\n    #[serde(rename = \"quic:mtu_updated\")]\n    QuicMtuUpdated(quic::MtuUpdated),\n\n    #[serde(rename = \"quic:version_information\")]\n    QuicVersionInformation(quic::QuicVersionInformation),\n\n    #[serde(rename = \"quic:alpn_information\")]\n    QuicAlpnInformation(quic::AlpnInformation),\n\n    #[serde(rename = \"quic:parameters_set\")]\n    QuicParametersSet(quic::ParametersSet),\n\n    #[serde(rename = \"quic:parameters_restored\")]\n    QuicParametersRestored(quic::ParametersRestored),\n\n    #[serde(rename = \"quic:packet_sent\")]\n    QuicPacketSent(quic::PacketSent),\n\n    #[serde(rename = \"quic:packet_received\")]\n    QuicPacketReceived(quic::PacketReceived),\n\n    #[serde(rename = \"quic:packet_dropped\")]\n    QuicPacketDropped(quic::PacketDropped),\n\n    #[serde(rename = \"quic:packet_buffered\")]\n    QuicPacketBuffered(quic::PacketBuffered),\n\n    #[serde(rename = \"quic:packets_acked\")]\n    QuicPacketsAcked(quic::PacketsAcked),\n\n    #[serde(rename = \"quic:datagrams_sent\")]\n    QuicUdpDatagramsSent(quic::UdpDatagramsSent),\n\n    #[serde(rename = \"quic:datagrams_received\")]\n    QuicUdpDatagramsReceived(quic::UdpDatagramsReceived),\n\n    #[serde(rename = \"quic:datagram_dropped\")]\n    QuicUdpDatagramDropped(quic::UdpDatagramDropped),\n\n    #[serde(rename = \"quic:stream_state_updated\")]\n    QuicStreamStateUpdated(quic::StreamStateUpdated),\n\n    #[serde(rename = \"quic:frames_processed\")]\n    QuicFramesProcessed(quic::FramesProcessed),\n\n    #[serde(rename = \"quic:stream_data_moved\")]\n    QuicStreamDataMoved(quic::StreamDataMoved),\n\n    #[serde(rename = \"quic:datagram_data_moved\")]\n    QuicDatagramDataMoved(quic::DatagramDataMoved),\n\n    #[serde(rename = \"quic:connection_data_blocked_updated\")]\n    QuicConnectionDataBlockedUpdated(quic::ConnectionDataBlockedUpdated),\n\n    #[serde(rename = \"quic:stream_data_blocked_updated\")]\n    QuicStreamDataBlockedUpdated(quic::StreamDataBlockedUpdated),\n\n    #[serde(rename = \"quic:datagram_data_blocked_updated\")]\n    QuicDatagramDataBlockedUpdated(quic::DatagramDataBlockedUpdated),\n\n    #[serde(rename = \"quic:migration_state_updated\")]\n    QuicMigrationStateUpdated(quic::MigrationStateUpdated),\n\n    #[serde(rename = \"quic:key_updated\")]\n    QuicKeyUpdated(quic::KeyUpdated),\n\n    #[serde(rename = \"quic:key_retired\")]\n    QuicKeyDiscarded(quic::KeyDiscarded),\n\n    #[serde(rename = \"quic:recovery_parameters_set\")]\n    QuicRecoveryParametersSet(quic::RecoveryParametersSet),\n\n    #[serde(rename = \"quic:recovery_metrics_updated\")]\n    QuicMetricsUpdated(quic::RecoveryMetricsUpdated),\n\n    #[serde(rename = \"quic:congestion_state_updated\")]\n    QuicCongestionStateUpdated(quic::CongestionStateUpdated),\n\n    #[serde(rename = \"quic:timer_updated\")]\n    QuicTimerUpdated(quic::TimerUpdated),\n\n    #[serde(rename = \"quic:packet_lost\")]\n    QuicPacketLost(quic::PacketLost),\n\n    #[serde(rename = \"quic:marked_for_retransmit\")]\n    QuicMarkedForRetransmit(quic::MarkedForRetransmit),\n\n    #[serde(rename = \"quic:ecn_state_updated\")]\n    QuicEcnStateUpdated(quic::EcnStateUpdated),\n\n    // HTTP/3\n    #[serde(rename = \"http3:parameters_set\")]\n    Http3ParametersSet(http3::ParametersSet),\n\n    #[serde(rename = \"http3:parameters_restored\")]\n    Http3ParametersRestored(http3::ParametersRestored),\n\n    #[serde(rename = \"http3:stream_type_set\")]\n    Http3StreamTypeSet(http3::StreamTypeSet),\n\n    #[serde(rename = \"http3:priority_updated\")]\n    Http3PriorityUpdated(http3::PriorityUpdated),\n\n    #[serde(rename = \"http3:frame_created\")]\n    Http3FrameCreated(http3::FrameCreated),\n\n    #[serde(rename = \"http3:frame_parsed\")]\n    Http3FrameParsed(http3::FrameParsed),\n\n    #[serde(rename = \"http3:datagram_created\")]\n    Http3DatagramCreated(http3::DatagramCreated),\n\n    #[serde(rename = \"http3:datagram_parsed\")]\n    Http3DatagramParsed(http3::DatagramParsed),\n\n    #[serde(rename = \"http3:push_resolved\")]\n    Http3PushResolved(http3::PushResolved),\n\n    // LogLevel\n    #[serde(rename = \"loglevel:error\")]\n    LogLevelError {\n        code: Option<u64>,\n        message: Option<String>,\n    },\n\n    #[serde(rename = \"loglevel:warning\")]\n    LogLevelWarning {\n        code: Option<u64>,\n        message: Option<String>,\n    },\n\n    #[serde(rename = \"loglevel:info\")]\n    LogLevelInfo {\n        code: Option<u64>,\n        message: Option<String>,\n    },\n\n    #[serde(rename = \"loglevel:debug\")]\n    LogLevelDebug {\n        code: Option<u64>,\n        message: Option<String>,\n    },\n\n    #[serde(rename = \"loglevel:verbose\")]\n    LogLevelVerbose {\n        code: Option<u64>,\n        message: Option<String>,\n    },\n}\n\nimpl EventData {\n    /// Returns size of `EventData` array of `QuicFrame`s if it exists.\n    pub fn contains_quic_frames(&self) -> Option<usize> {\n        // For some EventData variants, the frame array is optional\n        // but for others it is mandatory.\n        match self {\n            EventData::QuicPacketSent(pkt) =>\n                pkt.frames.as_ref().map(|f| f.len()),\n\n            EventData::QuicPacketReceived(pkt) =>\n                pkt.frames.as_ref().map(|f| f.len()),\n\n            EventData::QuicPacketLost(pkt) =>\n                pkt.frames.as_ref().map(|f| f.len()),\n\n            EventData::QuicMarkedForRetransmit(ev) => Some(ev.frames.len()),\n            EventData::QuicFramesProcessed(ev) => Some(ev.frames.len()),\n\n            _ => None,\n        }\n    }\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum LogLevelEventType {\n    Error,\n    Warning,\n    Info,\n    Debug,\n    Verbose,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(untagged)]\npub enum ConnectionClosedEventError {\n    TransportError(TransportError),\n    CryptoError(CryptoError),\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(untagged)]\npub enum ConnectionClosedFrameError {\n    TransportError(TransportError),\n    ApplicationError(ApplicationError),\n    CryptoError(CryptoError),\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum ApplicationError {\n    HttpNoError,\n    HttpGeneralProtocolError,\n    HttpInternalError,\n    HttpRequestCancelled,\n    HttpIncompleteRequest,\n    HttpConnectError,\n    HttpFrameError,\n    HttpExcessiveLoad,\n    HttpVersionFallback,\n    HttpIdError,\n    HttpStreamCreationError,\n    HttpClosedCriticalStream,\n    HttpEarlyResponse,\n    HttpMissingSettings,\n    HttpUnexpectedFrame,\n    HttpRequestRejection,\n    HttpSettingsError,\n    Unknown,\n}\n\n// TODO\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum CryptoError {\n    Prefix,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct TupleEndpointInfo {\n    pub ip_v4: Option<String>,\n    pub port_v4: Option<u16>,\n    pub ip_v6: Option<String>,\n    pub port_v6: Option<u16>,\n\n    pub connection_ids: Option<Vec<Bytes>>,\n}\n\npub mod http3;\npub mod quic;\n"
  },
  {
    "path": "qlog/src/events/quic.rs",
    "content": "// Copyright (C) 2021, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse smallvec::SmallVec;\n\nuse super::ExData;\nuse crate::HexSlice;\n\nuse crate::events::ApplicationError;\nuse crate::events::ConnectionClosedEventError;\nuse crate::events::ConnectionClosedFrameError;\nuse crate::events::DataRecipient;\nuse crate::events::RawInfo;\nuse crate::events::Token;\nuse crate::events::TupleEndpointInfo;\nuse crate::Bytes;\nuse crate::StatelessResetToken;\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum PacketType {\n    Initial,\n    Handshake,\n\n    #[serde(rename = \"0RTT\")]\n    ZeroRtt,\n\n    #[serde(rename = \"1RTT\")]\n    OneRtt,\n\n    Retry,\n    VersionNegotiation,\n    StatelessReset,\n    #[default]\n    Unknown,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug, Default)]\npub struct PacketHeader {\n    pub packet_type: PacketType,\n    pub packet_type_bytes: Option<u64>,\n    pub spin_bit: Option<bool>,\n    pub key_phase: Option<u64>,\n    pub key_phase_bit: Option<bool>,\n    pub packet_number_length: Option<u8>,\n    pub packet_number: Option<u64>,\n\n    pub token: Option<Token>,\n\n    pub length: Option<u16>,\n\n    pub version: Option<Bytes>,\n\n    pub scil: Option<u8>,\n    pub dcil: Option<u8>,\n    pub scid: Option<Bytes>,\n    pub dcid: Option<Bytes>,\n}\n\nimpl PacketHeader {\n    #[allow(clippy::too_many_arguments)]\n    /// Creates a new PacketHeader.\n    pub fn new(\n        packet_type: PacketType, packet_number: Option<u64>,\n        token: Option<Token>, length: Option<u16>, version: Option<u32>,\n        scid: Option<&[u8]>, dcid: Option<&[u8]>,\n    ) -> Self {\n        let (scil, scid) = match scid {\n            Some(cid) => (\n                Some(cid.len() as u8),\n                Some(format!(\"{}\", HexSlice::new(&cid))),\n            ),\n\n            None => (None, None),\n        };\n\n        let (dcil, dcid) = match dcid {\n            Some(cid) => (\n                Some(cid.len() as u8),\n                Some(format!(\"{}\", HexSlice::new(&cid))),\n            ),\n\n            None => (None, None),\n        };\n\n        let version = version.map(|v| format!(\"{v:x?}\"));\n\n        PacketHeader {\n            packet_type,\n            packet_number,\n            token,\n            length,\n            version,\n            scil,\n            dcil,\n            scid,\n            dcid,\n            ..Default::default()\n        }\n    }\n\n    /// Creates a new PacketHeader.\n    ///\n    /// Once a QUIC connection has formed, version, dcid and scid are stable, so\n    /// there are space benefits to not logging them in every packet, especially\n    /// PacketType::OneRtt.\n    pub fn with_type(\n        ty: PacketType, packet_number: Option<u64>, version: Option<u32>,\n        scid: Option<&[u8]>, dcid: Option<&[u8]>,\n    ) -> Self {\n        match ty {\n            PacketType::OneRtt =>\n                PacketHeader::new(ty, packet_number, None, None, None, None, None),\n\n            _ => PacketHeader::new(\n                ty,\n                packet_number,\n                None,\n                None,\n                version,\n                scid,\n                dcid,\n            ),\n        }\n    }\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PacketNumberSpace {\n    Initial,\n    Handshake,\n    ApplicationData,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum StreamType {\n    Bidirectional,\n    Unidirectional,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum StreamTrigger {\n    Local,\n    Remote,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum StreamState {\n    Idle,\n    Open,\n    Closed,\n\n    HalfClosedLocal,\n    HalfClosedRemote,\n    Ready,\n    Send,\n    DataSent,\n    ResetSent,\n    ResetReceived,\n    Receive,\n    SizeKnown,\n    DataRead,\n    ResetRead,\n    DataReceived,\n    Destroyed,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum ErrorSpace {\n    Transport,\n    Application,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum TransportError {\n    NoError,\n    InternalError,\n    ConnectionRefused,\n    FlowControlError,\n    StreamLimitError,\n    StreamStateError,\n    FinalSizeError,\n    FrameEncodingError,\n    TransportParameterError,\n    ConnectionIdLimitError,\n    ProtocolViolation,\n    InvalidToken,\n    ApplicationError,\n    CryptoBufferExceeded,\n    KeyUpdateError,\n    AeadLimitReached,\n    NoViablePath,\n    Unknown,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum QuicEventType {\n    ServerListening,\n    ConnectionStarted,\n    ConnectionClosed,\n    ConnectionIdUpdated,\n    SpinBitUpdated,\n    ConnectionStateUpdated,\n    TupleAssigned,\n    MtuUpdated,\n\n    VersionInformation,\n    AlpnInformation,\n    ParametersSet,\n    ParametersRestored,\n    PacketSent,\n    PacketReceived,\n    PacketDropped,\n    PacketBuffered,\n    PacketsAcked,\n    UdpDatagramsSent,\n    UdpDatagramsReceived,\n    UdpDatagramDropped,\n    StreamStateUpdated,\n    FramesProcessed,\n    StreamDataMoved,\n    DatagramDataMoved,\n    ConnectionDataBlockedUpdated,\n    StreamDataBlockedUpdated,\n    DatagramDataBlockedUpdated,\n    MigrationStateUpdated,\n\n    KeyUpdated,\n    KeyDiscarded,\n\n    RecoveryParametersSet,\n    RecoveryMetricsUpdated,\n    CongestionStateUpdated,\n    TimerUpdated,\n    PacketLost,\n    MarkedForRetransmit,\n    EcnStateUpdated,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum TransportInitiator {\n    Local,\n    Remote,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum ConnectionState {\n    Attempted,\n    PeerValidated,\n    HandshakeStarted,\n    EarlyWrite,\n    HandshakeCompleted,\n    HandshakeConfirmed,\n    Closing,\n    Draining,\n    Closed,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum ConnectionClosedTrigger {\n    Clean,\n    HandshakeTimeout,\n    IdleTimeout,\n    Error,\n    StatelessReset,\n    VersionMismatch,\n    Application,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct ServerListening {\n    pub ip_v4: Option<String>, // human-readable or bytes\n    pub port_v4: Option<u16>,\n    pub ip_v6: Option<String>, // human-readable or bytes\n    pub port_v6: Option<u16>,\n\n    pub retry_required: Option<bool>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct ConnectionStarted {\n    pub local: TupleEndpointInfo,\n    pub remote: TupleEndpointInfo,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct ConnectionClosed {\n    pub initiator: Option<TransportInitiator>,\n\n    pub connection_error: Option<ConnectionClosedEventError>,\n    pub application_error: Option<ApplicationError>,\n    pub error_code: Option<u64>,\n    pub internal_code: Option<u64>,\n\n    pub reason: Option<String>,\n\n    pub trigger: Option<ConnectionClosedTrigger>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct ConnectionIdUpdated {\n    pub owner: Option<TransportInitiator>,\n\n    pub old: Option<Bytes>,\n    pub new: Option<Bytes>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct SpinBitUpdated {\n    pub state: bool,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct ConnectionStateUpdated {\n    pub old: Option<ConnectionState>,\n    pub new: ConnectionState,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct TupleAssigned {\n    pub tuple_id: String,\n    pub tuple_remote: Option<TupleEndpointInfo>,\n    pub tuple_local: Option<TupleEndpointInfo>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct MtuUpdated {\n    pub old: Option<u32>,\n    pub new: u32,\n    pub done: Option<bool>,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PacketSentTrigger {\n    RetransmitReordered,\n    RetransmitTimeout,\n    PtoProbe,\n    RetransmitCrypto,\n    CcBandwidthProbe,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PacketReceivedTrigger {\n    KeysUnavailable,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PacketDroppedTrigger {\n    InternalError,\n    Rejected,\n    Unsupported,\n    Invalid,\n    ConnectionUnknown,\n    DecryptionFailure,\n    General,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum PacketBufferedTrigger {\n    Backpressure,\n    KeysUnavailable,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(untagged)]\npub enum AckedRanges {\n    Single(Vec<Vec<u64>>),\n    Double(Vec<(u64, u64)>),\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum QuicFrameTypeName {\n    Padding,\n    Ping,\n    Ack,\n    ResetStream,\n    StopSending,\n    Crypto,\n    NewToken,\n    Stream,\n    MaxData,\n    MaxStreamData,\n    MaxStreams,\n    DataBlocked,\n    StreamDataBlocked,\n    StreamsBlocked,\n    NewConnectionId,\n    RetireConnectionId,\n    PathChallenge,\n    PathResponse,\n    ConnectionClose,\n    ApplicationClose,\n    HandshakeDone,\n    Datagram,\n    #[default]\n    Unknown,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]\n#[serde(tag = \"frame_type\")]\n#[serde(rename_all = \"snake_case\")]\n// Strictly, the qlog spec says that all these frame types have a frame_type\n// field. But instead of making that a rust object property, just use serde to\n// ensure it goes out on the wire. This means that deserialization of frames\n// also works automatically.\npub enum QuicFrame {\n    Padding {\n        raw: Option<RawInfo>,\n    },\n\n    Ping {\n        raw: Option<RawInfo>,\n    },\n\n    Ack {\n        ack_delay: Option<f32>,\n        acked_ranges: Option<AckedRanges>,\n\n        ect1: Option<u64>,\n        ect0: Option<u64>,\n        ce: Option<u64>,\n\n        raw: Option<RawInfo>,\n    },\n\n    ResetStream {\n        stream_id: u64,\n        error: ApplicationError,\n        error_code: Option<u64>,\n        final_size: u64,\n\n        raw: Option<RawInfo>,\n    },\n\n    StopSending {\n        stream_id: u64,\n        error: ApplicationError,\n        error_code: Option<u64>,\n\n        raw: Option<RawInfo>,\n    },\n\n    Crypto {\n        offset: u64,\n        raw: Option<RawInfo>,\n    },\n\n    NewToken {\n        token: Token,\n        raw: Option<RawInfo>,\n    },\n\n    Stream {\n        stream_id: u64,\n        offset: Option<u64>,\n        fin: Option<bool>,\n\n        raw: Option<RawInfo>,\n    },\n\n    MaxData {\n        maximum: u64,\n        raw: Option<RawInfo>,\n    },\n\n    MaxStreamData {\n        stream_id: u64,\n        maximum: u64,\n        raw: Option<RawInfo>,\n    },\n\n    MaxStreams {\n        stream_type: StreamType,\n        maximum: u64,\n        raw: Option<RawInfo>,\n    },\n\n    DataBlocked {\n        limit: u64,\n        raw: Option<RawInfo>,\n    },\n\n    StreamDataBlocked {\n        stream_id: u64,\n        limit: u64,\n        raw: Option<RawInfo>,\n    },\n\n    StreamsBlocked {\n        stream_type: StreamType,\n        limit: u64,\n        raw: Option<RawInfo>,\n    },\n\n    NewConnectionId {\n        sequence_number: u64,\n        retire_prior_to: u64,\n        connection_id_length: Option<u8>,\n        connection_id: Bytes,\n        stateless_reset_token: Option<StatelessResetToken>,\n        raw: Option<RawInfo>,\n    },\n\n    RetireConnectionId {\n        sequence_number: u64,\n        raw: Option<RawInfo>,\n    },\n\n    PathChallenge {\n        data: Option<Bytes>,\n        raw: Option<RawInfo>,\n    },\n\n    PathResponse {\n        data: Option<Bytes>,\n        raw: Option<RawInfo>,\n    },\n\n    ConnectionClose {\n        error_space: Option<ErrorSpace>,\n        error: Option<ConnectionClosedFrameError>,\n        error_code: Option<u64>,\n        reason: Option<String>,\n        reason_bytes: Option<Bytes>,\n\n        trigger_frame_type: Option<u64>,\n    },\n\n    HandshakeDone {\n        raw: Option<RawInfo>,\n    },\n\n    Datagram {\n        raw: Option<RawInfo>,\n    },\n\n    Unknown {\n        frame_type_bytes: Option<u64>,\n        raw: Option<RawInfo>,\n    },\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct PreferredAddress {\n    pub ip_v4: String,\n    pub ip_v6: String,\n\n    pub port_v4: u16,\n    pub port_v6: u16,\n\n    pub connection_id: Bytes,\n    pub stateless_reset_token: StatelessResetToken,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct AlpnIdentifier {\n    pub byte_value: Option<Bytes>,\n    pub string_value: Option<String>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct QuicVersionInformation {\n    pub server_versions: Option<Vec<Bytes>>,\n    pub client_versions: Option<Vec<Bytes>>,\n    pub chosen_version: Option<Bytes>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct AlpnInformation {\n    pub server_alpns: Option<Vec<AlpnIdentifier>>,\n    pub client_alpns: Option<Vec<AlpnIdentifier>>,\n    pub chosen_alpn: Option<AlpnIdentifier>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct ParametersSet {\n    pub initiator: Option<TransportInitiator>,\n\n    pub resumption_allowed: Option<bool>,\n    pub early_data_enabled: Option<bool>,\n    pub tls_cipher: Option<String>,\n\n    pub original_destination_connection_id: Option<Bytes>,\n    pub initial_source_connection_id: Option<Bytes>,\n    pub retry_source_connection_id: Option<Bytes>,\n    pub stateless_reset_token: Option<StatelessResetToken>,\n    pub disable_active_migration: Option<bool>,\n\n    pub max_idle_timeout: Option<u64>,\n    pub max_udp_payload_size: Option<u64>,\n    pub ack_delay_exponent: Option<u64>,\n    pub max_ack_delay: Option<u64>,\n    pub active_connection_id_limit: Option<u64>,\n\n    pub initial_max_data: Option<u64>,\n    pub initial_max_stream_data_bidi_local: Option<u64>,\n    pub initial_max_stream_data_bidi_remote: Option<u64>,\n    pub initial_max_stream_data_uni: Option<u64>,\n    pub initial_max_streams_bidi: Option<u64>,\n    pub initial_max_streams_uni: Option<u64>,\n\n    pub preferred_address: Option<PreferredAddress>,\n\n    pub unknown_parameters: Vec<UnknownTransportParameter>,\n\n    pub max_datagram_frame_size: Option<u64>,\n    pub grease_quic_bit: Option<bool>,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct UnknownTransportParameter {\n    pub id: u64,\n    pub value: Bytes,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct ParametersRestored {\n    pub disable_active_migration: Option<bool>,\n\n    pub max_idle_timeout: Option<u64>,\n    pub max_udp_payload_size: Option<u64>,\n    pub active_connection_id_limit: Option<u64>,\n\n    pub initial_max_data: Option<u64>,\n    pub initial_max_stream_data_bidi_local: Option<u64>,\n    pub initial_max_stream_data_bidi_remote: Option<u64>,\n    pub initial_max_stream_data_uni: Option<u64>,\n    pub initial_max_streams_bidi: Option<u64>,\n    pub initial_max_streams_uni: Option<u64>,\n\n    pub max_datagram_frame_size: Option<u64>,\n    pub grease_quic_bit: Option<bool>,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub enum Ecn {\n    #[serde(rename = \"Not-ECT\")]\n    NotEct,\n    #[serde(rename = \"ECT(1)\")]\n    Ect1,\n    #[serde(rename = \"ECT(0)\")]\n    Ect0,\n    #[serde(rename = \"CE\")]\n    CE,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct UdpDatagramsReceived {\n    pub count: Option<u16>,\n    pub raw: Option<Vec<RawInfo>>,\n    pub ecn: Option<Ecn>,\n    pub datagram_ids: Option<Vec<u32>>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct UdpDatagramsSent {\n    pub count: Option<u16>,\n    pub raw: Option<Vec<RawInfo>>,\n    pub ecn: Option<Ecn>,\n    pub datagram_ids: Option<Vec<u32>>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct UdpDatagramDropped {\n    pub raw: Option<RawInfo>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]\npub struct PacketReceived {\n    pub header: PacketHeader,\n    // `frames` is defined here in the QLog schema specification. However,\n    // our streaming serializer requires serde to put the object at the end,\n    // so we define it there and depend on serde's preserve_order feature.\n    pub stateless_reset_token: Option<StatelessResetToken>,\n\n    pub supported_versions: Option<Vec<Bytes>>,\n\n    pub raw: Option<RawInfo>,\n    pub datagram_id: Option<u32>,\n\n    pub trigger: Option<PacketReceivedTrigger>,\n\n    pub frames: Option<Vec<QuicFrame>>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]\npub struct PacketSent {\n    pub header: PacketHeader,\n    // `frames` is defined here in the QLog schema specification. However,\n    // our streaming serializer requires serde to put the object at the end,\n    // so we define it there and depend on serde's preserve_order feature.\n    pub stateless_reset_token: Option<StatelessResetToken>,\n\n    pub supported_versions: Option<Vec<Bytes>>,\n\n    pub raw: Option<RawInfo>,\n    pub datagram_id: Option<u32>,\n    pub is_mtu_probe_packet: Option<bool>,\n\n    pub trigger: Option<PacketSentTrigger>,\n\n    pub send_at_time: Option<f64>,\n\n    pub frames: Option<SmallVec<[QuicFrame; 1]>>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct PacketDropped {\n    pub header: Option<PacketHeader>,\n\n    pub raw: Option<RawInfo>,\n    pub datagram_id: Option<u32>,\n\n    pub details: Option<String>,\n\n    pub trigger: Option<PacketDroppedTrigger>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct PacketBuffered {\n    pub header: Option<PacketHeader>,\n\n    pub raw: Option<RawInfo>,\n    pub datagram_id: Option<u32>,\n\n    pub trigger: Option<PacketBufferedTrigger>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct PacketsAcked {\n    pub packet_number_space: Option<PacketNumberSpace>,\n    pub packet_numbers: Option<Vec<u64>>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct StreamStateUpdated {\n    pub stream_id: u64,\n    pub stream_type: Option<StreamType>,\n\n    pub old: Option<StreamState>,\n    pub new: StreamState,\n\n    pub trigger: Option<StreamTrigger>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]\npub struct FramesProcessed {\n    pub frames: Vec<QuicFrame>,\n\n    pub packet_numbers: Option<u64>,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum DataMovedAdditionalInfo {\n    FinSet,\n    StreamReset,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct StreamDataMoved {\n    pub stream_id: Option<u64>,\n    pub offset: Option<u64>,\n    pub from: Option<DataRecipient>,\n    pub to: Option<DataRecipient>,\n    pub additional_info: Option<DataMovedAdditionalInfo>,\n    pub raw: Option<RawInfo>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct DatagramDataMoved {\n    pub from: Option<DataRecipient>,\n    pub to: Option<DataRecipient>,\n    pub raw: Option<RawInfo>,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum BlockedState {\n    Blocked,\n    Unblocked,\n    #[default]\n    Unknown,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum BlockedReason {\n    Scheduled,\n    Pacing,\n    AmplificationProtection,\n    CongestionControl,\n    ConnectionFlowControl,\n    StreamFlowControl,\n    StreamId,\n    Application,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct ConnectionDataBlockedUpdated {\n    pub old: Option<BlockedState>,\n    pub new: BlockedState,\n    pub reason: Option<BlockedReason>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct StreamDataBlockedUpdated {\n    pub old: Option<BlockedState>,\n    pub new: BlockedState,\n    pub stream_id: u64,\n    pub reason: Option<BlockedReason>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct DatagramDataBlockedUpdated {\n    pub old: Option<BlockedState>,\n    pub new: BlockedState,\n    pub reason: Option<BlockedReason>,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum MigrationState {\n    ProbingStarted,\n    ProbingAbandoned,\n    ProbingSuccessful,\n    MigrationStarted,\n    MigrationAbandoned,\n    MigrationComplete,\n    #[default]\n    Unknown,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct MigrationStateUpdated {\n    pub old: Option<MigrationState>,\n    pub new: MigrationState,\n\n    pub tuple_id: Option<String>,\n\n    pub tuple_remote: Option<TupleEndpointInfo>,\n    pub tuple_local: Option<TupleEndpointInfo>,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum CongestionStateUpdatedTrigger {\n    PersistentCongestion,\n    Ecn,\n    #[default]\n    Unknown,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum TimerType {\n    Ack,\n    Pto,\n    LossTimeout,\n    PathValidation,\n    HandshakeTimeout,\n    IdleTimeout,\n    #[default]\n    Unknown,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum PacketLostTrigger {\n    ReorderingThreshold,\n    TimeThreshold,\n    PtoExpired,\n    #[default]\n    Unknown,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum TimerEventType {\n    Set,\n    Expired,\n    Cancelled,\n    #[default]\n    Unknown,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]\npub struct RecoveryParametersSet {\n    pub reordering_threshold: Option<u16>,\n    pub time_threshold: Option<f32>,\n    pub timer_granularity: Option<u16>,\n    pub initial_rtt: Option<f32>,\n\n    pub max_datagram_size: Option<u32>,\n    pub initial_congestion_window: Option<u64>,\n    pub minimum_congestion_window: Option<u32>,\n    pub loss_reduction_factor: Option<f32>,\n    pub persistent_congestion_threshold: Option<u16>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]\npub struct RecoveryMetricsUpdated {\n    /// Extension data for non-standard fields. `flatten` causes these fields to\n    /// be serialized into the `data` field of a qlog event. On deserialize,\n    /// unknown fields are collected into `ex_data`.\n    #[serde(flatten)]\n    pub ex_data: ExData,\n\n    pub min_rtt: Option<f32>,\n    pub smoothed_rtt: Option<f32>,\n    pub latest_rtt: Option<f32>,\n    pub rtt_variance: Option<f32>,\n\n    pub pto_count: Option<u16>,\n\n    pub congestion_window: Option<u64>,\n    pub bytes_in_flight: Option<u64>,\n\n    pub ssthresh: Option<u64>,\n\n    // qlog defined\n    pub packets_in_flight: Option<u64>,\n\n    pub pacing_rate: Option<u64>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct CongestionStateUpdated {\n    pub old: Option<String>,\n    pub new: String,\n\n    pub trigger: Option<CongestionStateUpdatedTrigger>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]\npub struct TimerUpdated {\n    pub timer_type: Option<TimerType>,\n    pub timer_id: Option<u64>,\n    pub packet_number_space: Option<PacketNumberSpace>,\n    pub event_type: TimerEventType,\n    pub delta: Option<f32>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]\npub struct PacketLost {\n    pub header: Option<PacketHeader>,\n\n    pub frames: Option<Vec<QuicFrame>>,\n    pub is_mtu_probe_packet: Option<bool>,\n\n    pub trigger: Option<PacketLostTrigger>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]\npub struct MarkedForRetransmit {\n    pub frames: Vec<QuicFrame>,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum EcnState {\n    Testing,\n    #[default]\n    Unknown,\n    Failed,\n    Capable,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]\npub struct EcnStateUpdated {\n    pub old: Option<EcnState>,\n    pub new: EcnState,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\n#[serde(rename_all = \"snake_case\")]\npub enum KeyType {\n    ServerInitialSecret,\n    ClientInitialSecret,\n\n    ServerHandshakeSecret,\n    ClientHandshakeSecret,\n\n    #[serde(rename = \"server_0rtt_secret\")]\n    Server0RttSecret,\n    #[serde(rename = \"client_0rtt_secret\")]\n    Client0RttSecret,\n    #[serde(rename = \"server_1rtt_secret\")]\n    Server1RttSecret,\n    #[serde(rename = \"client_1rtt_secret\")]\n    Client1RttSecret,\n    #[default]\n    Unknown,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum KeyUpdateOrRetiredTrigger {\n    Tls,\n    RemoteUpdate,\n    LocalUpdate,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Default)]\npub struct KeyUpdated {\n    pub key_type: KeyType,\n\n    pub old: Option<Bytes>,\n    pub new: Option<Bytes>,\n\n    pub key_phase: Option<u64>,\n\n    pub trigger: Option<KeyUpdateOrRetiredTrigger>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\npub struct KeyDiscarded {\n    pub key_type: KeyType,\n    pub key: Option<Bytes>,\n\n    pub key_phase: Option<u64>,\n\n    pub trigger: Option<KeyUpdateOrRetiredTrigger>,\n}\n"
  },
  {
    "path": "qlog/src/lib.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! The qlog crate is an implementation of the qlog [main logging schema],\n//! [QUIC event definitions], and [HTTP/3 and QPACK event definitions].\n//! The crate provides a qlog data model that can be used for traces with\n//! events. It supports serialization and deserialization but defers logging IO\n//! choices to applications.\n//!\n//! Serialization operates in either a [buffered mode] or a [streaming mode].\n//!\n//! The crate uses Serde for conversion between Rust and JSON.\n//!\n//! [main logging schema]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema\n//! [QUIC event definitions]:\n//! https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-quic-events.html\n//! [HTTP/3 and QPACK event definitions]:\n//! https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-h3-events.html\n//! [buffered mode]: #buffered-traces-with-standard-json\n//! [streaming mode]: #streaming-traces-with-json-seq\n//!\n//! Overview\n//! ---------------\n//! qlog is a hierarchical logging format, with a rough structure of:\n//!\n//! * Log\n//!   * Trace(s)\n//!     * Event(s)\n//!\n//! In practice, a single QUIC connection maps to a single Trace file with one\n//! or more Events. Applications can decide whether to combine Traces from\n//! different connections into the same Log.\n//!\n//! ## Buffered Traces with standard JSON\n//!\n//! A [`Trace`] is a single JSON object. It contains metadata such as the\n//! [`VantagePoint`] of capture and the [`Configuration`], and protocol event\n//! data in the [`Event`] array.\n//!\n//! JSON Traces allow applications to appends events to them before eventually\n//! being serialized as a complete JSON object.\n//!\n//! ### Creating a Trace\n//!\n//! ```\n//! let mut trace = qlog::Trace::new(\n//! #    Some(\"Example qlog trace\".to_string()),\n//! #    Some(\"Example qlog trace description\".to_string()),\n//! #    None,\n//! #    Some(qlog::VantagePoint {\n//! #        name: Some(\"Example client\".to_string()),\n//! #        ty: qlog::VantagePointType::Client,\n//! #        flow: None,\n//! #    }),\n//! #    vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],\n//! # );\n//! ```\n//!\n//! ### Adding events to a Trace\n//!\n//! Qlog [`Event`] objects are added to [`qlog::Trace.events`].\n//!\n//! The following example demonstrates how to log a qlog QUIC `packet_sent`\n//! event containing a single Crypto frame. It constructs the necessary elements\n//! of the [`Event`], then appends it to the trace with [`push_event()`].\n//!\n//! ```\n//! # let mut trace = qlog::Trace::new(\n//! #    Some(\"Example qlog trace\".to_string()),\n//! #    Some(\"Example qlog trace description\".to_string()),\n//! #    None,\n//! #    Some(qlog::VantagePoint {\n//! #        name: Some(\"Example client\".to_string()),\n//! #        ty: qlog::VantagePointType::Client,\n//! #        flow: None,\n//! #    }),\n//! #    vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],\n//! # );\n//!\n//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];\n//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];\n//!\n//! let pkt_hdr = qlog::events::quic::PacketHeader::new(\n//!     qlog::events::quic::PacketType::Initial,\n//!     Some(0),          // packet_number\n//!     None,             // token\n//!     None,             // length\n//!     Some(0x00000001), // version\n//!     Some(&scid),\n//!     Some(&dcid),\n//! );\n//!\n//! let frames = vec![qlog::events::quic::QuicFrame::Crypto {\n//!     offset: 0,\n//!     raw: None,\n//! }];\n//!\n//! let raw = qlog::events::RawInfo {\n//!     length: Some(1251),\n//!     payload_length: Some(1224),\n//!     data: None,\n//! };\n//!\n//! let event_data =\n//!     qlog::events::EventData::QuicPacketSent(qlog::events::quic::PacketSent {\n//!         header: pkt_hdr,\n//!         frames: Some(frames.into()),\n//!         stateless_reset_token: None,\n//!         supported_versions: None,\n//!         raw: Some(raw),\n//!         datagram_id: None,\n//!         is_mtu_probe_packet: None,\n//!         send_at_time: None,\n//!         trigger: None,\n//!     });\n//!\n//! trace.push_event(qlog::events::Event::with_time(0.0, event_data));\n//! ```\n//!\n//! ### Serializing\n//!\n//! The qlog crate has only been tested with `serde_json`, however\n//! other serializer targets might work.\n//!\n//! For example, serializing the trace created above:\n//!\n//! ```\n//! # let mut trace = qlog::Trace::new(\n//! #    Some(\"Example qlog trace\".to_string()),\n//! #    Some(\"Example qlog trace description\".to_string()),\n//! #    None,\n//! #    Some(qlog::VantagePoint {\n//! #        name: Some(\"Example client\".to_string()),\n//! #        ty: qlog::VantagePointType::Client,\n//! #        flow: None,\n//! #    }),\n//! #    vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],\n//! # );\n//! serde_json::to_string_pretty(&trace).unwrap();\n//! ```\n//!\n//! which would generate the following:\n//!\n//! ```ignore\n//! {\n//!   \"vantage_point\": {\n//!     \"name\": \"Example client\",\n//!     \"type\": \"client\"\n//!   },\n//!   \"title\": \"Example qlog trace\",\n//!   \"description\": \"Example qlog trace description\",\n//!   \"configuration\": {\n//!     \"time_offset\": 0.0\n//!   },\n//!   \"events\": [\n//!     {\n//!       \"time\": 0.0,\n//!       \"name\": \"quic:packet_sent\",\n//!       \"data\": {\n//!         \"header\": {\n//!           \"packet_type\": \"initial\",\n//!           \"packet_number\": 0,\n//!           \"version\": \"1\",\n//!           \"scil\": 8,\n//!           \"dcil\": 8,\n//!           \"scid\": \"7e37e4dcc6682da8\",\n//!           \"dcid\": \"36ce104eee50101c\"\n//!         },\n//!         \"raw\": {\n//!           \"length\": 1251,\n//!           \"payload_length\": 1224\n//!         },\n//!         \"frames\": [\n//!           {\n//!             \"frame_type\": \"crypto\",\n//!             \"offset\": 0,\n//!             \"length\": 0\n//!           }\n//!         ]\n//!       }\n//!     }\n//!   ]\n//! }\n//! ```\n//!\n//! ## Streaming Traces with JSON-SEQ\n//!\n//! To help support streaming serialization of qlogs,\n//! draft-ietf-quic-qlog-main-schema-01 introduced support for RFC 7464 JSON\n//! Text Sequences (JSON-SEQ). The qlog crate supports this format and provides\n//! utilities that aid streaming.\n//!\n//! A [`TraceSeq`] contains metadata such as the [`VantagePoint`] of capture and\n//! the [`Configuration`]. However, protocol event data is handled as separate\n//! lines containing a record separator character, a serialized [`Event`], and a\n//! newline.\n//!\n//! ### Creating a TraceSeq\n//!\n//! ```\n//! let mut trace = qlog::TraceSeq::new(\n//!     Some(\"Example qlog trace\".to_string()),\n//!     Some(\"Example qlog trace description\".to_string()),\n//!     None,\n//!     Some(qlog::VantagePoint {\n//!         name: Some(\"Example client\".to_string()),\n//!         ty: qlog::VantagePointType::Client,\n//!         flow: None,\n//!     }),\n//!     vec![\n//!         qlog::events::QUIC_URI.to_string(),\n//!         qlog::events::HTTP3_URI.to_string(),\n//!     ],\n//! );\n//! ```\n//!\n//! Create an object with the [`Write`] trait:\n//!\n//! ```\n//! let mut file = std::fs::File::create(\"foo.sqlog\").unwrap();\n//! ```\n//!\n//! Create a [`QlogStreamer`] and start serialization to foo.sqlog\n//! using [`start_log()`]:\n//!\n//! ```\n//! # let mut trace = qlog::TraceSeq::new(\n//! #    Some(\"Example qlog trace\".to_string()),\n//! #    Some(\"Example qlog trace description\".to_string()),\n//! #    None,\n//! #    Some(qlog::VantagePoint {\n//! #        name: Some(\"Example client\".to_string()),\n//! #        ty: qlog::VantagePointType::Client,\n//! #        flow: None,\n//! #    }),\n//! #    vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],\n//! # );\n//! # let mut file = std::fs::File::create(\"foo.sqlog\").unwrap();\n//! let mut streamer = qlog::streamer::QlogStreamer::new(\n//!     Some(\"Example qlog\".to_string()),\n//!     Some(\"Example qlog description\".to_string()),\n//!     std::time::Instant::now(),\n//!     trace,\n//!     qlog::events::EventImportance::Base,\n//!     Box::new(file),\n//! );\n//!\n//! streamer.start_log().ok();\n//! ```\n//!\n//! ### Adding events\n//!\n//! Once logging has started you can stream events. Events\n//! are written in one step using one of [`add_event()`],\n//! [`add_event_with_instant()`], [`add_event_now()`],\n//! [`add_event_data_with_instant()`], or [`add_event_data_now()`] :\n//!\n//! ```\n//! # let mut trace = qlog::TraceSeq::new(\n//! #    Some(\"Example qlog trace\".to_string()),\n//! #    Some(\"Example qlog trace description\".to_string()),\n//! #    None,\n//! #    Some(qlog::VantagePoint {\n//! #        name: Some(\"Example client\".to_string()),\n//! #        ty: qlog::VantagePointType::Client,\n//! #        flow: None,\n//! #    }),\n//! #    vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],\n//! # );\n//! # let mut file = std::fs::File::create(\"foo.qlog\").unwrap();\n//! # let mut streamer = qlog::streamer::QlogStreamer::new(\n//! #     Some(\"Example qlog\".to_string()),\n//! #     Some(\"Example qlog description\".to_string()),\n//! #     std::time::Instant::now(),\n//! #     trace,\n//! #     qlog::events::EventImportance::Base,\n//! #     Box::new(file),\n//! # );\n//!\n//! let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];\n//! let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];\n//!\n//! let pkt_hdr = qlog::events::quic::PacketHeader::with_type(\n//!     qlog::events::quic::PacketType::OneRtt,\n//!     Some(0),\n//!     Some(0x00000001),\n//!     Some(&scid),\n//!     Some(&dcid),\n//! );\n//!\n//! let ping = qlog::events::quic::QuicFrame::Ping {\n//!     raw: None,\n//! };\n//!\n//! let raw = qlog::events::RawInfo {\n//!             length: None,\n//!             payload_length:\n//!             Some(1234), data: None\n//!           };\n//! let padding = qlog::events::quic::QuicFrame::Padding {\n//!     raw: Some(raw),\n//! };\n//!\n//! let event_data =\n//!     qlog::events::EventData::QuicPacketSent(qlog::events::quic::PacketSent {\n//!         header: pkt_hdr,\n//!         frames: Some(vec![ping, padding].into()),\n//!         stateless_reset_token: None,\n//!         supported_versions: None,\n//!         raw: None,\n//!         datagram_id: None,\n//!         is_mtu_probe_packet: None,\n//!         send_at_time: None,\n//!         trigger: None,\n//!     });\n//!\n//! let event = qlog::events::Event::with_time(0.0, event_data);\n//!\n//! streamer.add_event(event).ok();\n//! ```\n//!\n//! Once all events have been written, the log\n//! can be finalized with [`finish_log()`]:\n//!\n//! ```\n//! # let mut trace = qlog::TraceSeq::new(\n//! #    Some(\"Example qlog trace\".to_string()),\n//! #    Some(\"Example qlog trace description\".to_string()),\n//! #    None,\n//! #    Some(qlog::VantagePoint {\n//! #        name: Some(\"Example client\".to_string()),\n//! #        ty: qlog::VantagePointType::Client,\n//! #        flow: None,\n//! #    }),\n//! #    vec![qlog::events::QUIC_URI.to_string(), qlog::events::HTTP3_URI.to_string()],\n//! # );\n//! # let mut file = std::fs::File::create(\"foo.qlog\").unwrap();\n//! # let mut streamer = qlog::streamer::QlogStreamer::new(\n//! #     Some(\"Example qlog\".to_string()),\n//! #     Some(\"Example qlog description\".to_string()),\n//! #     std::time::Instant::now(),\n//! #     trace,\n//! #     qlog::events::EventImportance::Base,\n//! #     Box::new(file),\n//! # );\n//! streamer.finish_log().ok();\n//! ```\n//!\n//! ### Serializing\n//!\n//! Serialization to JSON occurs as methods on the [`QlogStreamer`]\n//! are called. No additional steps are required.\n//!\n//! [`Trace`]: struct.Trace.html\n//! [`TraceSeq`]: struct.TraceSeq.html\n//! [`VantagePoint`]: struct.VantagePoint.html\n//! [`Configuration`]: struct.Configuration.html\n//! [`qlog::Trace.events`]: struct.Trace.html#structfield.events\n//! [`push_event()`]: struct.Trace.html#method.push_event\n//! [`QlogStreamer`]: struct.QlogStreamer.html\n//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html\n//! [`start_log()`]: streamer/struct.QlogStreamer.html#method.start_log\n//! [`add_event()`]: streamer/struct.QlogStreamer.html#method.add_event\n//! [`add_event_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_with_instant\n//! [`add_event_now()`]: streamer/struct.QlogStreamer.html#method.add_event_now\n//! [`add_event_data_with_instant()`]: streamer/struct.QlogStreamer.html#method.add_event_data_with_instant\n//! [`add_event_data_now()`]: streamer/struct.QlogStreamer.html#method.add_event_data_now\n//! [`finish_log()`]: streamer/struct.QlogStreamer.html#method.finish_log\n\nuse crate::events::quic::PacketHeader;\nuse crate::events::Event;\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\n/// A quiche qlog error.\n#[derive(Debug)]\npub enum Error {\n    /// There is no more work to do.\n    Done,\n\n    /// The operation cannot be completed because it was attempted\n    /// in an invalid state.\n    InvalidState,\n\n    // Invalid Qlog format\n    InvalidFormat,\n\n    /// I/O error.\n    IoError(std::io::Error),\n}\n\nimpl std::fmt::Display for Error {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl std::error::Error for Error {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        None\n    }\n}\n\nimpl std::convert::From<std::io::Error> for Error {\n    fn from(err: std::io::Error) -> Self {\n        Error::IoError(err)\n    }\n}\n\npub const QLOGFILE_URI: &str = \"urn:ietf:params:qlog:file:contained\";\npub const QLOGFILESEQ_URI: &str = \"urn:ietf:params:qlog:file:sequential\";\n\npub type Bytes = String;\npub type StatelessResetToken = Bytes;\n\n/// A specialized [`Result`] type for quiche qlog operations.\n///\n/// This type is used throughout the public API for any operation that\n/// can produce an error.\n///\n/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html\npub type Result<T> = std::result::Result<T, Error>;\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone)]\npub struct Qlog {\n    pub file_schema: String,\n    pub serialization_format: String,\n    pub title: Option<String>,\n    pub description: Option<String>,\n\n    pub traces: Vec<Trace>,\n}\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, Debug)]\npub struct QlogSeq {\n    pub file_schema: String,\n    pub serialization_format: String,\n    pub title: Option<String>,\n    pub description: Option<String>,\n\n    pub trace: TraceSeq,\n}\n\n#[derive(Clone, Copy)]\npub enum ImportanceLogLevel {\n    Core  = 0,\n    Base  = 1,\n    Extra = 2,\n}\n\n// We now commence data definitions heavily styled on the QLOG\n// schema definition. Data is serialized using serde.\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]\npub struct Trace {\n    pub title: Option<String>,\n    pub description: Option<String>,\n    pub common_fields: Option<CommonFields>,\n    pub vantage_point: Option<VantagePoint>,\n    pub event_schemas: Vec<String>,\n\n    pub events: Vec<Event>,\n}\n\n/// Helper functions for using a qlog [Trace].\nimpl Trace {\n    /// Creates a new qlog [Trace]\n    pub fn new(\n        title: Option<String>, description: Option<String>,\n        common_fields: Option<CommonFields>, vantage_point: Option<VantagePoint>,\n        event_schemas: Vec<String>,\n    ) -> Self {\n        Trace {\n            title,\n            description,\n            common_fields,\n            vantage_point,\n            event_schemas,\n            events: Vec::new(),\n        }\n    }\n\n    /// Append an [Event] to a [Trace]\n    pub fn push_event(&mut self, event: Event) {\n        self.events.push(event);\n    }\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]\npub struct TraceSeq {\n    pub title: Option<String>,\n    pub description: Option<String>,\n    pub common_fields: Option<CommonFields>,\n    pub vantage_point: Option<VantagePoint>,\n    pub event_schemas: Vec<String>,\n}\n\n/// Helper functions for using a qlog [TraceSeq].\nimpl TraceSeq {\n    /// Creates a new qlog [TraceSeq]\n    pub fn new(\n        title: Option<String>, description: Option<String>,\n        common_fields: Option<CommonFields>, vantage_point: Option<VantagePoint>,\n        event_schemas: Vec<String>,\n    ) -> Self {\n        TraceSeq {\n            title,\n            description,\n            common_fields,\n            vantage_point,\n            event_schemas,\n        }\n    }\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]\npub struct VantagePoint {\n    pub name: Option<String>,\n\n    #[serde(rename = \"type\")]\n    pub ty: VantagePointType,\n\n    pub flow: Option<VantagePointType>,\n}\n\n#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum VantagePointType {\n    Client,\n    Server,\n    Network,\n    #[default]\n    Unknown,\n}\n\n#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub struct ReferenceTime {\n    pub clock_type: String,\n    pub epoch: String,\n    pub wall_clock_time: Option<String>,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Debug)]\npub struct CommonFields {\n    pub tuple: Option<String>,\n    pub group_id: Option<String>,\n    pub protocol_types: Option<Vec<String>>,\n\n    pub reference_time: ReferenceTime,\n    pub time_format: Option<String>,\n}\n\n#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]\n#[serde(rename_all = \"snake_case\")]\npub enum TokenType {\n    Retry,\n    Resumption,\n}\n\n#[serde_with::skip_serializing_none]\n#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]\npub struct Token {\n    #[serde(rename(serialize = \"type\"))]\n    pub ty: Option<TokenType>,\n\n    pub details: Option<String>,\n\n    pub raw: Option<events::RawInfo>,\n}\n\npub struct HexSlice<'a>(&'a [u8]);\n\nimpl<'a> HexSlice<'a> {\n    pub fn new<T>(data: &'a T) -> HexSlice<'a>\n    where\n        T: ?Sized + AsRef<[u8]> + 'a,\n    {\n        HexSlice(data.as_ref())\n    }\n\n    pub fn maybe_string<T>(data: Option<&'a T>) -> Option<String>\n    where\n        T: ?Sized + AsRef<[u8]> + 'a,\n    {\n        data.map(|d| format!(\"{}\", HexSlice::new(d)))\n    }\n}\n\nimpl std::fmt::Display for HexSlice<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        for byte in self.0 {\n            write!(f, \"{byte:02x}\")?;\n        }\n        Ok(())\n    }\n}\n\npub mod events;\npub mod reader;\npub mod streamer;\n#[doc(hidden)]\npub mod testing;\n"
  },
  {
    "path": "qlog/src/reader.rs",
    "content": "// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::QlogSeq;\n\n/// Represents the format of the read event.\n#[allow(clippy::large_enum_variant)]\n#[derive(Clone, Debug)]\npub enum Event {\n    /// A native qlog event type.\n    Qlog(crate::events::Event),\n\n    // An extended JSON event type.\n    Json(crate::events::JsonEvent),\n}\n\n/// A helper object specialized for reading JSON-SEQ qlog from a [`BufRead`]\n/// trait.\n///\n/// [`BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html\npub struct QlogSeqReader<'a> {\n    pub qlog: QlogSeq,\n    reader: Box<dyn std::io::BufRead + Send + Sync + 'a>,\n}\n\nimpl<'a> QlogSeqReader<'a> {\n    pub fn new(\n        mut reader: Box<dyn std::io::BufRead + Send + Sync + 'a>,\n    ) -> Result<Self, Box<dyn std::error::Error>> {\n        // \"null record\" skip it\n        Self::read_record(reader.as_mut());\n\n        let header = Self::read_record(reader.as_mut()).ok_or_else(|| {\n            std::io::Error::other(\"error reading file header bytes\")\n        })?;\n\n        let res: Result<QlogSeq, serde_json::Error> =\n            serde_json::from_slice(&header);\n        match res {\n            Ok(qlog) => Ok(Self { qlog, reader }),\n\n            Err(e) => Err(e.into()),\n        }\n    }\n\n    fn read_record(\n        reader: &mut (dyn std::io::BufRead + Send + Sync),\n    ) -> Option<Vec<u8>> {\n        let mut buf = Vec::<u8>::new();\n        let size = reader.read_until(b'\u001e', &mut buf).unwrap();\n        if size <= 1 {\n            return None;\n        }\n\n        buf.truncate(buf.len() - 1);\n\n        Some(buf)\n    }\n}\n\nimpl Iterator for QlogSeqReader<'_> {\n    type Item = Event;\n\n    #[inline]\n    fn next(&mut self) -> Option<Self::Item> {\n        // Attempt to deserialize events but skip them if that fails for any\n        // reason, ensuring we always read all bytes in the reader.\n        while let Some(bytes) = Self::read_record(&mut self.reader) {\n            let r: serde_json::Result<crate::events::Event> =\n                serde_json::from_slice(&bytes);\n\n            if let Ok(event) = r {\n                return Some(Event::Qlog(event));\n            }\n\n            let r: serde_json::Result<crate::events::JsonEvent> =\n                serde_json::from_slice(&bytes);\n\n            if let Ok(event) = r {\n                return Some(Event::Json(event));\n            }\n        }\n\n        None\n    }\n}\n"
  },
  {
    "path": "qlog/src/streamer.rs",
    "content": "// Copyright (C) 2021, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::events::EventData;\nuse crate::events::EventImportance;\nuse crate::events::EventType;\nuse crate::events::Eventable;\nuse crate::events::ExData;\n\n/// Multiplier for rounding qlog event time values to at most 6 decimal places.\n/// Since event times are in milliseconds, this provides microsecond precision.\nconst TIME_PRECISION_MULTIPLIER: f64 = 1e6;\n\n/// Computes elapsed time in milliseconds since `start`, rounded to 6 decimal\n/// places. In test builds, always returns 0.0 for deterministic output.\nfn elapsed_millis(start: std::time::Instant, now: std::time::Instant) -> f64 {\n    let dur = if cfg!(test) {\n        std::time::Duration::from_secs(0)\n    } else {\n        now.duration_since(start)\n    };\n    let ms = dur.as_secs_f64() * 1000.0;\n    (ms * TIME_PRECISION_MULTIPLIER).round() / TIME_PRECISION_MULTIPLIER\n}\n\n/// A helper object specialized for streaming JSON-serialized qlog to a\n/// [`Write`] trait.\n///\n/// The object is responsible for the `Qlog` object that contains the\n/// provided `Trace`.\n///\n/// Serialization is progressively driven by method calls; once log streaming\n/// is started, `event::Events` can be written using `add_event()`.\n///\n/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html\nuse super::*;\n\n#[derive(PartialEq, Eq, Debug)]\npub enum StreamerState {\n    Initial,\n    Ready,\n    Finished,\n}\n\npub struct QlogStreamer {\n    start_time: std::time::Instant,\n    writer: Box<dyn std::io::Write + Send + Sync>,\n    qlog: QlogSeq,\n    state: StreamerState,\n    log_level: EventImportance,\n}\n\nimpl QlogStreamer {\n    /// Creates a [QlogStreamer] object.\n    ///\n    /// It owns a [QlogSeq] object that contains the provided [TraceSeq]\n    /// containing [Event]s.\n    ///\n    /// All serialization will be written to the provided [`Write`] using the\n    /// JSON-SEQ format.\n    ///\n    /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html\n    #[allow(clippy::too_many_arguments)]\n    pub fn new(\n        title: Option<String>, description: Option<String>,\n        start_time: std::time::Instant, trace: TraceSeq,\n        log_level: EventImportance,\n        writer: Box<dyn std::io::Write + Send + Sync>,\n    ) -> Self {\n        let qlog = QlogSeq {\n            file_schema: QLOGFILESEQ_URI.to_string(),\n            serialization_format: \"JSON-SEQ\".to_string(),\n            title,\n            description,\n            trace,\n        };\n\n        QlogStreamer {\n            start_time,\n            writer,\n            qlog,\n            state: StreamerState::Initial,\n            log_level,\n        }\n    }\n\n    /// Starts qlog streaming serialization.\n    ///\n    /// This writes out the JSON-SEQ-serialized form of all initial qlog\n    /// information. [Event]s are separately appended using [add_event()],\n    /// [add_event_with_instant()], [add_event_now()],\n    /// [add_event_data_with_instant()], or [add_event_data_now()].\n    ///\n    /// [add_event()]: #method.add_event\n    /// [add_event_with_instant()]: #method.add_event_with_instant\n    /// [add_event_now()]: #method.add_event_now\n    /// [add_event_data_with_instant()]: #method.add_event_data_with_instant\n    /// [add_event_data_now()]: #method.add_event_data_now\n    pub fn start_log(&mut self) -> Result<()> {\n        if self.state != StreamerState::Initial {\n            return Err(Error::Done);\n        }\n\n        self.writer.as_mut().write_all(b\"\u001e\")?;\n        serde_json::to_writer(self.writer.as_mut(), &self.qlog)\n            .map_err(|_| Error::Done)?;\n        self.writer.as_mut().write_all(b\"\\n\")?;\n\n        self.state = StreamerState::Ready;\n\n        Ok(())\n    }\n\n    /// Finishes qlog streaming serialization.\n    ///\n    /// After this is called, no more serialization will occur.\n    pub fn finish_log(&mut self) -> Result<()> {\n        if self.state == StreamerState::Initial ||\n            self.state == StreamerState::Finished\n        {\n            return Err(Error::InvalidState);\n        }\n\n        self.state = StreamerState::Finished;\n\n        self.writer.as_mut().flush()?;\n\n        Ok(())\n    }\n\n    /// Writes a serializable to a JSON-SEQ record using\n    /// [std::time::Instant::now()].\n    pub fn add_event_now<E: Serialize + Eventable>(\n        &mut self, event: E,\n    ) -> Result<()> {\n        let now = std::time::Instant::now();\n\n        self.add_event_with_instant(event, now)\n    }\n\n    /// Writes a serializable to a pretty-printed JSON-SEQ record using\n    /// [std::time::Instant::now()].\n    pub fn add_event_now_pretty<E: Serialize + Eventable>(\n        &mut self, event: E,\n    ) -> Result<()> {\n        let now = std::time::Instant::now();\n\n        self.add_event_with_instant_pretty(event, now)\n    }\n\n    /// Writes a serializable to a JSON-SEQ record using the provided\n    /// [std::time::Instant].\n    pub fn add_event_with_instant<E: Serialize + Eventable>(\n        &mut self, event: E, now: std::time::Instant,\n    ) -> Result<()> {\n        self.event_with_instant(event, now, false)\n    }\n\n    /// Writes a serializable to a pretty-printed JSON-SEQ record using the\n    /// provided [std::time::Instant].\n    pub fn add_event_with_instant_pretty<E: Serialize + Eventable>(\n        &mut self, event: E, now: std::time::Instant,\n    ) -> Result<()> {\n        self.event_with_instant(event, now, true)\n    }\n\n    fn event_with_instant<E: Serialize + Eventable>(\n        &mut self, mut event: E, now: std::time::Instant, pretty: bool,\n    ) -> Result<()> {\n        if self.state != StreamerState::Ready {\n            return Err(Error::InvalidState);\n        }\n\n        if !event.importance().is_contained_in(&self.log_level) {\n            return Err(Error::Done);\n        }\n\n        event.set_time(elapsed_millis(self.start_time, now));\n\n        if pretty {\n            self.add_event_pretty(event)\n        } else {\n            self.add_event(event)\n        }\n    }\n\n    /// Writes an [Event] based on the provided [EventData] to a JSON-SEQ record\n    /// at time [std::time::Instant::now()].\n    pub fn add_event_data_now(&mut self, event_data: EventData) -> Result<()> {\n        self.add_event_data_ex_now(event_data, Default::default())\n    }\n\n    /// Writes an [Event] based on the provided [EventData] to a pretty-printed\n    /// JSON-SEQ record at time [std::time::Instant::now()].\n    pub fn add_event_data_now_pretty(\n        &mut self, event_data: EventData,\n    ) -> Result<()> {\n        self.add_event_data_ex_now_pretty(event_data, Default::default())\n    }\n\n    /// Writes an [Event] based on the provided [EventData] and [ExData] to a\n    /// JSON-SEQ record at time [std::time::Instant::now()].\n    pub fn add_event_data_ex_now(\n        &mut self, event_data: EventData, ex_data: ExData,\n    ) -> Result<()> {\n        let now = std::time::Instant::now();\n\n        self.add_event_data_ex_with_instant(event_data, ex_data, now)\n    }\n\n    /// Writes an [Event] based on the provided [EventData] and [ExData] to a\n    /// pretty-printed JSON-SEQ record at time [std::time::Instant::now()].\n    pub fn add_event_data_ex_now_pretty(\n        &mut self, event_data: EventData, ex_data: ExData,\n    ) -> Result<()> {\n        let now = std::time::Instant::now();\n\n        self.add_event_data_ex_with_instant_pretty(event_data, ex_data, now)\n    }\n\n    /// Writes an [Event] based on the provided [EventData] and\n    /// [std::time::Instant] to a JSON-SEQ record.\n    pub fn add_event_data_with_instant(\n        &mut self, event_data: EventData, now: std::time::Instant,\n    ) -> Result<()> {\n        self.add_event_data_ex_with_instant(event_data, Default::default(), now)\n    }\n\n    /// Writes an [Event] based on the provided [EventData] and\n    /// [std::time::Instant] to a pretty-printed JSON-SEQ record.\n    pub fn add_event_data_with_instant_pretty(\n        &mut self, event_data: EventData, now: std::time::Instant,\n    ) -> Result<()> {\n        self.add_event_data_ex_with_instant_pretty(\n            event_data,\n            Default::default(),\n            now,\n        )\n    }\n\n    /// Writes an [Event] based on the provided [EventData], [ExData], and\n    /// [std::time::Instant] to a JSON-SEQ record.\n    pub fn add_event_data_ex_with_instant(\n        &mut self, event_data: EventData, ex_data: ExData,\n        now: std::time::Instant,\n    ) -> Result<()> {\n        self.event_data_ex_with_instant(event_data, ex_data, now, false)\n    }\n\n    // Writes an [Event] based on the provided [EventData], [ExData], and\n    /// [std::time::Instant] to a pretty-printed JSON-SEQ record.\n    pub fn add_event_data_ex_with_instant_pretty(\n        &mut self, event_data: EventData, ex_data: ExData,\n        now: std::time::Instant,\n    ) -> Result<()> {\n        self.event_data_ex_with_instant(event_data, ex_data, now, true)\n    }\n\n    fn event_data_ex_with_instant(\n        &mut self, event_data: EventData, ex_data: ExData,\n        now: std::time::Instant, pretty: bool,\n    ) -> Result<()> {\n        if self.state != StreamerState::Ready {\n            return Err(Error::InvalidState);\n        }\n\n        let ty = EventType::from(&event_data);\n        if !EventImportance::from(ty).is_contained_in(&self.log_level) {\n            return Err(Error::Done);\n        }\n\n        let event = Event::with_time_ex(\n            elapsed_millis(self.start_time, now),\n            event_data,\n            ex_data,\n        );\n\n        if pretty {\n            self.add_event_pretty(event)\n        } else {\n            self.add_event(event)\n        }\n    }\n\n    /// Writes a JSON-SEQ-serialized [Event] using the provided [Event].\n    pub fn add_event<E: Serialize + Eventable>(\n        &mut self, event: E,\n    ) -> Result<()> {\n        self.write_event(event, false)\n    }\n\n    /// Writes a pretty-printed JSON-SEQ-serialized [Event] using the provided\n    /// [Event].\n    pub fn add_event_pretty<E: Serialize + Eventable>(\n        &mut self, event: E,\n    ) -> Result<()> {\n        self.write_event(event, true)\n    }\n\n    /// Writes a JSON-SEQ-serialized [Event] using the provided [Event].\n    fn write_event<E: Serialize + Eventable>(\n        &mut self, event: E, pretty: bool,\n    ) -> Result<()> {\n        if self.state != StreamerState::Ready {\n            return Err(Error::InvalidState);\n        }\n\n        if !event.importance().is_contained_in(&self.log_level) {\n            return Err(Error::Done);\n        }\n\n        self.writer.as_mut().write_all(b\"\u001e\")?;\n        if pretty {\n            serde_json::to_writer_pretty(self.writer.as_mut(), &event)\n                .map_err(|_| Error::Done)?;\n        } else {\n            serde_json::to_writer(self.writer.as_mut(), &event)\n                .map_err(|_| Error::Done)?;\n        }\n        self.writer.as_mut().write_all(b\"\\n\")?;\n\n        Ok(())\n    }\n\n    /// Returns the writer.\n    #[allow(clippy::borrowed_box)]\n    pub fn writer(&self) -> &Box<dyn std::io::Write + Send + Sync> {\n        &self.writer\n    }\n\n    pub fn start_time(&self) -> std::time::Instant {\n        self.start_time\n    }\n}\n\nimpl Drop for QlogStreamer {\n    fn drop(&mut self) {\n        let _ = self.finish_log();\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::BTreeMap;\n\n    use super::*;\n    use crate::events::quic;\n    use crate::events::quic::QuicFrame;\n    use crate::events::RawInfo;\n    use smallvec::smallvec;\n    use testing::*;\n\n    use serde_json::json;\n\n    #[test]\n    fn serialization_states() {\n        let v: Vec<u8> = Vec::new();\n        let buff = std::io::Cursor::new(v);\n        let writer = Box::new(buff);\n\n        let trace = make_trace_seq();\n        let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake);\n        let raw = Some(RawInfo {\n            length: Some(1251),\n            payload_length: Some(1224),\n            data: None,\n        });\n\n        let frame1 = QuicFrame::Stream {\n            stream_id: 40,\n            offset: Some(40),\n            raw: Some(RawInfo {\n                length: None,\n                payload_length: Some(400),\n                data: None,\n            }),\n            fin: Some(true),\n        };\n\n        let event_data1 = EventData::QuicPacketSent(quic::PacketSent {\n            header: pkt_hdr.clone(),\n            frames: Some(smallvec![frame1]),\n            raw: raw.clone(),\n            ..Default::default()\n        });\n\n        let ev1 = Event::with_time(0.0, event_data1);\n\n        let frame2 = QuicFrame::Stream {\n            stream_id: 0,\n            offset: Some(0),\n            raw: Some(RawInfo {\n                length: None,\n                payload_length: Some(100),\n                data: None,\n            }),\n            fin: Some(true),\n        };\n\n        let frame3 = QuicFrame::Stream {\n            stream_id: 0,\n            offset: Some(0),\n            raw: Some(RawInfo {\n                length: None,\n                payload_length: Some(100),\n                data: None,\n            }),\n            fin: Some(true),\n        };\n\n        let event_data2 = EventData::QuicPacketSent(quic::PacketSent {\n            header: pkt_hdr.clone(),\n            frames: Some(smallvec![frame2]),\n            raw: raw.clone(),\n            ..Default::default()\n        });\n\n        let ev2 = Event::with_time(0.0, event_data2);\n\n        let event_data3 = EventData::QuicPacketSent(quic::PacketSent {\n            header: pkt_hdr,\n            frames: Some(smallvec![frame3]),\n            stateless_reset_token: Some(\"reset_token\".to_string()),\n            raw,\n            ..Default::default()\n        });\n\n        let ev3 = Event::with_time(0.0, event_data3);\n\n        let mut s = streamer::QlogStreamer::new(\n            Some(\"title\".to_string()),\n            Some(\"description\".to_string()),\n            std::time::Instant::now(),\n            trace,\n            EventImportance::Base,\n            writer,\n        );\n\n        // Before the log is started all other operations should fail.\n        assert!(matches!(s.add_event(ev2.clone()), Err(Error::InvalidState)));\n        assert!(matches!(s.finish_log(), Err(Error::InvalidState)));\n\n        // Start log and add a simple event.\n        assert!(matches!(s.start_log(), Ok(())));\n        assert!(matches!(s.add_event(ev1), Ok(())));\n\n        // Add some more events.\n        assert!(matches!(s.add_event(ev2), Ok(())));\n        assert!(matches!(s.add_event(ev3.clone()), Ok(())));\n\n        // Adding an event with an external time should work too.\n        // For tests, it will resolve to 0 but we care about proving the API\n        // here, not timing specifics.\n        let now = std::time::Instant::now();\n\n        assert!(matches!(s.add_event_with_instant(ev3, now), Ok(())));\n\n        assert!(matches!(s.finish_log(), Ok(())));\n\n        let r = s.writer();\n        #[allow(clippy::borrowed_box)]\n        let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };\n\n        let log_string = r#\"\u001e{\"file_schema\":\"urn:ietf:params:qlog:file:sequential\",\"serialization_format\":\"JSON-SEQ\",\"title\":\"title\",\"description\":\"description\",\"trace\":{\"title\":\"Quiche qlog trace\",\"description\":\"Quiche qlog trace description\",\"vantage_point\":{\"type\":\"server\"},\"event_schemas\":[]}}\n\u001e{\"time\":0.0,\"name\":\"quic:packet_sent\",\"data\":{\"header\":{\"packet_type\":\"handshake\",\"packet_number\":0,\"version\":\"1\",\"scil\":8,\"dcil\":8,\"scid\":\"7e37e4dcc6682da8\",\"dcid\":\"36ce104eee50101c\"},\"raw\":{\"length\":1251,\"payload_length\":1224},\"frames\":[{\"frame_type\":\"stream\",\"stream_id\":40,\"offset\":40,\"fin\":true,\"raw\":{\"payload_length\":400}}]}}\n\u001e{\"time\":0.0,\"name\":\"quic:packet_sent\",\"data\":{\"header\":{\"packet_type\":\"handshake\",\"packet_number\":0,\"version\":\"1\",\"scil\":8,\"dcil\":8,\"scid\":\"7e37e4dcc6682da8\",\"dcid\":\"36ce104eee50101c\"},\"raw\":{\"length\":1251,\"payload_length\":1224},\"frames\":[{\"frame_type\":\"stream\",\"stream_id\":0,\"offset\":0,\"fin\":true,\"raw\":{\"payload_length\":100}}]}}\n\u001e{\"time\":0.0,\"name\":\"quic:packet_sent\",\"data\":{\"header\":{\"packet_type\":\"handshake\",\"packet_number\":0,\"version\":\"1\",\"scil\":8,\"dcil\":8,\"scid\":\"7e37e4dcc6682da8\",\"dcid\":\"36ce104eee50101c\"},\"stateless_reset_token\":\"reset_token\",\"raw\":{\"length\":1251,\"payload_length\":1224},\"frames\":[{\"frame_type\":\"stream\",\"stream_id\":0,\"offset\":0,\"fin\":true,\"raw\":{\"payload_length\":100}}]}}\n\u001e{\"time\":0.0,\"name\":\"quic:packet_sent\",\"data\":{\"header\":{\"packet_type\":\"handshake\",\"packet_number\":0,\"version\":\"1\",\"scil\":8,\"dcil\":8,\"scid\":\"7e37e4dcc6682da8\",\"dcid\":\"36ce104eee50101c\"},\"stateless_reset_token\":\"reset_token\",\"raw\":{\"length\":1251,\"payload_length\":1224},\"frames\":[{\"frame_type\":\"stream\",\"stream_id\":0,\"offset\":0,\"fin\":true,\"raw\":{\"payload_length\":100}}]}}\n\"#;\n\n        let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();\n\n        pretty_assertions::assert_eq!(log_string, written_string);\n    }\n\n    #[test]\n    fn stream_json_event() {\n        let data = json!({\"foo\": \"Bar\", \"hello\": 123});\n        let ev = events::JsonEvent {\n            time: 0.0,\n            importance: events::EventImportance::Core,\n            name: \"jsonevent:sample\".into(),\n            data,\n        };\n\n        let v: Vec<u8> = Vec::new();\n        let buff = std::io::Cursor::new(v);\n        let writer = Box::new(buff);\n\n        let trace = make_trace_seq();\n\n        let mut s = streamer::QlogStreamer::new(\n            Some(\"title\".to_string()),\n            Some(\"description\".to_string()),\n            std::time::Instant::now(),\n            trace,\n            EventImportance::Base,\n            writer,\n        );\n\n        assert!(matches!(s.start_log(), Ok(())));\n        assert!(matches!(s.add_event(ev), Ok(())));\n        assert!(matches!(s.finish_log(), Ok(())));\n\n        let r = s.writer();\n        #[allow(clippy::borrowed_box)]\n        let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };\n\n        let log_string = r#\"\u001e{\"file_schema\":\"urn:ietf:params:qlog:file:sequential\",\"serialization_format\":\"JSON-SEQ\",\"title\":\"title\",\"description\":\"description\",\"trace\":{\"title\":\"Quiche qlog trace\",\"description\":\"Quiche qlog trace description\",\"vantage_point\":{\"type\":\"server\"},\"event_schemas\":[]}}\n\u001e{\"time\":0.0,\"name\":\"jsonevent:sample\",\"data\":{\"foo\":\"Bar\",\"hello\":123}}\n\"#;\n\n        let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();\n\n        pretty_assertions::assert_eq!(log_string, written_string);\n    }\n\n    #[test]\n    fn stream_data_ex() {\n        let v: Vec<u8> = Vec::new();\n        let buff = std::io::Cursor::new(v);\n        let writer = Box::new(buff);\n\n        let trace = make_trace_seq();\n        let pkt_hdr = make_pkt_hdr(quic::PacketType::Handshake);\n        let raw = Some(RawInfo {\n            length: Some(1251),\n            payload_length: Some(1224),\n            data: None,\n        });\n\n        let frame1 = QuicFrame::Stream {\n            stream_id: 40,\n            offset: Some(40),\n            raw: Some(RawInfo {\n                length: None,\n                payload_length: Some(400),\n                data: None,\n            }),\n            fin: Some(true),\n        };\n\n        let event_data1 = EventData::QuicPacketSent(quic::PacketSent {\n            header: pkt_hdr.clone(),\n            frames: Some(smallvec![frame1]),\n            raw: raw.clone(),\n            ..Default::default()\n        });\n        let j1 = json!({\"foo\": \"Bar\", \"hello\": 123});\n        let j2 = json!({\"baz\": [1,2,3,4]});\n        let mut ex_data = BTreeMap::new();\n        ex_data.insert(\"first\".to_string(), j1);\n        ex_data.insert(\"second\".to_string(), j2);\n\n        let ev1 = Event::with_time_ex(0.0, event_data1, ex_data);\n\n        let frame2 = QuicFrame::Stream {\n            stream_id: 1,\n            offset: Some(0),\n            raw: Some(RawInfo {\n                length: None,\n                payload_length: Some(100),\n                data: None,\n            }),\n            fin: Some(true),\n        };\n\n        let event_data2 = EventData::QuicPacketSent(quic::PacketSent {\n            header: pkt_hdr.clone(),\n            frames: Some(smallvec![frame2]),\n            raw: raw.clone(),\n            ..Default::default()\n        });\n\n        let ev2 = Event::with_time(0.0, event_data2);\n\n        let mut s = streamer::QlogStreamer::new(\n            Some(\"title\".to_string()),\n            Some(\"description\".to_string()),\n            std::time::Instant::now(),\n            trace,\n            EventImportance::Base,\n            writer,\n        );\n\n        assert!(matches!(s.start_log(), Ok(())));\n        assert!(matches!(s.add_event(ev1), Ok(())));\n        assert!(matches!(s.add_event(ev2), Ok(())));\n        assert!(matches!(s.finish_log(), Ok(())));\n\n        let r = s.writer();\n        #[allow(clippy::borrowed_box)]\n        let w: &Box<std::io::Cursor<Vec<u8>>> = unsafe { std::mem::transmute(r) };\n\n        let log_string = r#\"\u001e{\"file_schema\":\"urn:ietf:params:qlog:file:sequential\",\"serialization_format\":\"JSON-SEQ\",\"title\":\"title\",\"description\":\"description\",\"trace\":{\"title\":\"Quiche qlog trace\",\"description\":\"Quiche qlog trace description\",\"vantage_point\":{\"type\":\"server\"},\"event_schemas\":[]}}\n\u001e{\"time\":0.0,\"name\":\"quic:packet_sent\",\"data\":{\"header\":{\"packet_type\":\"handshake\",\"packet_number\":0,\"version\":\"1\",\"scil\":8,\"dcil\":8,\"scid\":\"7e37e4dcc6682da8\",\"dcid\":\"36ce104eee50101c\"},\"raw\":{\"length\":1251,\"payload_length\":1224},\"frames\":[{\"frame_type\":\"stream\",\"stream_id\":40,\"offset\":40,\"fin\":true,\"raw\":{\"payload_length\":400}}]},\"first\":{\"foo\":\"Bar\",\"hello\":123},\"second\":{\"baz\":[1,2,3,4]}}\n\u001e{\"time\":0.0,\"name\":\"quic:packet_sent\",\"data\":{\"header\":{\"packet_type\":\"handshake\",\"packet_number\":0,\"version\":\"1\",\"scil\":8,\"dcil\":8,\"scid\":\"7e37e4dcc6682da8\",\"dcid\":\"36ce104eee50101c\"},\"raw\":{\"length\":1251,\"payload_length\":1224},\"frames\":[{\"frame_type\":\"stream\",\"stream_id\":1,\"offset\":0,\"fin\":true,\"raw\":{\"payload_length\":100}}]}}\n\"#;\n\n        let written_string = std::str::from_utf8(w.as_ref().get_ref()).unwrap();\n\n        pretty_assertions::assert_eq!(log_string, written_string);\n    }\n}\n"
  },
  {
    "path": "qlog/src/testing/event_tests.rs",
    "content": "// Copyright (C) 2026, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::*;\nuse crate::events::quic::PacketSent;\nuse crate::events::quic::PacketType;\nuse crate::events::quic::QuicFrame;\nuse crate::events::quic::RecoveryMetricsUpdated;\nuse crate::events::EventData;\nuse crate::events::ExData;\nuse crate::events::RawInfo;\nuse crate::Event;\n\n#[test]\nfn packet_sent_event_no_frames() {\n    let log_string = r#\"{\n  \"time\": 0.0,\n  \"name\": \"quic:packet_sent\",\n  \"data\": {\n    \"header\": {\n      \"packet_type\": \"initial\",\n      \"packet_number\": 0,\n      \"version\": \"1\",\n      \"scil\": 8,\n      \"dcil\": 8,\n      \"scid\": \"7e37e4dcc6682da8\",\n      \"dcid\": \"36ce104eee50101c\"\n    },\n    \"raw\": {\n      \"length\": 1251,\n      \"payload_length\": 1224\n    }\n  }\n}\"#;\n\n    let pkt_hdr = make_pkt_hdr(PacketType::Initial);\n    let ev_data = EventData::QuicPacketSent(PacketSent {\n        header: pkt_hdr,\n        raw: Some(RawInfo {\n            length: Some(1251),\n            payload_length: Some(1224),\n            data: None,\n        }),\n        ..Default::default()\n    });\n\n    let ev = Event::with_time(0.0, ev_data);\n\n    pretty_assertions::assert_eq!(\n        serde_json::to_string_pretty(&ev).unwrap(),\n        log_string\n    );\n}\n\n#[test]\nfn packet_sent_event_some_frames() {\n    let log_string = r#\"{\n  \"time\": 0.0,\n  \"name\": \"quic:packet_sent\",\n  \"data\": {\n    \"header\": {\n      \"packet_type\": \"initial\",\n      \"packet_number\": 0,\n      \"version\": \"1\",\n      \"scil\": 8,\n      \"dcil\": 8,\n      \"scid\": \"7e37e4dcc6682da8\",\n      \"dcid\": \"36ce104eee50101c\"\n    },\n    \"raw\": {\n      \"length\": 1251,\n      \"payload_length\": 1224\n    },\n    \"frames\": [\n      {\n        \"frame_type\": \"padding\",\n        \"raw\": {\n          \"payload_length\": 1234\n        }\n      },\n      {\n        \"frame_type\": \"ping\"\n      },\n      {\n        \"frame_type\": \"stream\",\n        \"stream_id\": 0,\n        \"offset\": 0,\n        \"fin\": true,\n        \"raw\": {\n          \"payload_length\": 100\n        }\n      }\n    ]\n  }\n}\"#;\n\n    let pkt_hdr = make_pkt_hdr(PacketType::Initial);\n\n    let frames = vec![\n        QuicFrame::Padding {\n            raw: Some(RawInfo {\n                length: None,\n                payload_length: Some(1234),\n                data: None,\n            }),\n        },\n        QuicFrame::Ping { raw: None },\n        QuicFrame::Stream {\n            stream_id: 0,\n            offset: Some(0),\n            fin: Some(true),\n            raw: Some(RawInfo {\n                length: None,\n                payload_length: Some(100),\n                data: None,\n            }),\n        },\n    ];\n\n    let ev_data = EventData::QuicPacketSent(PacketSent {\n        header: pkt_hdr,\n        frames: Some(frames.into()),\n        raw: Some(RawInfo {\n            length: Some(1251),\n            payload_length: Some(1224),\n            data: None,\n        }),\n        ..Default::default()\n    });\n\n    let ev = Event::with_time(0.0, ev_data);\n    pretty_assertions::assert_eq!(\n        serde_json::to_string_pretty(&ev).unwrap(),\n        log_string\n    );\n}\n\n// Test constants for MetricsUpdated tests\nconst MIN_RTT: f32 = 10.0;\nconst SMOOTHED_RTT: f32 = 15.0;\nconst CONGESTION_WINDOW: u64 = 12000;\nconst PACING_RATE: u64 = 500000;\nconst DELIVERY_RATE: u64 = 1000000;\nconst COLLISION_VALUE: f32 = 999.0;\n\n#[test]\nfn packet_header() {\n    let pkt_hdr = make_pkt_hdr(PacketType::Initial);\n\n    let log_string = r#\"{\n  \"packet_type\": \"initial\",\n  \"packet_number\": 0,\n  \"version\": \"1\",\n  \"scil\": 8,\n  \"dcil\": 8,\n  \"scid\": \"7e37e4dcc6682da8\",\n  \"dcid\": \"36ce104eee50101c\"\n}\"#;\n\n    assert_eq!(serde_json::to_string_pretty(&pkt_hdr).unwrap(), log_string);\n}\n\n#[test]\nfn metrics_updated_with_ex_data() {\n    // Test that ex_data fields are flattened into the same object\n    let ex_data = ExData::from([(\n        \"delivery_rate\".to_string(),\n        serde_json::json!(DELIVERY_RATE),\n    )]);\n\n    let metrics = RecoveryMetricsUpdated {\n        min_rtt: Some(MIN_RTT),\n        congestion_window: Some(CONGESTION_WINDOW),\n        ex_data,\n        ..Default::default()\n    };\n\n    let json = serde_json::to_value(&metrics).unwrap();\n\n    // Verify standard fields are present\n    assert_eq!(json[\"min_rtt\"], MIN_RTT);\n    assert_eq!(json[\"congestion_window\"], CONGESTION_WINDOW);\n\n    // Verify ex_data field is flattened (not nested under \"ex_data\")\n    assert_eq!(json[\"delivery_rate\"], DELIVERY_RATE);\n    assert!(json.get(\"ex_data\").is_none(), \"ex_data should be flattened\");\n}\n\n#[test]\nfn metrics_updated_ex_data_collision() {\n    // Test collision: same field set via struct AND ex_data.\n    // With serde's preserve_order feature and ex_data at the top of the\n    // struct, standard fields are serialized last and take precedence.\n\n    let ex_data = ExData::from([(\n        \"min_rtt\".to_string(),\n        serde_json::json!(COLLISION_VALUE),\n    )]);\n\n    let metrics = RecoveryMetricsUpdated {\n        min_rtt: Some(MIN_RTT), // struct field value\n        ex_data,                // ex_data also has min_rtt\n        ..Default::default()\n    };\n\n    let json = serde_json::to_value(&metrics).unwrap();\n\n    // Standard field wins in collision - ex_data cannot overwrite standard\n    // fields, which prevents accidental data corruption.\n    assert_eq!(json[\"min_rtt\"], MIN_RTT);\n}\n\n#[test]\nfn metrics_updated_round_trip() {\n    // Test serialization -> deserialization round-trip\n    let ex_data = ExData::from([(\n        \"delivery_rate\".to_string(),\n        serde_json::json!(DELIVERY_RATE),\n    )]);\n\n    let original = RecoveryMetricsUpdated {\n        min_rtt: Some(MIN_RTT),\n        smoothed_rtt: Some(SMOOTHED_RTT),\n        congestion_window: Some(CONGESTION_WINDOW),\n        pacing_rate: Some(PACING_RATE),\n        ex_data,\n        ..Default::default()\n    };\n\n    let json_str = serde_json::to_string(&original).unwrap();\n    let deserialized: RecoveryMetricsUpdated =\n        serde_json::from_str(&json_str).unwrap();\n\n    // Standard fields round-trip correctly\n    assert_eq!(deserialized.min_rtt, original.min_rtt);\n    assert_eq!(deserialized.smoothed_rtt, original.smoothed_rtt);\n    assert_eq!(deserialized.congestion_window, original.congestion_window);\n    assert_eq!(deserialized.pacing_rate, original.pacing_rate);\n\n    // ex_data fields round-trip correctly\n    assert_eq!(\n        deserialized.ex_data.get(\"delivery_rate\"),\n        Some(&serde_json::json!(DELIVERY_RATE))\n    );\n}\n\n#[test]\nfn metrics_updated_no_ex_data() {\n    // Test that ex_data is not present when not used\n    let metrics = RecoveryMetricsUpdated {\n        min_rtt: Some(MIN_RTT),\n        congestion_window: Some(CONGESTION_WINDOW),\n        ..Default::default()\n    };\n\n    let json = serde_json::to_value(&metrics).unwrap();\n\n    // Verify standard fields are present\n    assert_eq!(json[\"min_rtt\"], MIN_RTT);\n    assert_eq!(json[\"congestion_window\"], CONGESTION_WINDOW);\n\n    // Verify ex_data is not present\n    assert!(\n        json.get(\"ex_data\").is_none(),\n        \"ex_data should not be present\"\n    );\n}\n"
  },
  {
    "path": "qlog/src/testing/mod.rs",
    "content": "// Copyright (C) 2026, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Testing utilities for qlog.\n\nuse crate::events::quic::PacketType;\nuse crate::PacketHeader;\nuse crate::Trace;\nuse crate::TraceSeq;\nuse crate::VantagePoint;\nuse crate::VantagePointType;\n\npub fn make_pkt_hdr(packet_type: PacketType) -> PacketHeader {\n    let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];\n    let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];\n\n    // Some(1251),\n    // Some(1224),\n\n    PacketHeader::new(\n        packet_type,\n        Some(0),\n        None,\n        None,\n        Some(0x0000_0001),\n        Some(&scid),\n        Some(&dcid),\n    )\n}\n\npub fn make_trace() -> Trace {\n    Trace::new(\n        Some(\"Quiche qlog trace\".to_string()),\n        Some(\"Quiche qlog trace description\".to_string()),\n        None,\n        Some(VantagePoint {\n            name: None,\n            ty: VantagePointType::Server,\n            flow: None,\n        }),\n        vec![],\n    )\n}\n\npub fn make_trace_seq() -> TraceSeq {\n    TraceSeq::new(\n        Some(\"Quiche qlog trace\".to_string()),\n        Some(\"Quiche qlog trace description\".to_string()),\n        None,\n        Some(VantagePoint {\n            name: None,\n            ty: VantagePointType::Server,\n            flow: None,\n        }),\n        vec![],\n    )\n}\n\n#[cfg(test)]\nmod event_tests;\n#[cfg(test)]\nmod trace_tests;\n"
  },
  {
    "path": "qlog/src/testing/trace_tests.rs",
    "content": "// Copyright (C) 2026, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::*;\nuse crate::events::quic::PacketSent;\nuse crate::events::quic::PacketType;\nuse crate::events::quic::QuicFrame;\nuse crate::events::EventData;\nuse crate::events::RawInfo;\nuse crate::Event;\nuse crate::Trace;\nuse crate::TraceSeq;\n\n#[test]\nfn trace_no_events() {\n    let log_string = r#\"{\n  \"title\": \"Quiche qlog trace\",\n  \"description\": \"Quiche qlog trace description\",\n  \"vantage_point\": {\n    \"type\": \"server\"\n  },\n  \"event_schemas\": [],\n  \"events\": []\n}\"#;\n\n    let trace = make_trace();\n\n    let serialized = serde_json::to_string_pretty(&trace).unwrap();\n    pretty_assertions::assert_eq!(serialized, log_string);\n\n    let deserialized: Trace = serde_json::from_str(&serialized).unwrap();\n    pretty_assertions::assert_eq!(deserialized, trace);\n}\n\n#[test]\nfn trace_seq_no_events() {\n    let log_string = r#\"{\n  \"title\": \"Quiche qlog trace\",\n  \"description\": \"Quiche qlog trace description\",\n  \"vantage_point\": {\n    \"type\": \"server\"\n  },\n  \"event_schemas\": []\n}\"#;\n\n    let trace = make_trace_seq();\n\n    let serialized = serde_json::to_string_pretty(&trace).unwrap();\n    pretty_assertions::assert_eq!(serialized, log_string);\n\n    let deserialized: TraceSeq = serde_json::from_str(&serialized).unwrap();\n    pretty_assertions::assert_eq!(deserialized, trace);\n}\n\n#[test]\nfn trace_single_transport_event() {\n    let log_string = r#\"{\n  \"title\": \"Quiche qlog trace\",\n  \"description\": \"Quiche qlog trace description\",\n  \"vantage_point\": {\n    \"type\": \"server\"\n  },\n  \"event_schemas\": [],\n  \"events\": [\n    {\n      \"time\": 0.0,\n      \"name\": \"quic:packet_sent\",\n      \"data\": {\n        \"header\": {\n          \"packet_type\": \"initial\",\n          \"packet_number\": 0,\n          \"version\": \"1\",\n          \"scil\": 8,\n          \"dcil\": 8,\n          \"scid\": \"7e37e4dcc6682da8\",\n          \"dcid\": \"36ce104eee50101c\"\n        },\n        \"raw\": {\n          \"length\": 1251,\n          \"payload_length\": 1224\n        },\n        \"frames\": [\n          {\n            \"frame_type\": \"stream\",\n            \"stream_id\": 0,\n            \"offset\": 0,\n            \"fin\": true,\n            \"raw\": {\n              \"payload_length\": 100\n            }\n          }\n        ]\n      }\n    }\n  ]\n}\"#;\n\n    let mut trace = make_trace();\n\n    let pkt_hdr = make_pkt_hdr(PacketType::Initial);\n\n    let frames = vec![QuicFrame::Stream {\n        stream_id: 0,\n        offset: Some(0),\n        fin: Some(true),\n        raw: Some(RawInfo {\n            length: None,\n            payload_length: Some(100),\n            data: None,\n        }),\n    }];\n    let event_data = EventData::QuicPacketSent(PacketSent {\n        header: pkt_hdr,\n        frames: Some(frames.into()),\n        raw: Some(RawInfo {\n            length: Some(1251),\n            payload_length: Some(1224),\n            data: None,\n        }),\n        ..Default::default()\n    });\n\n    let ev = Event::with_time(0.0, event_data);\n\n    trace.push_event(ev);\n\n    let serialized = serde_json::to_string_pretty(&trace).unwrap();\n    pretty_assertions::assert_eq!(serialized, log_string);\n\n    let deserialized: Trace = serde_json::from_str(&serialized).unwrap();\n    pretty_assertions::assert_eq!(deserialized, trace);\n}\n"
  },
  {
    "path": "qlog-dancer/AGENTS.md",
    "content": "# qlog-dancer/\n\n## OVERVIEW\n\nVisualization/analysis tool for qlog and Chrome netlog files. Parses logs into a `Datastore`, extracts time-series into `SeriesStore`, renders PNG charts (native) or canvas plots (wasm). Outputs HTML/text reports with tabled summaries.\n\nDual-target: native CLI binary (`main.rs`) + wasm-bindgen web UI (`web.rs`, `#[cfg(target_arch = \"wasm32\")]`). `crate-type = [\"lib\", \"cdylib\"]` -- cdylib is for wasm.\n\n## STRUCTURE\n\n```\nsrc/\n  main.rs              CLI entry: parse args via AppConfig, render selected plots, emit report\n  lib.rs               Public API: parse_log_file(), PacketType, type aliases\n  web.rs               wasm-bindgen exports (851 lines), canvas rendering, JS interop\n  config.rs            AppConfig (clap CLI + wasm config), plot toggles, colors\n  datastore.rs         Datastore struct (~1985 lines) -- central parsed-log representation\n  seriesstore.rs       SeriesStore: extracts plot-ready time-series from Datastore\n  wirefilter.rs        Event filtering via cloudflare/wirefilter-engine DSL\n  request_stub.rs      Stub types for request-level data\n  plots/               Chart rendering (plotters crate)\n    mod.rs             PlotParameters, ChartSize, ClampParams, output type enums\n    conn_overview.rs   Multi-panel connection overview (cwnd, bytes, rtt, streams)\n    congestion_control.rs  CC-specific plot\n    conn_flow_control.rs   Connection-level flow control\n    packet_sent.rs     Packet-number vs time scatter\n    packet_received.rs Received packet scatter\n    stream_sparks.rs   Per-stream sparkline grids\n    stream_multiplex.rs  Stream multiplexing timeline\n    pending.rs         Pending data plot\n    rtt.rs             RTT plot\n    colors.rs          Color palettes\n    minmax.rs          Axis range utilities\n  reports/             Output reports\n    mod.rs             report() dispatcher\n    html.rs            HTML report generation (table_to_html)\n    text.rs            Plain-text summary\n    events.rs          Event-level report details\n  trackers/            Stateful metric accumulators\n    stream_buffer_tracker.rs  Per-stream buffer sizes\n    stream_max_tracker.rs     Stream high-water marks\nindex.html, *.js, *.css   Web UI assets (crate root, non-standard location)\n```\n\n## WHERE TO LOOK\n\n| Task | File |\n|------|------|\n| Add new plot type | `src/plots/` -- add module, wire into `main.rs` + `web.rs` |\n| Change parsed fields | `src/datastore.rs` -- all log-to-struct extraction |\n| Add series for plotting | `src/seriesstore.rs` |\n| Modify event filters | `src/wirefilter.rs` (wirefilter-engine DSL) |\n| CLI args / config | `src/config.rs` (AppConfig, clap) |\n| Wasm API surface | `src/web.rs` (`#[wasm_bindgen]` exports) |\n| Report formatting | `src/reports/` |\n\n## NOTES\n\n- Native build requires system libs: **libexpat, freetype, fontconfig** (plotters SVG/bitmap backends).\n- Wasm uses `plotters-canvas` backend -- canvas rendering, no system deps.\n- `wirefilter-engine` pinned to specific git rev, not on crates.io.\n- Log format auto-detection: tries qlog JSON, falls back to JSON-SEQ (sqlog), and vice versa.\n- `getrandom/wasm_js` feature + `.cargo/config.toml` needed for wasm randomness.\n- Web assets (`index.html`, `qlog-dancer-ui.js`, `qlog-dancer.css`) live in crate root, not `src/`.\n"
  },
  {
    "path": "qlog-dancer/Cargo.toml",
    "content": "[package]\nname = \"qlog-dancer\"\nversion = \"0.1.0\"\ndescription = \"qlog and netlog log analysis and visualization\"\nrepository = { workspace = true }\nauthors = [\"Lucas Pardue lucas@lucaspardue.com>\"]\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\nreadme = \"README.md\"\n\n[dependencies]\nclap = \"4\"\nenv_logger = { workspace = true }\nfutures-util = { version = \"0.3\", features = [\"io\", \"sink\"] }\njs-sys = \"0.3\"\nlog = { workspace = true }\nnetlog = { workspace = true }\nplotters = \"0.3.4\"\nqlog = { workspace = true }\nregex = { workspace = true }\nserde = { workspace = true }\nserde_json = { workspace = true }\ntabled = \"0.15.0\"\ntable_to_html = \"0.9.0\"\n\nwirefilter-engine = { git = \"https://github.com/cloudflare/wirefilter.git\", rev=\"f3116d9f244d10a2d9a37d036cfa7129c5e50d3b\" }\n\n# By default, getrandom doesn't have any source of randomness on wasm32-unknown.\n# This optional dependency allows us to build with `--features getrandom/wasm_js`.\n# This also requires that the backend is set via .cargo/config.toml.\n# For more information see: https://docs.rs/getrandom/#webassembly-support\n[target.'cfg(target_family = \"wasm\")'.dependencies]\ngetrandom = { version = \"0.3\", features = [\"wasm_js\"] }\nplotters-canvas = \"0.3\"\nserde-wasm-bindgen = \"0.6\"\nwasm-bindgen = \"0.2\"\nwasm-streams = \"0.4\"\nwasm-bindgen-futures = \"0.4\"\nweb-sys = { version = \"0.3\", features = [\"HtmlCanvasElement\", \"MouseEvent\", \"console\"] }\n\n[dev-dependencies]\nsmallvec = { workspace = true }\n\n[lib]\ncrate-type = [\"lib\", \"cdylib\"]\n\n[package.metadata.cargo-machete]\nignored = [\"wirefilter-engine\"]\n"
  },
  {
    "path": "qlog-dancer/README.md",
    "content": "qlog-dancer parses logs and generates stats and graphs.\n\nSupports log files in the following formats:\n\n* qlog and sqlog format according to version 0.3 of the datamodel.\n* Chrome netlog format\n\n# Pre-requisites\n\nOn Debian-based systems, the following additional packages are required:\n\n* cmake\n* libexpat1-dev\n* libfreetype6-dev\n* libfontconfig1-dev\n\n# Generating reports\n\nqlog-dancer can chew through logs and produce reports in text or HTML.\n\nThe following example creates a directory named\n\"chrome-net-export-log.json-report\" and places HTML files containing reports\nthere.\n\n```\n$ cargo run --release -- --report-html /path/to/chrome-net-export-log.json\n```\n\nThe following example prints out text reports to stdout\n```\n$ cargo run --release -- --report-text /path/to/chrome-net-export-log.json\n```\n\n## qlog event table\n\nBoth the `--report-text` and `--report-html` options will produce a table\nsummary of the qlog events contained in the qlog file. This table can be\nfiltered using the `--qlog-wirefilter` option, which uses\n[Wirefilter](https://github.com/cloudflare/wirefilter) for Wireshark-like\nexpressions and matching behavior. The fields supported are:\n\n* category: A string representing the qlog category of the event\n* name: A string representing the qlog event name without category.\n* stream_id: An integer representing a QUIC stream ID, that can appear in many\n  types of event. A filter based on stream ID will match several events.\n\nSome examples:\n\n* `--qlog-wirefilter 'name != \"data_moved\" && name != \"metrics_updated\"'` will\n  filter out events with the name `data_moved` and `metrics_updated`\n* `--qlog-wirefilter 'category = \"http\"'` will filter in events belonging to the\n  `http` category.\n* `--qlog-wirefilter 'any(stream_id[*]==0)'` will filter\n  in events that contains a stream ID of 0.\n* `--qlog-wirefilter 'any(stream_id[*] in {0 3})'` will filter\n  in events that contains a stream ID of either 0 or 3.\n\nWhen using the `--report-html` option, a table of filtered qlog events in the\nprovided file will be produced in event-list.html. This includes an in-browser\nsearch capability based on the default [DataTables](https://datatables.net/)\nlibrary.\n\n# Generating charts\n\nqlog-dancer can chew through logs and produce charts as PNG images.\n\nBy default, this feature is not enabled. Use the `charts` option to enable it,\nuse `-h` for a description of the options.\n\nThe following example creates a directory named \"file.sqlog-charts\" and places\nimages there.\n\n```\n$ cargo run --release -- --charts all /path/to/file.sqlog\n```\n\nThere are several types of chart including:\n\n* file.sqlog-conn-overview.png\n  * Congestion control, RTT, and combined stream data plotting\n* file.sqlog-conn-spark-absolute.png\n  * Plot individual charts for stream data buffering and STREAM frame emission,\n    x-axis (representing time) intercept is test run start time\n* file.sqlog-conn-spark-relative.png\n  * Plot individual charts for stream data buffering and STREAM frame emission,\n    x-axis (representing time) intercept is when stream buffering started\n\nqlog-dancer uses the plotters library, which has interpolated line series. We\nattempt to work around this in a pre-processing step but be careful to check\nline series rending matches the expectations of underlying daa types.\n\n# Filtering Chrome netlogs\n\nNetlogs can contain events related to many connections. The `netlog-filter`\noption is a comma-seperated list of hostnames to filter in to netlog analysis.\nBy default, all hostname are analyzed.\n\n```\n$ cargo run --release -- --netlog-filter \"example.com\" /path/to/chrome-net-export-log.json\n```\n\n# The qlog-dancer web app\n\nqlog-dancer also provides some capabilities as a web app via WASM. Some of the\nfile handling and loading code is different but otherwise it still uses plotters\nto generate charts that are then drawn to a canvas on a web page. This provides\nsupport for interactive elements with the plots.\n\nFor local development:\n\n1. Install [wasm-pack](https://github.com/rustwasm/wasm-pack).\n2. From the qlog-dancer directory, run `wasm-pack build --target=web`. This\n   generates files into a `pkg` subdirectory.\n3. Launch a webserver of any kind that can serve the checked in `index.html` and\n   the generated file such `pkg/qlog_dancer.js`. For example, the following works just fine\n\n```\npath/to/qlog-dancer/qlog-dancer$ python3 -m http.server\nServing HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/)\n```\n4. Connect to your webserver and click the \"Choose file\" button to load a file\n   and render some plots. E.g. `http://localhost:8000/` (no need to type\n   index.html since the example server deals with that itself)\n\n"
  },
  {
    "path": "qlog-dancer/index.html",
    "content": "<html>\n  <head>\n    <meta content=\"text/html;charset=utf-8\" http-equiv=\"Content-Type\"/>\n    <link rel=\"stylesheet\" href=\"qlog-dancer.css\"/>\n    <script src=\"https://code.jquery.com/jquery-3.7.0.js\"></script>\n    <script src=\"https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js\"></script>\n    <script src=\"https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js\"></script>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.datatables.net/1.13.7/css/jquery.dataTables.min.css\">\n  </head>\n  <body>\n    <script type=\"module\">\n      import init from './pkg/qlog_dancer.js';\n\n      async function run() {\n        await init();\n\n        // After init we can use all the functionality defined in wasm ...\n      }\n\n      run();\n    </script>\n    <script type=\"module\" src=\"qlog-dancer-ui.js\"></script>\n\n    <h1>Welcome to qlog-dancer</h1>\n    <p>This web app can load .sqlog files and render information in various charts. Pick an input to get started.</p>\n    <p>The parsing and plotting code is shared with the qlog-dancer binary. That\n    can be run offline for faster batch processing. For more info, see\n    <a href=\"https://github.com//cloudflare/quiche/tree/master/qlog-dancer\">the repo</a>.</p>\n\n    <div>\n    <input type=\"file\" id=\"input\">\n    </div>\n    <div class=\"line\"></div>\n    <div class=\"line\"></div>\n    <div class=\"line\"></div>\n    <div id=\"loading\" style=\"display: none\">\n      <p>Loading..</p>\n      <div class=\"lds-ellipsis\"><div></div><div></div><div></div><div></div></div>\n    </div>\n    <div id=\"load-complete\" style=\"display: none\">\n      <p><strong>Load complete!</strong> Toggle plot visibility using the buttons below.</p>\n      <button id=\"overview-btn\">Overview hidden</button>\n      <button id=\"flow-control-btn\">Flow control hidden</button>\n      <button id=\"pkt-tx-btn\">Packet tx hidden</button>\n      <button id=\"pkt-rx-btn\">Packet rx hidden</button>\n      <button id=\"stream-multiplex-btn\">Stream multiplexing hidden</button>\n      <button id=\"sparks-btn\">Stream sparks hidden</button>\n      <button id=\"pending-btn\">Stream pending hidden</button>\n      <button id=\"event-list-btn\">Events hidden</button>\n    </div>\n    <div id=\"tooltip\" class=\"tooltip\"></div>\n\n    <div id=\"overview\" style=\"display: none\">\n      <h2>Connection overview</h2>\n      <div>\n        <div id=\"overview_container\" class=\"canvas-container\" width=\"1200\" height=\"400\">\n          <canvas id=\"overview_canvas\" class=\"canvassy\" width=\"1200\" height=\"400\"></canvas>\n          <div class=\"resize-handle se\"></div>\n        </div>\n        <button id=\"overview-reset-btn\" class=\"canvas-control\">Reset zoom</button>\n        <button id=\"overview-toggle-legend-btn\" class=\"canvas-control\">Toggle legend</button>\n      </div>\n\n      <h3>Congestion control</h2>\n      <div>\n        <div id=\"cc_container\" class=\"canvas-container\" width=\"1200\" height=\"300\">\n          <canvas id=\"cc_canvas\" class=\"canvassy\" width=\"1200\" height=\"300\"></canvas>\n          <div class=\"resize-handle se\"></div>\n        </div>\n        <button id=\"cc-reset-btn\" class=\"canvas-control\">Reset zoom</button>\n        <button id=\"cc-toggle-legend-btn\" class=\"canvas-control\">Toggle legend</button>\n      </div>\n\n      <h3>RTT</h2>\n      <div>\n        <div id=\"rtt_container\" class=\"canvas-container\" width=\"1200\" height=\"300\">\n          <canvas id=\"rtt_canvas\" class=\"canvassy\" width=\"1200\" height=\"300\"></canvas>\n          <div class=\"resize-handle se\"></div>\n        </div>\n        <button id=\"rtt-reset-btn\" class=\"canvas-control\">Reset zoom</button>\n        <button id=\"rtt-toggle-legend-btn\" class=\"canvas-control\">Toggle legend</button>\n      </div>\n    </div>\n\n    <div id=\"flow-control\" style=\"display: none\">\n        <h2>Flow control</h2>\n        <div id=\"flow_control_container\" class=\"canvas-container\" width=\"1200\" height=\"400\">\n          <canvas id=\"flow_control_canvas\" class=\"canvassy\" width=\"1200\" height=\"400\"></canvas>\n          <div class=\"resize-handle se\"></div>\n        </div>\n    </div>\n\n    <div id=\"pkt-rx\" style=\"display: none\">\n        <h2>Packets received</h2>\n        <div id=\"pkt-rx-container\" class=\"canvas-container\" width=\"1200\" height=\"400\">\n          <canvas id=\"pkt-rx-canvas\" class=\"canvassy\" width=\"1200\" height=\"400\"></canvas>\n          <div class=\"resize-handle se\"></div>\n        </div>\n    </div>\n\n    <div id=\"pkt-tx\" style=\"display: none\">\n        <h2>Packets sent</h2>\n        <div>\n          <div id=\"pkt-tx-container\" class=\"canvas-container\" width=\"1200\" height=\"200\">\n            <canvas id=\"pkt-tx-canvas\" class=\"canvassy\" width=\"1200\" height=\"200\"></canvas>\n            <div class=\"resize-handle se\"></div>\n          </div>\n          <button id=\"pkt-tx-reset-btn\" class=\"canvas-control\">Reset zoom</button>\n          <button id=\"pkt-tx-toggle-legend-btn\" class=\"canvas-control\">Toggle legend</button>\n        </div>\n\n        <h3>Packet sent / lost / delivered counts</h3>\n        <div>\n          <div id=\"pkt-tx-counts-container\" class=\"canvas-container\" width=\"1200\" height=\"200\">\n            <canvas id=\"pkt-tx-counts-canvas\" class=\"canvassy\" width=\"1200\" height=\"200\"></canvas>\n            <div class=\"resize-handle se\"></div>\n          </div>\n          <button id=\"pkt-tx-counts-reset-btn\" class=\"canvas-control\">Reset zoom</button>\n          <button id=\"pkt-tx-counts-toggle-legend-btn\" class=\"canvas-control\">Toggle legend</button>\n        </div>\n\n        <h3>Pacing rate</h3>\n        <div>\n          <div id=\"pkt-tx-pacing-container\" class=\"canvas-container\" width=\"1200\" height=\"200\">\n            <canvas id=\"pkt-tx-pacing-canvas\" class=\"canvassy\" width=\"1200\" height=\"200\"></canvas>\n            <div class=\"resize-handle se\"></div>\n          </div>\n          <button id=\"pkt-tx-pacing-reset-btn\" class=\"canvas-control\">Reset zoom</button>\n          <button id=\"pkt-tx-pacing-toggle-legend-btn\" class=\"canvas-control\">Toggle legend</button>\n        </div>\n\n        <h3>Packet created / sent delta timing</h3>\n        <div>\n          <div id=\"pkt-tx-delta-container\" class=\"canvas-container\" width=\"1200\" height=\"200\">\n            <canvas id=\"pkt-tx-delta-canvas\" class=\"canvassy\" width=\"1200\" height=\"200\"></canvas>\n            <div class=\"resize-handle se\"></div>\n          </div>\n          <button id=\"pkt-tx-delta-reset-btn\" class=\"canvas-control\">Reset zoom</button>\n          <button id=\"pkt-tx-delta-toggle-legend-btn\" class=\"canvas-control\">Toggle legend</button>\n        </div>\n\n    </div>\n\n    <div id=\"stream-multiplex\" style=\"display: none\">\n      <h2>Stream multiplexing</h2>\n      <div id=\"stream-multiplex-container\" class=\"canvas-container\" width=\"600\" height=\"400\">\n        <canvas id=\"stream-multiplex-canvas\" class=\"canvassy\" width=\"600\" height=\"800\"></canvas>\n        <div class=\"resize-handle se\"></div>\n      </div>\n    </div>\n\n    <div id=\"abs-dl\" style=\"display: none\">\n      <h3>Download (absolute timings)</h3>\n      <div id=\"abs_dl-container\" class=\"canvas-container\" width=\"600\" height=\"400\">\n        <canvas id=\"abs_dl_canvas\" class=\"canvassy\" width=\"600\" height=\"200\"></canvas>\n        <div class=\"resize-handle se\"></div>\n      </div>\n    </div>\n\n    <div id=\"rel-dl\" style=\"display: none\">\n      <h3>Download (normalized timings)</h3>\n      <div id=\"rel_dl-container\" class=\"canvas-container\" width=\"600\" height=\"400\">\n        <canvas id=\"rel_dl_canvas\" class=\"canvassy\" width=\"600\" height=\"200\"></canvas>\n        <div class=\"resize-handle se\"></div>\n      </div>\n    </div>\n\n    <div id=\"abs-ul\" style=\"display: none\">\n      <h3>Upload (absolute timings)</h3>\n      <div id=\"abs_ul-container\" class=\"canvas-container\" width=\"600\" height=\"400\">\n        <canvas id=\"abs_ul_canvas\" class=\"canvassy\" width=\"600\" height=\"200\"></canvas>\n        <div class=\"resize-handle se\"></div>\n      </div>\n    </div>\n\n    <div id=\"rel-ul\" style=\"display: none\">\n      <h3>Upload (normalized timings)</h3>\n      <div id=\"rel_ul-container\" class=\"canvas-container\" width=\"600\" height=\"400\">\n        <canvas id=\"rel_ul_canvas\" class=\"canvassy\" width=\"600\" height=\"200\"></canvas>\n        <div class=\"resize-handle se\"></div>\n      </div>\n    </div>\n\n    <div id=\"pending\" style=\"display: none\">\n      <h2>Pending streams</h2>\n      <div id=\"pending-container\" class=\"canvas-container\" width=\"600\" height=\"400\">\n        <canvas id=\"pending_canvas\" class=\"canvassy\" width=\"600\" height=\"100\"></canvas>\n        <div class=\"resize-handle se\"></div>\n      </div>\n    </div>\n\n    <div id=\"events-top-container\" style=\"display: none\">\n      <h2>qlog event list</h2>\n      <button id=\"render-events-btn\">Render table</button>\n      <div id=\"events-loading\" style=\"display: none\">\n        <p>Loading event list..</p>\n        <div class=\"lds-ellipsis\"><div></div><div></div><div></div><div></div></div>\n      </div>\n      <!-- This following div is a skeleton for the WASM to fill in-->\n      <div id=\"events-container\"></div>\n    </div>\n\n    </div>\n  </body>\n</html>"
  },
  {
    "path": "qlog-dancer/qlog-dancer-ui.js",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nimport  {new_qlog_dancer} from './pkg/qlog_dancer.js';\n\nlet qlog_dancer = null;\nlet currentRectangle = null;\n\nconst OVERVIEW_CANVAS = \"overview_canvas\";\nconst CC_CANVAS = \"cc_canvas\";\nconst RTT_CANVAS = \"rtt_canvas\";\nconst FLOW_CONTROL_CANVAS = \"flow_control_canvas\";\nconst PKT_RX_CANVAS = \"pkt-rx-canvas\";\nconst PKT_TX_CANVAS = \"pkt-tx-canvas\";\nconst PKT_TX_COUNTS_CANVAS = \"pkt-tx-counts-canvas\";\nconst PKT_TX_DELTA_CANVAS = \"pkt-tx-delta-canvas\";\nconst PKT_TX_PACING_CANVAS = \"pkt-tx-pacing-canvas\";\nconst STREAM_MULTIPLEX_CANVAS = \"stream-multiplex-canvas\";\nconst STREAM_ABS_DL_CANVAS = \"abs_dl_canvas\";\nconst STREAM_REL_DL_CANVAS = \"rel_dl_canvas\"\nconst STREAM_ABS_UL_CANVAS = \"abs_ul_canvas\"\nconst STREAM_REL_UL_CANVAS = \"rel_ul_canvas\"\nconst PENDING_CANVAS = \"pending_canvas\"\n\nlet canvasMap = new Map();\ncanvasMap.set(OVERVIEW_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(CC_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(RTT_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(FLOW_CONTROL_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(PKT_RX_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(PKT_TX_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(PKT_TX_COUNTS_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(PKT_TX_DELTA_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(PKT_TX_PACING_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(STREAM_MULTIPLEX_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(STREAM_ABS_DL_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(STREAM_REL_DL_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(STREAM_ABS_UL_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(STREAM_REL_UL_CANVAS, {show_legend: false, x_start: null, x_end: null});\ncanvasMap.set(PENDING_CANVAS, {show_legend: false, x_start: null, x_end: null});\n\n\nfunction setupDarkmode() {\n    window.addEventListener(\"load\", (event) => {\n\n        let prefers = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n        let html = document.querySelector('html');\n\n        html.classList.add(prefers);\n        html.setAttribute('data-bs-theme', prefers);\n    });\n}\n\nlet showFcLegend = false;\nlet showPktRxLegend = false;\n\nfunction setupCanvas(div_id, canvas_id) {\n    const div = document.getElementById(div_id);\n    const dpr = window.devicePixelRatio || 1.0;\n    //const aspectRatio = canvas.width / canvas.height;\n    //const size = canvas.parentNode.offsetWidth * 0.8;\n    //canvas.style.width = size + \"px\";\n    //canvas.style.height = size / aspectRatio + \"px\";\n    //canvas.width = size;\n    //canvas.height = size / aspectRatio;\n\n    // Original dimensions\n    //const baseWidth = 600;\n    //const baseHeight = 400;\n\n    // Adjust for DPR\n    //div.style.width = `${baseWidth / dpr}px`;\n    //div.style.height = `${baseHeight / dpr}px`;\n\n        div.style.width = '100vw';\n\n        const canvas = document.getElementById(div_id);\n        canvas.width = div.clientWidth;\n        canvas.height = div.clientHeight;\n}\n\nasync function loadLog(event) {\n\n    if (!event || !event.target || !event.target.files || event.target.files.length === 0) {\n        console.log(\"big problem happened loading log\");\n        return;\n    }\n\n    let file = event.target.files[0];\n    console.log(`Loading ${file.name}`);\n    var loading = document.getElementById(\"loading\");\n    loading.style.display = \"block\";\n\n    qlog_dancer = new_qlog_dancer(file.name);\n\n    const start = Date.now();\n    const reader = file.stream().getReader();\n    while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        qlog_dancer.process_chunk(value);\n    }\n    const end = Date.now();\n    console.log(`Log read duration: ${end - start} ms`);\n\n    const ds_pop_start = Date.now();\n    qlog_dancer.populate_datastore();\n    const ds_pop_end = Date.now();\n    console.log(`populate datastore duration: ${ds_pop_end - ds_pop_start} ms`);\n\n    const packets_sent = qlog_dancer.total_packets_sent();\n    console.log(`packet sent events total = ${packets_sent}`);\n\n    const start_pop = Date.now();\n    qlog_dancer.populate_seriesstore();\n    const end_pop = Date.now();\n    console.log(`data processing of charts duration: ${end_pop - start_pop} ms`);\n\n    //setupCanvas(\"overview_container\", \"overview_canvas\")\n\n    // Default to draw overview page\n    toggleOverview()\n\n    var comp = document.getElementById(\"load-complete\");\n    loading.style.display = \"none\";\n    comp.style.display = \"block\";\n}\n\nfunction toggleElem(e) {\n    if (e.style.display === \"none\") {\n    e.style.display = \"block\";\n    return true;\n    } else {\n    e.style.display = \"none\";\n    return false;\n    }\n}\n\nfunction toggleLegend(event) {\n    let canvas_id = findCanvasIdInDiv(event);\n    let canvas = canvasMap.get(canvas_id);\n\n    if (canvas.show_legend) {\n        canvas.show_legend = false;\n    } else {\n        canvas.show_legend = true;\n    }\n\n    redrawByCanvasId(canvas_id);\n}\n\nfunction redrawByCanvasId(canvas_id) {\n    let sub_start_draw = Date.now();\n    let canvas = canvasMap.get(canvas_id);\n\n    if (canvas_id === OVERVIEW_CANVAS) {\n        qlog_dancer.draw_connection_overview(OVERVIEW_CANVAS, canvas.show_legend, canvas.x_start, canvas.x_end);\n    } else if (canvas_id === RTT_CANVAS) {\n        qlog_dancer.draw_rtt_plot(RTT_CANVAS, canvas.show_legend, canvas.x_start, canvas.x_end);\n    } else if (canvas_id === CC_CANVAS) {\n        qlog_dancer.draw_cc_plot(CC_CANVAS, canvas.show_legend, canvas.x_start, canvas.x_end);\n    } else if (canvas_id === FLOW_CONTROL_CANVAS) {\n        drawFlowControl(true);\n    } else if (canvas_id === PKT_RX_CANVAS) {\n        drawPacketReceived(true);\n    } else if (canvas_id === PKT_TX_CANVAS) {\n        qlog_dancer.draw_packet_sent_plot(PKT_TX_CANVAS, canvas.show_legend, canvas.x_start, canvas.x_end);\n    } else if (canvas_id === PKT_TX_COUNTS_CANVAS) {\n        qlog_dancer.draw_packet_sent_lost_delivered_count_plot(PKT_TX_COUNTS_CANVAS, canvas.show_legend, canvas.x_start, canvas.x_end);\n    }else if (canvas_id === PKT_TX_DELTA_CANVAS) {\n        qlog_dancer.draw_packet_sent_delta_plot(PKT_TX_DELTA_CANVAS, canvas.show_legend, canvas.x_start, canvas.x_end);\n    } else if (canvas_id === PKT_TX_PACING_CANVAS) {\n        qlog_dancer.draw_packet_sent_pacing_plot(PKT_TX_PACING_CANVAS, canvas.show_legend, canvas.x_start, canvas.x_end);\n    } else if (canvas_id === \"stream-multiplex-canvas\") {\n        drawStreamMultiplex(true);\n    } else if (canvas_id === \"abs_dl_canvas\" ||\n                canvas_id === \"abs_ul_canvas\" ||\n                canvas_id === \"rel_dl_canvas\" ||\n                canvas_id === \"rel_ul_canvas\") {\n        drawSparks(true)\n    } else if(canvas_id === \"pending_canvas\") {\n        drawPending(true)\n    } else {\n        console.log(\"unknown canvas id is \" + canvas_id)\n    }\n\n    console.log(`${canvas_id} draw duration: ${Date.now() - sub_start_draw} ms`);\n}\n\nfunction drawOverview(resize) {\n    if (resize || !window.overviewDrawn) {\n        let sub_start_draw = Date.now();\n        let overview_canvas = canvasMap.get(OVERVIEW_CANVAS);\n        qlog_dancer.draw_connection_overview(OVERVIEW_CANVAS, overview_canvas.show_legend, overview_canvas.x_start, overview_canvas.x_end);\n        let cc_canvas = canvasMap.get(CC_CANVAS);\n        qlog_dancer.draw_cc_plot(CC_CANVAS, cc_canvas.show_legend, cc_canvas.x_start, cc_canvas.x_end);\n        let rtt_canvas = canvasMap.get(RTT_CANVAS);\n        qlog_dancer.draw_rtt_plot(RTT_CANVAS, cc_canvas.show_legend, rtt_canvas.x_start, cc_canvas.x_end);\n        console.log(`complete conn overview draw duration: ${Date.now() - sub_start_draw} ms`);\n        window.overviewDrawn = true;\n        setupCanvasInteraction(OVERVIEW_CANVAS);\n        setupCanvasInteraction(CC_CANVAS);\n        setupCanvasInteraction(RTT_CANVAS);\n    } else {\n        console.log(\"no need to redraw overview\")\n    }\n}\n\nfunction drawFlowControl(resize) {\n    if (resize || !window.flowControlDrawn) {\n        let sub_start_draw = Date.now();\n        qlog_dancer.draw_flow_control(FLOW_CONTROL_CANVAS, showFcLegend);\n        console.log(`flow control draw duration: ${Date.now() - sub_start_draw} ms`);\n        window.flowControlDrawn = true;\n        setupCanvasInteraction(FLOW_CONTROL_CANVAS);\n    } else {\n    console.log(\"no need to redraw flow control\")\n    }\n}\n\nfunction drawPacketReceived(resize) {\n    if (resize || !window.packetReceivedDrawn) {\n        let sub_start_draw = Date.now();\n        qlog_dancer.draw_packet_received(PKT_RX_CANVAS, showPktRxLegend);\n        console.log(`packet received draw duration: ${Date.now() - sub_start_draw} ms`);\n        window.packetReceivedDrawn = true;\n        setupCanvasInteraction(PKT_RX_CANVAS);\n    } else {\n    console.log(\"no need to redraw pkt rx\")\n    }\n}\n\nfunction drawPacketSent(resize) {\n    if (resize || !window.packetSentDrawn) {\n        let sub_start_draw = Date.now();\n        let pkt_sent_canvas = canvasMap.get(PKT_TX_CANVAS);\n        qlog_dancer.draw_packet_sent_plot(PKT_TX_CANVAS, pkt_sent_canvas.show_legend, pkt_sent_canvas.x_start, pkt_sent_canvas.x_end);\n        let pkt_sent_counts_canvas = canvasMap.get(PKT_TX_COUNTS_CANVAS);\n        qlog_dancer.draw_packet_sent_lost_delivered_count_plot(PKT_TX_COUNTS_CANVAS, pkt_sent_counts_canvas.show_legend, pkt_sent_counts_canvas.x_start, pkt_sent_counts_canvas.x_end);\n        let pkt_sent_delta_canvas = canvasMap.get(PKT_TX_DELTA_CANVAS);\n        qlog_dancer.draw_packet_sent_delta_plot(PKT_TX_DELTA_CANVAS, pkt_sent_delta_canvas.show_legend, pkt_sent_delta_canvas.x_start, pkt_sent_delta_canvas.x_end);\n        let pkt_sent_pacing_canvas = canvasMap.get(PKT_TX_PACING_CANVAS);\n        qlog_dancer.draw_packet_sent_pacing_plot(PKT_TX_PACING_CANVAS, pkt_sent_pacing_canvas.show_legend, pkt_sent_pacing_canvas.x_start, pkt_sent_pacing_canvas.x_end);\n        console.log(`packet sent draw duration: ${Date.now() - sub_start_draw} ms`);\n        window.packetSentDrawn = true;\n        setupCanvasInteraction(PKT_TX_CANVAS);\n        setupCanvasInteraction(PKT_TX_COUNTS_CANVAS);\n        setupCanvasInteraction(PKT_TX_DELTA_CANVAS);\n        setupCanvasInteraction(PKT_TX_PACING_CANVAS);\n    } else {\n    console.log(\"no need to redraw pkt tx\")\n    }\n}\n\nfunction drawStreamMultiplex(resize) {\n    if (resize) {\n        let sub_start_draw = Date.now();\n        qlog_dancer.draw_stream_multiplexing(\"stream-multiplex-canvas\");\n        console.log(`stream multiplex draw duration: ${Date.now() - sub_start_draw} ms`);\n        return;\n    }\n\n    if (!window.streamMultiplexDrawn) {\n        //setupCanvas(\"stream-multiplex-canvas\");\n        let sub_start_draw = Date.now();\n        qlog_dancer.draw_stream_multiplexing(\"stream-multiplex-canvas\");\n        console.log(`stream multiplex draw duration: ${Date.now() - sub_start_draw} ms`);\n        window.streamMultiplexDrawn = true;\n    } else {\n    console.log(\"no need to redraw stream multiplex\")\n    }\n}\n\nfunction drawSparks(resize) {\n    if (resize) {\n        let sub_start_draw = Date.now();\n        qlog_dancer.draw_sparks(\"abs_dl_canvas\", \"rel_dl_canvas\", \"abs_ul_canvas\", \"rel_ul_canvas\")\n        console.log(`sparks draw duration: ${Date.now() - sub_start_draw} ms`);\n        return;\n    }\n\n    if (!window.streamSparksDrawn) {\n        let sub_start_draw = Date.now();\n        qlog_dancer.draw_sparks(\"abs_dl_canvas\", \"rel_dl_canvas\", \"abs_ul_canvas\", \"rel_ul_canvas\")\n        console.log(`sparks draw duration: ${Date.now() - sub_start_draw} ms`);\n        window.streamSparksDrawn = true;\n    } else {\n        console.log(\"no need to redraw\")\n    }\n}\n\nfunction drawPending(resize) {\n    if (resize) {\n        let sub_start_draw = Date.now();\n        qlog_dancer.draw_pending(\"pending_canvas\");\n        console.log(`pending draw duration: ${Date.now() - sub_start_draw} ms`);\n        return;\n    }\n\n    if (!window.pendingDrawn) {\n        let sub_start_draw = Date.now();\n        qlog_dancer.draw_pending(\"pending_canvas\");\n        console.log(`pending draw duration: ${Date.now() - sub_start_draw} ms`);\n        window.pendingDrawn = true;\n    } else {\n        console.log(\"no need to redraw\")\n    }\n}\n\nfunction toggleOverview() {\n    if (document.getElementById(\"overview-btn\").innerText == \"Overview shown\") {\n        document.getElementById(\"overview-btn\").innerText = \"Overview hidden\";\n    } else {\n        document.getElementById(\"overview-btn\").innerText = \"Overview shown\";\n    }\n    if (toggleElem(document.getElementById(\"overview\"))) {\n        drawOverview(false)\n    }\n}\n\nfunction toggleFlowControl() {\n    if (document.getElementById(\"flow-control-btn\").innerText == \"Flow control shown\") {\n        document.getElementById(\"flow-control-btn\").innerText = \"Flow control hidden\";\n    } else {\n        document.getElementById(\"flow-control-btn\").innerText = \"Flow control shown\";\n    }\n    if (toggleElem(document.getElementById(\"flow-control\"))) {\n        drawFlowControl()\n    }\n}\n\nfunction togglePacketReceived() {\n    if (document.getElementById(\"pkt-rx-btn\").innerText == \"Packet rx shown\") {\n        document.getElementById(\"pkt-rx-btn\").innerText = \"Packet rx hidden\";\n    } else {\n        document.getElementById(\"pkt-rx-btn\").innerText = \"Packet rx shown\";\n    }\n    if (toggleElem(document.getElementById(\"pkt-rx\"))) {\n        drawPacketReceived()\n    }\n}\n\nfunction togglePacketSent() {\n    if (document.getElementById(\"pkt-tx-btn\").innerText == \"Packet tx shown\") {\n        document.getElementById(\"pkt-tx-btn\").innerText = \"Packet tx hidden\";\n    } else {\n        document.getElementById(\"pkt-tx-btn\").innerText = \"Packet tx shown\";\n    }\n    if (toggleElem(document.getElementById(\"pkt-tx\"))) {\n        drawPacketSent()\n    }\n}\n\nfunction toggleStreamMultiplex() {\n    if (document.getElementById(\"stream-multiplex-btn\").innerText == \"Stream multiplexing shown\") {\n        document.getElementById(\"stream-multiplex-btn\").innerText = \"Stream multiplexing hidden\";\n    } else {\n        document.getElementById(\"stream-multiplex-btn\").innerText = \"Stream multiplexing shown\";\n    }\n    if (toggleElem(document.getElementById(\"stream-multiplex\"))) {\n        drawStreamMultiplex()\n    }\n}\n\nfunction togglePending() {\n    if (document.getElementById(\"pending-btn\").innerText == \"Stream pending shown\") {\n        document.getElementById(\"pending-btn\").innerText = \"Stream pending hidden\";\n    } else {\n        document.getElementById(\"pending-btn\").innerText = \"Stream pending shown\";\n    }\n    if (toggleElem(document.getElementById(\"pending\"))) {\n        drawPending()\n    }\n}\n\nfunction toggleSparks() {\n    if (document.getElementById(\"sparks-btn\").innerText == \"Stream sparks shown\") {\n        document.getElementById(\"sparks-btn\").innerText = \"Stream sparks hidden\";\n    } else {\n        document.getElementById(\"sparks-btn\").innerText = \"Stream sparks shown\";\n    }\n    // Base redraw decision on just one, since they are all kept in sync\n    let redraw = toggleElem(document.getElementById(\"abs-dl\"));\n    toggleElem(document.getElementById(\"rel-dl\"));\n    toggleElem(document.getElementById(\"abs-ul\"));\n    toggleElem(document.getElementById(\"rel-ul\"));\n    if (redraw) {\n        drawSparks()\n    }\n}\n\nlet recalc_events = true;\n\nfunction toggleEventList() {\nif (document.getElementById(\"event-list-btn\").innerText == \"Events shown\") {\n        document.getElementById(\"event-list-btn\").innerText = \"Events hidden\";\n    } else {\n        document.getElementById(\"event-list-btn\").innerText = \"Events shown\";\n    }\n    if (toggleElem(document.getElementById(\"events-top-container\"))) {\n\n    }\n}\n\nfunction render_events_table() {\n    if (recalc_events) {\n        var loading = document.getElementById(\"events-loading\");\n        loading.style.display = \"block\";\n\n        // Use intersection observer to detect when div is actually visible\n        const observer = new IntersectionObserver((entries) => {\n            if (entries[0].isIntersecting) {\n                observer.disconnect();\n\n                // Div is definitely visible now\n                setTimeout(() => {\n                    populate_event_table();\n\n                    setupTable();\n                    loading.style.display = \"none\";\n                }, 0);\n            }\n        });\n\n        observer.observe(loading);\n\n\n    }\n}\n\nfunction populate_event_table() {\n    if (recalc_events) {\n        qlog_dancer.populate_event_table(\"events-container\");\n        recalc_events = false;\n\n    }\n}\n\nfunction setupTable() {\n    new DataTable('table.log-dancer-table',\n        {\n            paging: false,\n            dom: '<\"center\" flpti  >'\n        });\n}\n\nfunction findCanvasIdInDiv(event) {\n    let canvas_id = null;\n    // Get the div where the click happened\n    const clickedDiv = event.target.closest('div');\n\n    if (clickedDiv) {\n        // Find the canvas element within the div\n        const canvas = clickedDiv.querySelector('canvas');\n\n        if (canvas && canvas.id) {\n            return canvas.id;\n        } else {\n            console.log('No canvas with ID found in the clicked div');\n            return null;\n        }\n    } else {\n        console.log('No parent div found');\n        return null ;\n    }\n}\n\nfunction resetZoom(event) {\n    console.log(\"resetty\");\n\n    let canvas_id = findCanvasIdInDiv(event);\n    let canvas = canvasMap.get(canvas_id);\n    canvas.x_start = null;\n    canvas.x_end = null;\n    redrawByCanvasId(canvas_id);\n}\n\nconst input = document.querySelector(\"input\");\ninput.addEventListener(\"input\", loadLog);\n\ndocument.getElementById(\"overview-btn\").addEventListener(\"click\", toggleOverview);\ndocument.getElementById(\"flow-control-btn\").addEventListener(\"click\", toggleFlowControl);\ndocument.getElementById(\"pkt-rx-btn\").addEventListener(\"click\", togglePacketReceived);\ndocument.getElementById(\"pkt-tx-btn\").addEventListener(\"click\", togglePacketSent);\ndocument.getElementById(\"stream-multiplex-btn\").addEventListener(\"click\", toggleStreamMultiplex);\ndocument.getElementById(\"sparks-btn\").addEventListener(\"click\", toggleSparks);\ndocument.getElementById(\"pending-btn\").addEventListener(\"click\", togglePending);\ndocument.getElementById(\"event-list-btn\").addEventListener(\"click\", toggleEventList);\ndocument.getElementById(\"render-events-btn\").addEventListener(\"click\", render_events_table);\n\ndocument.getElementById(\"overview-reset-btn\").addEventListener(\"click\", resetZoom);\ndocument.getElementById(\"cc-reset-btn\").addEventListener(\"click\", resetZoom);\ndocument.getElementById(\"rtt-reset-btn\").addEventListener(\"click\", resetZoom);\ndocument.getElementById(\"pkt-tx-reset-btn\").addEventListener(\"click\", resetZoom);\ndocument.getElementById(\"pkt-tx-counts-reset-btn\").addEventListener(\"click\", resetZoom);\ndocument.getElementById(\"pkt-tx-delta-reset-btn\").addEventListener(\"click\", resetZoom);\ndocument.getElementById(\"pkt-tx-pacing-reset-btn\").addEventListener(\"click\", resetZoom);\n\ndocument.getElementById(\"overview-toggle-legend-btn\").addEventListener(\"click\", toggleLegend);\ndocument.getElementById(\"cc-toggle-legend-btn\").addEventListener(\"click\", toggleLegend);\ndocument.getElementById(\"rtt-toggle-legend-btn\").addEventListener(\"click\", toggleLegend);\ndocument.getElementById(\"pkt-tx-toggle-legend-btn\").addEventListener(\"click\", toggleLegend);\ndocument.getElementById(\"pkt-tx-counts-toggle-legend-btn\").addEventListener(\"click\", toggleLegend);\ndocument.getElementById(\"pkt-tx-delta-toggle-legend-btn\").addEventListener(\"click\", toggleLegend);\ndocument.getElementById(\"pkt-tx-pacing-toggle-legend-btn\").addEventListener(\"click\", toggleLegend);\n\nfunction setupCanvasResizer(canvas_id) {\n    const canvas = document.getElementById(canvas_id);\n    const container = canvas.parentElement;\n    const handle = container.querySelector('.resize-handle');\n    const ctx = canvas.getContext('2d');\n\n    // Variables to track resize state\n    let isResizing = false;\n    let startX, startY, startWidth, startHeight;\n    let canvas_id_me = canvas_id;\n\n    // Mouse down event on resize handle\n    handle.addEventListener('mousedown', (e) => {\n        isResizing = true;\n        startX = e.clientX;\n        startY = e.clientY;\n        startWidth = canvas.width;\n        startHeight = canvas.height;\n        document.addEventListener('mousemove', onMouseMove);\n        document.addEventListener('mouseup', onMouseUp);\n        e.preventDefault();\n    });\n\n    // Mouse move event for resizing\n    function onMouseMove(e) {\n        if (!isResizing) return;\n\n        const newWidth = startWidth + (e.clientX - startX);\n        const newHeight = startHeight + (e.clientY - startY);\n\n        // Ensure minimum size\n        if (newWidth > 50 && newHeight > 50) {\n            canvas.width = newWidth;\n            canvas.height = newHeight;\n\n        }\n    }\n\n    // Mouse up event to stop resizing\n    function onMouseUp() {\n        isResizing = false;\n        redrawByCanvasId(canvas_id_me);\n\n        document.removeEventListener('mousemove', onMouseMove);\n        document.removeEventListener('mouseup', onMouseUp);\n    }\n}\n\n// Setup all canvas'\ncanvasMap.forEach((value, key) => {\n    setupCanvasResizer(key);\n});\n\nfunction canvasToPlotCoords(canvasX, canvasY, chartBounds, plotRanges) {\n    const plotPixelX = canvasX - chartBounds.left;\n    const plotPixelY = canvasY - chartBounds.top;\n\n    if (plotPixelX < 0 || plotPixelX > chartBounds.width ||\n        plotPixelY < 0 || plotPixelY > chartBounds.height) {\n        return null;\n    }\n\n    const normalizedX = plotPixelX / chartBounds.width;\n    const normalizedY = plotPixelY / chartBounds.height;\n\n    const plotX = plotRanges.x_min + normalizedX * (plotRanges.x_max - plotRanges.x_min);\n    const plotY = plotRanges.y_max - normalizedY * (plotRanges.y_max - plotRanges.y_min);\n\n    return { x: plotX, y: plotY };\n}\n\nfunction setupCanvasInteraction(canvasId) {\n    const canvas = document.getElementById(canvasId);\n    const container = canvas.parentElement;\n    const tooltip = document.getElementById('tooltip');\n    let [chartBounds, plotRanges] = qlog_dancer.get_chart_info(canvasId);\n    let isMouseOverCanvas = false;\n    let isDrawingRect = false;\n    let startX, startY, startXPlot, startYPlot;\n\n    function tidyCurrentRectangle() {\n        isDrawingRect = false;\n        if (currentRectangle) {\n        currentRectangle.remove();\n        }\n        currentRectangle = null;\n    }\n\n    canvas.addEventListener('mousedown', (e) => {\n        isDrawingRect = true;\n        const rect = canvas.getBoundingClientRect();\n        startX = e.clientX - rect.left;\n        startY = e.clientY - rect.top;\n\n        let [chartBounds, plotRanges] = qlog_dancer.get_chart_info(canvasId);\n        const plotCoords = canvasToPlotCoords(startX, startY, chartBounds, plotRanges);\n        if (plotCoords) {\n        startXPlot = plotCoords.x;\n        startYPlot = plotCoords.y;\n\n        // Create a new rectangle div\n        currentRectangle = document.createElement('div');\n        currentRectangle.className = 'rectangle';\n        currentRectangle.style.left = startX + 'px';\n        currentRectangle.style.top = startY + 'px';\n        currentRectangle.style.width = '0px';\n        currentRectangle.style.height = '0px';\n        container.appendChild(currentRectangle);\n        }\n    });\n\n        // Mouse move handler for live tooltip\n        canvas.addEventListener('mousemove', function(event) {\n            const rect = canvas.getBoundingClientRect();\n            const canvasX = event.clientX - rect.left;\n            const canvasY = event.clientY - rect.top;\n\n            let [chartBounds, plotRanges] = qlog_dancer.get_chart_info(canvasId);\n            const plotCoords = canvasToPlotCoords(canvasX, canvasY, chartBounds, plotRanges);\n\n            if (plotCoords) {\n                isMouseOverCanvas = true;\n                updateTooltip(event, plotCoords, tooltip);\n            } else {\n                hideTooltip(tooltip);\n                isMouseOverCanvas = false;\n            }\n\n            if (isDrawingRect && currentRectangle) {\n                // Calculate rectangle dimensions\n                const width = Math.abs(canvasX - startX);\n                const height = Math.abs(canvasY - startY);\n\n                // Update rectangle position and size\n                currentRectangle.style.left = Math.min(startX, canvasX) + 'px';\n                currentRectangle.style.top = Math.min(startY, canvasY) + 'px';\n                currentRectangle.style.width = width + 'px';\n                currentRectangle.style.height = height + 'px';\n            }\n        });\n\n        // Hide tooltip when mouse leaves canvas\n        canvas.addEventListener('mouseleave', function() {\n            hideTooltip(tooltip);\n            isMouseOverCanvas = false;\n            tidyCurrentRectangle();\n\n        });\n\n        canvas.addEventListener('mouseup', function() {\n            const rect = canvas.getBoundingClientRect();\n            const canvasX = event.clientX - rect.left;\n            const canvasY = event.clientY - rect.top;\n\n            let [chartBounds, plotRanges] = qlog_dancer.get_chart_info(canvasId);\n            const plotCoords = canvasToPlotCoords(canvasX, canvasY, chartBounds, plotRanges);\n\n            if (plotCoords) {\n                let canvas_data = canvasMap.get(canvas.id);\n                canvas_data.x_start = startXPlot.toFixed(1);\n                canvas_data.x_end = plotCoords.x.toFixed(1);\n\n                redrawByCanvasId(canvas.id);\n\n                console.log(`Start at: (${startXPlot.toFixed(1)}, ${startYPlot.toFixed(1)}), end at (${plotCoords.x.toFixed(1)}, ${plotCoords.y.toFixed(1)})`);\n            }\n\n            isDrawingRect = false;\n            tidyCurrentRectangle();\n\n\n        });\n\n        // Show tooltip when mouse enters canvas\n        canvas.addEventListener('mouseenter', function() {\n            isMouseOverCanvas = true;\n        });\n}\n\nfunction updateTooltip(event, plotCoords, tooltip) {\n    // Update tooltip content\n    tooltip.innerHTML = `\n        <div class=\"tooltip-content\">\n            <div class=\"coord-line\">X: <span class=\"coord-value\">${plotCoords.x.toFixed(0)}</span></div>\n            <div class=\"coord-line\">Y: <span class=\"coord-value\">${plotCoords.y.toFixed(0)}</span></div>\n        </div>\n    `;\n\n    // Position tooltip near mouse cursor\n    const tooltipOffset = 15;\n    let tooltipX = event.pageX + tooltipOffset;\n    let tooltipY = event.pageY - tooltipOffset;\n\n    // Prevent tooltip from going off-screen\n    const tooltipRect = tooltip.getBoundingClientRect();\n    const viewportWidth = window.innerWidth;\n    const viewportHeight = window.innerHeight;\n\n    // Adjust horizontal position if tooltip would go off right edge\n    if (tooltipX + tooltipRect.width > viewportWidth) {\n        tooltipX = event.pageX - tooltipRect.width - tooltipOffset;\n    }\n\n    // Adjust vertical position if tooltip would go off top edge\n    if (tooltipY < 0) {\n        tooltipY = event.pageY + tooltipOffset;\n    }\n\n    tooltip.style.left = tooltipX + 'px';\n    tooltip.style.top = tooltipY + 'px';\n    tooltip.style.display = 'block';\n    tooltip.style.opacity = '1';\n}\n\nfunction hideTooltip(tooltip) {\n    tooltip.style.display = 'none';\n    tooltip.style.opacity = '0';\n}"
  },
  {
    "path": "qlog-dancer/qlog-dancer.css",
    "content": ".canvas-container {\n  position: relative;\n  display: inline-block;\n  border: 1px solid #000;\n}\n\n.canvas-control {\n    display: block;\n}\n\n.resize-handle {\n  position: absolute;\n  width: 10px;\n  height: 10px;\n  background-color: #4285f4;\n  border-radius: 50%;\n}\n\n.resize-handle.se {\n  bottom: -5px;\n  right: -5px;\n  cursor: nwse-resize;\n}\n\n.line {\n  width: 53px;\n  height: 0;\n  border: 1px solid #C4C4C4;\n  margin: 3px;\n  display:inline-block;\n}\n\n.canvassy {\n  cursor: crosshair;\n}\n\n#tooltip {\n  position: absolute;\n  background: rgba(0, 0, 0, 0.8);\n  color: white;\n  padding: 5px;\n  border-radius: 3px;\n  display: none;\n  pointer-events: none;\n  z-index: 1000;\n}\n\n.rectangle {\n  position: absolute;\n  background-color: rgba(20, 45, 187, 0.5);\n  border: 2px solid rgba(15, 34, 142, 0.8);\n  pointer-events: none;\n}\n\n.center {\n  margin: auto;\n  width: 90%;\n  padding: 10px;\n}\n\n.yellow {\n    color: yellow\n}\n\n.red {\n    color: red\n}\n\n.green {\n    color: limegreen\n}\n\nlds-ellipsis {\n  color: #1c4c5b\n}\n.lds-ellipsis,\n.lds-ellipsis div {\n  box-sizing: border-box;\n}\n.lds-ellipsis {\n  display: inline-block;\n  position: relative;\n  width: 80px;\n  height: 80px;\n}\n.lds-ellipsis div {\n  position: absolute;\n  top: 33.33333px;\n  width: 13.33333px;\n  height: 13.33333px;\n  border-radius: 50%;\n  background: currentColor;\n  animation-timing-function: cubic-bezier(0, 1, 1, 0);\n}\n.lds-ellipsis div:nth-child(1) {\n  left: 8px;\n  animation: lds-ellipsis1 0.6s infinite;\n}\n.lds-ellipsis div:nth-child(2) {\n  left: 8px;\n  animation: lds-ellipsis2 0.6s infinite;\n}\n.lds-ellipsis div:nth-child(3) {\n  left: 32px;\n  animation: lds-ellipsis2 0.6s infinite;\n}\n.lds-ellipsis div:nth-child(4) {\n  left: 56px;\n  animation: lds-ellipsis3 0.6s infinite;\n}\n@keyframes lds-ellipsis1 {\n  0% {\n    transform: scale(0);\n  }\n  100% {\n    transform: scale(1);\n  }\n}\n@keyframes lds-ellipsis3 {\n  0% {\n    transform: scale(1);\n  }\n  100% {\n    transform: scale(0);\n  }\n}\n@keyframes lds-ellipsis2 {\n  0% {\n    transform: translate(0, 0);\n  }\n  100% {\n    transform: translate(24px, 0);\n  }\n}"
  },
  {
    "path": "qlog-dancer/src/config.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::HashSet;\n\nuse clap::Arg;\nuse clap::ArgAction;\nuse clap::Command;\n\nuse crate::datastore::PrintStatsConfig;\nuse crate::plots::colors::PlotColors;\nuse crate::plots::colors::DARK_MODE;\nuse crate::plots::colors::LIGHT_MODE;\nuse crate::plots::stream_sparks::SparkPlotsParams;\nuse crate::plots::ClampParams;\nuse crate::SerializationFormat;\n\n#[derive(Debug)]\npub struct AppConfig {\n    pub file: String,\n    pub filename: String,\n\n    pub charts_dir: String,\n    pub plot_conn_overview: bool,\n    pub plot_pkt_sent: bool,\n    pub plot_pkt_received: bool,\n    pub plot_conn_flow_control: bool,\n    pub plot_sparks: bool,\n    pub plot_multiplex: bool,\n    pub plot_pending: bool,\n    pub sparks_layout: SparkPlotsParams,\n\n    pub report_text: bool,\n    pub report_omit_upload: bool,\n    pub report_omit_priorities: bool,\n    pub report_text_csv: bool,\n    pub report_html: bool,\n\n    pub dark_mode: bool,\n\n    pub start: Option<f64>,\n    pub end: Option<f64>,\n\n    pub stream_y_max: Option<u64>,\n    pub cwnd_y_max: Option<u64>,\n\n    pub netlog_filter: HashSet<String>,\n    pub qlog_wirefilter: Option<String>,\n\n    pub stats_config: PrintStatsConfig,\n    pub ignore_acks: bool,\n\n    pub log_format: SerializationFormat,\n}\n\nimpl AppConfig {\n    pub fn colors(dark_mode: bool) -> PlotColors {\n        if dark_mode {\n            DARK_MODE\n        } else {\n            LIGHT_MODE\n        }\n    }\n\n    pub fn from_clap() -> std::result::Result<Self, String> {\n        let mut matches = Command::new(\"qlog-dancer\")\n        .version(\"v0.1.0\")\n        .about(\"dances with qlog (and more!)\")\n        .arg(\n            Arg::new(\"LOG FILE\")\n                .help(\"Sets the input log file to use\")\n                .required(true)\n                .index(1),\n        )\n        .arg(\n            Arg::new(\"charts\")\n                .long(\"charts\")\n                .help(\"Type of charts to plot\")\n                .value_parser([\n                    \"all\",\n                    \"overview\",\n                    \"pkt-rx\",\n                    \"sparks\",\n                    \"multiplex\",\n                    \"pending\",\n                    \"conn-flow\",\n                    \"none\",\n                ])\n                .default_value(\"none\"),\n        )\n        .arg(\n            Arg::new(\"charts_directory\")\n                .long(\"charts-directory\")\n                .help(\"Sets the output directory for charts\"),\n        )\n        .arg(\n            Arg::new(\"sparks_layout\")\n                .long(\"sparks-layout\")\n                .help(\"Layout of spark charts\")\n                .value_parser([\"grid\", \"vert\"])\n                .default_value(\"vert\"),\n        )\n        .arg(\n            Arg::new(\"start\")\n                .short('s')\n                .help(\"Relative start time for plots\")\n        )\n        .arg(\n            Arg::new(\"end\")\n                .short('e')\n                .help(\"Relative end time for plots\")\n        )\n        .arg(\n            Arg::new(\"stream_y_axis_max\")\n                .long(\"stream-y-axis_max\")\n                .help(\"Maximum value of Y axis on stream-related charts\")\n        )\n        .arg(\n            Arg::new(\"cwnd_y_axis_max\")\n                .long(\"cwnd-y-axis-max\")\n                .help(\"Maximum value of Y axis on cwnd-related charts\")\n        )\n        .arg(\n            Arg::new(\"netlog_filter\")\n                .long(\"netlog-filter\")\n                .help(\"A comma-seperated list of hostnames to filter in to netlog analysis. By default, all hostname are analysed.\")\n        )\n        .arg(\n            Arg::new(\"qlog_wirefilter\")\n                .long(\"qlog-wirefilter\")\n                .help(\"A Wirefilter expression for Wireshark-like matching of qlog events.\")\n        )\n        .arg(\n            Arg::new(\"dark_mode\")\n                .long(\"dark_mode\")\n                .help(\"Generate outputs in a dark mode style\")\n                .action(ArgAction::SetTrue),\n        )\n        .arg(\n            Arg::new(\"print_stats\")\n                .long(\"print-stats\")\n                .help(\"Print stats about qlog events\")\n                .value_parser([\"all\", \"stream\", \"packets\", \"none\"])\n                .default_value(\"none\"),\n        )\n        .arg(\n            Arg::new(\"report_text\")\n                .long(\"report-text\")\n                .help(\"Generate log reports in text format\")\n                .action(ArgAction::SetTrue),\n        )\n        .arg(\n            Arg::new(\"report_html\")\n                .long(\"report-html\")\n                .help(\"Generate log reports in HTML format\")\n                .action(ArgAction::SetTrue),\n        )\n        .arg(\n            Arg::new(\"report_omit_upload\")\n                .long(\"report-omit-upload\")\n                .help(\"Omit printing columns about HTTP uploads\")\n                .action(ArgAction::SetTrue),\n        )\n        .arg(\n            Arg::new(\"report_omit_priorities\")\n                .long(\"report-omit-priorities\")\n                .help(\"Omit printing columns about HTTP priorities\")\n                .action(ArgAction::SetTrue),\n        )\n        .arg(\n            Arg::new(\"report_text_csv\")\n                .long(\"report-text-csv\")\n                .help(\"Tables printed in a CSV-compatible format\")\n                .action(ArgAction::SetTrue),\n        )\n        .get_matches();\n\n        let file = matches.remove_one::<String>(\"LOG FILE\").unwrap();\n        let filename = std::path::Path::new(&file)\n            .file_name()\n            .unwrap()\n            .to_str()\n            .unwrap();\n\n        let charts_dir = matches\n            .remove_one::<String>(\"charts_directory\")\n            .unwrap_or(format!(\"{filename}-charts\"));\n\n        let start = matches.remove_one::<f64>(\"start\");\n        let end = matches.remove_one::<f64>(\"end\");\n\n        if end.is_some() && end < start {\n            return Err(\"End time cannot be earlier than start time.\".into());\n        }\n\n        if let Some(start) = start {\n            if start < 0.0 {\n                return Err(\"Start time cannot be less than 0.0.\".into());\n            }\n        }\n\n        if let Some(end) = end {\n            if end < 0.0 {\n                return Err(\"End time cannot be less than 0.0.\".into());\n            }\n        }\n\n        let stream_y_max =\n            matches.remove_one::<i64>(\"stream_y_axis_max\").unwrap_or(-1);\n        let stream_y_max = if stream_y_max == -1 {\n            None\n        } else {\n            Some(stream_y_max as u64)\n        };\n\n        let cwnd_y_max =\n            matches.remove_one::<i64>(\"cwnd_y_axis_max\").unwrap_or(-1);\n        let cwnd_y_max = if cwnd_y_max == -1 {\n            None\n        } else {\n            Some(cwnd_y_max as u64)\n        };\n\n        let dark_mode = matches.get_flag(\"dark_mode\");\n\n        let charts = matches.remove_one::<String>(\"charts\").unwrap();\n        let (\n            plot_conn_overview,\n            plot_pkt_sent,\n            plot_pkt_received,\n            plot_sparks,\n            plot_multiplex,\n            plot_pending,\n            plot_conn_flow_control,\n        ) = match charts.as_str() {\n            \"all\" => (true, true, true, true, true, true, true),\n\n            \"overview\" => (true, true, false, false, false, false, false),\n\n            \"pkt-rx\" => (false, false, true, false, false, false, false),\n\n            \"sparks\" => (false, false, false, true, false, false, false),\n\n            \"multiplex\" => (false, false, false, false, true, false, false),\n\n            \"pending\" => (false, false, false, false, false, true, false),\n\n            \"conn-flow\" => (false, false, false, false, false, false, true),\n\n            \"none\" => (false, false, false, false, false, false, false),\n\n            _ => unreachable!(),\n        };\n\n        let sparks_layout =\n            matches.remove_one::<String>(\"sparks_layout\").unwrap();\n        let sparks_layout = if sparks_layout == \"grid\" {\n            SparkPlotsParams {\n                clamp: ClampParams {\n                    start,\n                    end,\n                    stream_y_max,\n                },\n                colors: Self::colors(dark_mode),\n                ..Default::default()\n            }\n        } else {\n            SparkPlotsParams {\n                clamp: ClampParams {\n                    start,\n                    end,\n                    stream_y_max,\n                },\n                colors: Self::colors(dark_mode),\n                sparks_per_row: 1,\n                captions_on_top: false,\n                spark_dimension_x: 600,\n                caption_area_width: 600,\n                label_area_height: 1,\n                ..Default::default()\n            }\n        };\n\n        let netlog_filter = match matches.remove_one::<String>(\"netlog_filter\") {\n            Some(filter_string) => filter_string\n                .split(',')\n                .map(|v| v.to_string())\n                .collect::<HashSet<String>>(),\n\n            None => HashSet::new(),\n        };\n\n        let qlog_wirefilter = matches.remove_one::<String>(\"qlog_wirefilter\");\n\n        let report_text = matches.get_flag(\"report_text\");\n        let report_omit_upload = matches.get_flag(\"report_omit_upload\");\n        let report_omit_priorities = matches.get_flag(\"report_omit_priorities\");\n        let report_text_csv = matches.get_flag(\"report_text_csv\");\n        let report_html = matches.get_flag(\"report_html\");\n\n        let print_stats = matches.remove_one::<String>(\"print_stats\").unwrap();\n        let stats_config = match print_stats.as_str() {\n            \"all\" => PrintStatsConfig {\n                rx_flow_control: true,\n                tx_flow_control: true,\n                reset_streams: true,\n                stream_buffering: true,\n                tx_stream_frames: true,\n                packet_stats: true,\n            },\n\n            \"stream\" => PrintStatsConfig {\n                rx_flow_control: true,\n                tx_flow_control: true,\n                reset_streams: true,\n                stream_buffering: true,\n                tx_stream_frames: true,\n                packet_stats: false,\n            },\n\n            \"packets\" => PrintStatsConfig {\n                rx_flow_control: false,\n                tx_flow_control: false,\n                reset_streams: false,\n                stream_buffering: false,\n                tx_stream_frames: false,\n                packet_stats: true,\n            },\n\n            \"none\" => PrintStatsConfig {\n                rx_flow_control: false,\n                tx_flow_control: false,\n                reset_streams: false,\n                stream_buffering: false,\n                tx_stream_frames: false,\n                packet_stats: false,\n            },\n\n            _ => unreachable!(),\n        };\n\n        let ignore_acks = false;\n\n        let qlog_ext = std::path::Path::new(&file)\n            .extension()\n            .unwrap()\n            .to_str()\n            .unwrap();\n        let log_format = SerializationFormat::from_file_extension(qlog_ext);\n\n        let config = Self {\n            file: file.to_string(),\n            filename: filename.to_string(),\n            charts_dir: charts_dir.to_string(),\n            plot_conn_overview,\n            plot_pkt_sent,\n            plot_pkt_received,\n            plot_conn_flow_control,\n            plot_sparks,\n            plot_multiplex,\n            plot_pending,\n            sparks_layout,\n            report_text,\n            report_omit_upload,\n            report_omit_priorities,\n            report_text_csv,\n            report_html,\n            dark_mode,\n            start,\n            end,\n            stream_y_max,\n            cwnd_y_max,\n            netlog_filter,\n            qlog_wirefilter,\n            stats_config,\n            ignore_acks,\n            log_format,\n        };\n\n        Ok(config)\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/datastore.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\nuse std::collections::BTreeSet;\nuse std::collections::HashMap;\nuse std::collections::HashSet;\nuse std::convert::TryFrom;\nuse std::fmt::Debug;\nuse std::fmt::Display;\n\nuse log::error;\nuse log::trace;\nuse qlog::events::http3::Http3Frame;\nuse qlog::events::quic::AckedRanges;\nuse qlog::events::quic::QuicFrame;\nuse qlog::events::quic::TransportInitiator;\nuse qlog::events::EventData;\nuse qlog::events::RawInfo;\n\nuse regex::Regex;\nuse tabled::Tabled;\n\nuse crate::request_stub::find_header_value;\nuse crate::request_stub::HttpRequestStub;\nuse crate::request_stub::NaOption;\nuse crate::trackers::StreamBufferTracker;\nuse crate::trackers::StreamMaxTracker;\nuse crate::LogFileData;\nuse crate::PacketType;\nuse crate::QlogPointRtt;\nuse crate::QlogPointu64;\nuse crate::RawLogEvents::Netlog;\nuse netlog;\nuse netlog::h2;\nuse netlog::h2::Event::*;\nuse netlog::h2::*;\nuse netlog::h3;\nuse netlog::h3::Event::*;\nuse netlog::http;\nuse netlog::quic;\nuse netlog::quic::Event::*;\nuse netlog::quic::*;\nuse netlog::read_netlog_record;\n\npub type ParseResult<T> = Result<T, serde_json::Error>;\n#[derive(Debug, Clone)]\npub struct PacketInfoStub {\n    pub acked: Option<bool>,\n    pub raw: Option<RawInfo>,\n    pub created_time: f64,\n    pub send_at_time: Option<f64>,\n    pub ty: PacketType,\n    pub number: u64,\n}\n\n#[derive(Clone, Debug)]\npub struct StreamAccess {\n    pub offset: u64,\n    pub length: u64,\n}\n\n#[derive(Default, Debug)]\npub struct PrintStatsConfig {\n    pub rx_flow_control: bool,\n    pub tx_flow_control: bool,\n    pub reset_streams: bool,\n    pub stream_buffering: bool,\n    pub tx_stream_frames: bool,\n    pub packet_stats: bool,\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct RequestAtServerDeltas {\n    pub rx_hdr_tx_hdr: NaOption<f64>,\n    pub rx_hdr_tx_first_data: NaOption<f64>,\n    pub rx_hdr_tx_last_data: NaOption<f64>,\n    pub tx_first_data_tx_last_data: NaOption<f64>,\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct RequestAtClientDeltas {\n    pub discover_tx_hdr: NaOption<f64>,\n    pub tx_hdr_rx_hdr: NaOption<f64>,\n    pub tx_hdr_rx_first_data: NaOption<f64>,\n    pub tx_hdr_rx_last_data: NaOption<f64>,\n    pub tx_first_data_tx_last_data: NaOption<f64>,\n    pub rx_first_data_rx_last_data: NaOption<f64>,\n    pub rx_hdr_rx_last_data: NaOption<f64>,\n}\n\n#[derive(Debug, Default)]\npub enum RequestActor {\n    #[default]\n    Client,\n    Server,\n}\n\nimpl From<VantagePoint> for RequestActor {\n    fn from(value: VantagePoint) -> Self {\n        match value {\n            VantagePoint::Client => RequestActor::Client,\n            VantagePoint::Server => RequestActor::Server,\n        }\n    }\n}\n\n#[derive(Debug, Default, Clone, Copy)]\npub enum VantagePoint {\n    #[default]\n    Client,\n    Server,\n}\n\n#[derive(Debug, Default)]\npub struct StreamDatapoint {\n    pub offset: u64,\n    pub length: u64,\n}\n\n#[derive(Default, Clone, Copy, PartialEq)]\npub enum ApplicationProto {\n    Http2,\n    #[default]\n    Http3,\n}\n\nimpl Debug for ApplicationProto {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        Display::fmt(self, f)\n    }\n}\n\nimpl Display for ApplicationProto {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let v = match self {\n            ApplicationProto::Http2 => \"HTTP/2\",\n            ApplicationProto::Http3 => \"HTTP/3\",\n        };\n\n        write!(f, \"{}\", v)\n    }\n}\n\n#[derive(Debug, Default, Tabled)]\npub struct QuicSessionClose {\n    #[tabled(rename = \"ID\")]\n    pub session_id: i64,\n    #[tabled(rename = \"SNI\")]\n    pub sni: String,\n    #[tabled(rename = \"Error\")]\n    pub quic_error: i64,\n    #[tabled(rename = \"Description\")]\n    pub quic_error_pretty: NaOption<String>,\n    #[tabled(rename = \"From peer\")]\n    pub from_peer: bool,\n    #[tabled(rename = \"Additional Details\")]\n    pub details: String,\n}\n\n#[derive(Debug, Default, Tabled)]\npub struct H2SessionClose {\n    #[tabled(rename = \"ID\")]\n    pub session_id: i64,\n    #[tabled(rename = \"SNI\")]\n    pub sni: String,\n    #[tabled(rename = \"Error\")]\n    pub net_err: i64,\n    #[tabled(rename = \"Description\")]\n    pub net_err_pretty: NaOption<String>,\n    #[tabled(rename = \"Additional Details\")]\n    pub details: String,\n}\n\n#[derive(Default, Debug)]\npub struct QuicStreamStopSending {\n    pub quic_rst_stream_error: u64,\n    pub quic_rst_stream_error_friendly: Option<String>,\n}\n\nimpl Display for QuicStreamStopSending {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"code={}, {}\",\n            self.quic_rst_stream_error,\n            self.quic_rst_stream_error_friendly.as_deref().unwrap_or(\"\")\n        )\n    }\n}\n\n#[derive(Default, Debug)]\npub struct QuicStreamReset {\n    pub offset: u64,\n    pub quic_rst_stream_error: u64,\n    pub quic_rst_stream_error_friendly: Option<String>,\n}\n\nimpl Display for QuicStreamReset {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"offset={}, code={}, {}\",\n            self.offset,\n            self.quic_rst_stream_error,\n            self.quic_rst_stream_error_friendly.as_deref().unwrap_or(\"\")\n        )\n    }\n}\n\n#[derive(Default, Debug)]\npub struct H2StreamReset {\n    pub error: String,\n    pub description: String,\n}\n\nimpl Display for H2StreamReset {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"code={}, {}\", self.error, self.description,)\n    }\n}\n\n#[derive(Default, Debug)]\npub struct Datastore {\n    pub vantage_point: VantagePoint,\n    pub application_proto: ApplicationProto,\n    pub session_id: Option<i64>,\n    pub host: Option<String>,\n\n    pub h2_client_settings: Http2Settings,\n    pub h2_server_settings: Http2Settings,\n\n    pub client_quic_tps: TransportParameters,\n\n    pub last_event_time: f64,\n\n    // There are several packet spaces, so store a map of all packets sent\n    // according to packet space. Each space then contains a map of packet\n    // header info keyed off the packet number.\n    pub packet_sent: HashMap<PacketType, BTreeMap<u64, PacketInfoStub>>,\n    pub packet_received: HashMap<PacketType, BTreeMap<u64, PacketInfoStub>>,\n\n    // TODO: netlog packet sent happens after frame, so we can't detect the\n    // packet type properly. Stick all in one bucket for now and accept we'll\n    // alias packet numbers that overlap between spaced\n    pub netlog_ack_sent_missing_packet: BTreeMap<PacketType, BTreeSet<u64>>,\n    pub netlog_ack_received_missing_packet: BTreeMap<PacketType, BTreeSet<u64>>,\n\n    pub packet_acked: Vec<QlogPointu64>,\n\n    pub local_cwnd: Vec<QlogPointu64>,\n    pub local_bytes_in_flight: Vec<QlogPointu64>,\n    pub local_ssthresh: Vec<QlogPointu64>,\n    pub local_pacing_rate: Vec<QlogPointu64>,\n    pub local_delivery_rate: Vec<QlogPointu64>,\n    pub local_send_rate: Vec<QlogPointu64>,\n    pub local_ack_rate: Vec<QlogPointu64>,\n\n    pub local_min_rtt: Vec<QlogPointRtt>,\n    pub local_latest_rtt: Vec<QlogPointRtt>,\n    pub local_smoothed_rtt: Vec<QlogPointRtt>,\n\n    pub congestion_state_updates: Vec<(f64, u64, String)>,\n\n    pub received_max_data: Vec<QlogPointu64>,\n\n    /// Tracks per-stream max data: full history, current max, and cumulative\n    /// sum.\n    pub received_stream_max_data_tracker: StreamMaxTracker,\n\n    pub sent_max_data: Vec<QlogPointu64>,\n\n    /// Tracks per-stream max data: full history, current max, and cumulative\n    /// sum.\n    pub sent_stream_max_data_tracker: StreamMaxTracker,\n\n    /// Tracks stream buffer reads: per-stream history, current max, and running\n    /// sum.\n    pub stream_buffer_reads_tracker: StreamBufferTracker,\n\n    /// Tracks stream buffer writes: per-stream history, current max, and\n    /// running sum.\n    pub stream_buffer_writes_tracker: StreamBufferTracker,\n\n    /// Tracks stream buffer dropped: per-stream history, current max, and\n    /// running sum.\n    pub stream_buffer_dropped_tracker: StreamBufferTracker,\n\n    pub received_reset_stream: BTreeMap<u64, Vec<QuicFrame>>,\n    pub sent_reset_stream: BTreeMap<u64, Vec<QuicFrame>>,\n\n    pub received_stream_frames: BTreeMap<u64, Vec<(f64, StreamDatapoint)>>,\n    pub received_stream_frames_count_based:\n        BTreeMap<u64, Vec<(u64, StreamDatapoint)>>,\n    pub total_received_stream_frame_count: u64,\n\n    pub sent_stream_frames: BTreeMap<u64, Vec<(f64, QuicFrame)>>,\n    pub sent_stream_frames_count_based: BTreeMap<u64, Vec<(u64, QuicFrame)>>,\n    pub total_sent_stream_frame_count: u64,\n\n    pub received_data_frames: BTreeMap<u64, Vec<(f64, u64)>>,\n    pub received_data_frames_count_based: BTreeMap<u64, Vec<(u64, u64)>>,\n    pub total_received_data_frame_count: u64,\n    pub received_data_cumulative: BTreeMap<u64, Vec<(f64, u64)>>,\n    pub received_data_cumulative_max: BTreeMap<u64, u64>,\n\n    pub sent_data_frames: BTreeMap<u64, Vec<(f64, u64)>>,\n    pub sent_data_frames_count_based: BTreeMap<u64, Vec<(u64, u64)>>,\n    pub total_sent_data_frame_count: u64,\n    pub sent_data_cumulative: BTreeMap<u64, Vec<(f64, u64)>>,\n    pub sent_data_cumulative_max: BTreeMap<u64, u64>,\n\n    pub http_requests: BTreeMap<u64, HttpRequestStub>,\n    pub largest_data_frame_rx_length_global: u64,\n    pub largest_data_frame_tx_length_global: u64,\n\n    pub local_init_max_stream_data_bidi_local: u64,\n    pub local_init_max_stream_data_uni: u64,\n    pub peer_init_max_stream_data_bidi_local: u64,\n    pub peer_init_max_stream_data_bidi_remote: u64,\n    pub peer_init_max_stream_data_uni: u64,\n\n    pub h2_recv_window_updates: BTreeMap<u32, Vec<(f64, i32)>>,\n\n    // Balance against incoming data to make it easier to plot in some cases\n    pub h2_send_window_updates_balanced: BTreeMap<u32, Vec<(f64, i32)>>,\n\n    // Just store raw updates for clear absolute values\n    pub h2_send_window_updates_absolute: BTreeMap<u32, Vec<(f64, u64)>>,\n\n    pub netlog_quic_server_window_blocked: BTreeMap<i64, Vec<f64>>,\n    pub netlog_quic_client_side_window_updates: BTreeMap<i64, Vec<(f64, u64)>>,\n\n    pub netlog_h2_stream_received_connection_cumulative: Vec<QlogPointu64>,\n    pub netlog_quic_stream_received_connection_cumulative: Vec<QlogPointu64>,\n\n    pub received_packets_netlog: Vec<(f64, PacketInfoStub)>,\n    pub discontinuous_packet_number_count: u64,\n\n    pub netlog_ack_sent_missing_packets_raw: Vec<(f64, Vec<u64>)>,\n\n    pub total_tx_ack: usize,\n    pub max_ack_sent_missing_packets_size: usize,\n\n    pub total_rx_ack: usize,\n    pub max_ack_received_missing_packets_size: usize,\n\n    pub quic_session_close: Option<QuicSessionClose>,\n    pub h2_session_close: Option<H2SessionClose>,\n\n    pub h2_concurrent_requests: u64,\n}\n\nfn is_bidi(stream_id: u64) -> bool {\n    (stream_id & 0x2) == 0\n}\n\nimpl Datastore {\n    pub fn consume_netlog_event(\n        &mut self, session_start_time: u64, ev_hdr: &netlog::EventHeader,\n        event: &netlog::Event, constants: &netlog::constants::Constants,\n        stream_bind: &StreamBindingMap,\n        h3_session_requests: Option<&Vec<ReqOverH3>>,\n    ) {\n        match event {\n            // nothing to do for this type just now\n            netlog::Event::Http(_e) => (),\n            netlog::Event::H2(e) => self.consume_netlog_h2(\n                session_start_time,\n                ev_hdr,\n                e,\n                constants,\n                stream_bind,\n            ),\n\n            netlog::Event::H3(e) => self.consume_netlog_h3(\n                session_start_time,\n                ev_hdr,\n                e,\n                h3_session_requests,\n            ),\n            netlog::Event::Quic(e) =>\n                self.consume_netlog_quic(session_start_time, ev_hdr, e, constants),\n        }\n    }\n\n    fn consume_netlog_quic(\n        &mut self, session_start_time: u64, ev_hdr: &netlog::EventHeader,\n        ev: &quic::Event, constants: &netlog::constants::Constants,\n    ) {\n        let rel_event_time = (ev_hdr.time_num - session_start_time) as f64;\n\n        match ev {\n            QuicSessionStreamFrameReceived(e) => {\n                let s = self\n                    .received_stream_frames\n                    .entry(e.params.stream_id)\n                    .or_default();\n                s.push((rel_event_time, StreamDatapoint {\n                    offset: e.params.offset,\n                    length: e.params.length,\n                }));\n\n                let s = self\n                    .received_stream_frames_count_based\n                    .entry(e.params.stream_id)\n                    .or_default();\n                s.push((\n                    self.total_received_stream_frame_count,\n                    StreamDatapoint {\n                        offset: e.params.offset,\n                        length: e.params.length,\n                    },\n                ));\n\n                self.total_received_stream_frame_count += 1;\n\n                if e.params.fin {\n                    if let Some(req) =\n                        self.http_requests.get_mut(&e.params.stream_id)\n                    {\n                        req.time_fin_rx = Some(rel_event_time);\n                    }\n                }\n\n                let cumulative = if let Some(last) = self\n                    .netlog_quic_stream_received_connection_cumulative\n                    .last()\n                {\n                    last.1 + e.params.length\n                } else {\n                    // insert a 0'th point\n                    e.params.length\n                };\n\n                self.netlog_quic_stream_received_connection_cumulative\n                    .push((rel_event_time, cumulative));\n            },\n\n            QuicSessionUnauthenticatedPacketHeaderReceived(e) => {\n                if let Some((_, last)) = self.received_packets_netlog.last() {\n                    let gap = if e.params.packet_number > last.number {\n                        e.params.packet_number - last.number\n                    } else {\n                        last.number.saturating_sub(e.params.packet_number)\n                    };\n\n                    if gap > 1 {\n                        self.discontinuous_packet_number_count += 1;\n                    }\n                }\n\n                let packet_type = PacketType::from_netlog_packet_header(\n                    &e.params.header_format,\n                    &e.params.long_header_type,\n                );\n\n                let packet_info = PacketInfoStub {\n                    acked: None,\n                    raw: None,\n                    created_time: rel_event_time,\n                    send_at_time: None,\n                    ty: packet_type,\n                    number: e.params.packet_number,\n                };\n\n                self.received_packets_netlog\n                    .push((rel_event_time, packet_info.clone()));\n\n                let s = self.packet_received.entry(packet_type).or_default();\n\n                s.insert(e.params.packet_number, packet_info);\n            },\n\n            QuicSessionPacketSent(e) => {\n                let packet_type = PacketType::from_netlog_encryption_level(\n                    &e.params.encryption_level,\n                );\n\n                let packet_info = PacketInfoStub {\n                    acked: None,\n                    raw: None,\n                    created_time: rel_event_time,\n                    send_at_time: None,\n                    ty: packet_type,\n                    number: e.params.packet_number,\n                };\n\n                let s = self.packet_sent.entry(packet_type).or_default();\n\n                s.insert(e.params.packet_number, packet_info);\n\n                // Go back and update the Ack frame type if there was one\n                if let Some(pkts) = self\n                    .netlog_ack_sent_missing_packet\n                    .get_mut(&PacketType::Unknown)\n                {\n                    if !pkts.is_empty() {\n                        let old = std::mem::take(pkts);\n\n                        let s = self\n                            .netlog_ack_sent_missing_packet\n                            .entry(packet_type)\n                            .or_default();\n\n                        for num in old {\n                            s.insert(num);\n                        }\n                    }\n                }\n            },\n\n            QuicSessionAckFrameSent(e) => {\n                self.total_tx_ack += 1;\n                self.max_ack_sent_missing_packets_size = std::cmp::max(\n                    e.params.missing_packets.len(),\n                    self.max_ack_sent_missing_packets_size,\n                );\n\n                if !e.params.missing_packets.is_empty() {\n                    self.netlog_ack_sent_missing_packets_raw\n                        .push((rel_event_time, e.params.missing_packets.clone()));\n                }\n\n                let s = self\n                    .netlog_ack_sent_missing_packet\n                    .entry(PacketType::Unknown)\n                    .or_default();\n\n                for pn in &e.params.missing_packets {\n                    // At this stage, we don't know the packet type we sent the\n                    // ACK in, because it comes later in the netlog. Insert with\n                    // a placeholder now, and we'll update later in\n                    // QuicSessionPacketSent handler. Ugly but functional.\n                    s.insert(*pn);\n                }\n            },\n\n            QuicSessionAckFrameReceived(e) => {\n                self.total_rx_ack += 1;\n                self.max_ack_received_missing_packets_size = std::cmp::max(\n                    e.params.missing_packets.len(),\n                    self.max_ack_sent_missing_packets_size,\n                );\n\n                if !e.params.missing_packets.is_empty() {\n                    // For netlogs, it is assumed that the last packet received\n                    // relates to this event.\n                    let parent_packet = self.received_packets_netlog.last();\n                    if let Some((_, pkt_info)) = parent_packet {\n                        let s = self\n                            .netlog_ack_received_missing_packet\n                            .entry(pkt_info.ty)\n                            .or_default();\n\n                        for missing in &e.params.missing_packets {\n                            s.insert(*missing);\n                        }\n                    }\n                }\n            },\n\n            QuicSessionClosed(e) => {\n                self.quic_session_close = Some(QuicSessionClose {\n                    session_id: self.session_id.unwrap_or(-1),\n                    sni: self.host.clone().unwrap_or(\"ERROR UNKNOWN\".to_string()),\n                    details: e.params.details.clone(),\n                    from_peer: e.params.from_peer,\n                    quic_error: e.params.quic_error,\n                    quic_error_pretty: NaOption::new(\n                        constants\n                            .quic_error_id_keyed\n                            .get(&e.params.quic_error)\n                            .cloned(),\n                    ),\n                });\n            },\n\n            QuicSessionRstStreamFrameReceived(e) => {\n                // Non-request streams can be reset, we don't care about them\n                // right now\n                if let Some(req) = self.http_requests.get_mut(&e.params.stream_id)\n                {\n                    req.quic_stream_reset_received = Some(QuicStreamReset {\n                        offset: e.params.offset,\n                        quic_rst_stream_error: e.params.quic_rst_stream_error,\n                        quic_rst_stream_error_friendly: constants\n                            .quic_rst_stream_error_id_keyed\n                            .get(&(e.params.quic_rst_stream_error as i64))\n                            .cloned(),\n                    });\n                }\n            },\n\n            QuicSessionRstStreamFrameSent(e) => {\n                // Non-request streams can be reset, we don't care about them\n                // right now\n                if let Some(req) = self.http_requests.get_mut(&e.params.stream_id)\n                {\n                    req.quic_stream_reset_sent = Some(QuicStreamReset {\n                        offset: e.params.offset,\n                        quic_rst_stream_error: e.params.quic_rst_stream_error,\n                        quic_rst_stream_error_friendly: constants\n                            .quic_rst_stream_error_id_keyed\n                            .get(&(e.params.quic_rst_stream_error as i64))\n                            .cloned(),\n                    });\n                }\n            },\n\n            QuicSessionStopSendingFrameSent(e) => {\n                // Non-request streams can be stopped, we don't care about them\n                // right now\n                if let Some(req) = self.http_requests.get_mut(&e.params.stream_id)\n                {\n                    req.quic_stream_stop_sending_sent =\n                        Some(QuicStreamStopSending {\n                            quic_rst_stream_error: e.params.quic_rst_stream_error,\n                            quic_rst_stream_error_friendly: constants\n                                .quic_rst_stream_error_id_keyed\n                                .get(&(e.params.quic_rst_stream_error as i64))\n                                .cloned(),\n                        });\n                }\n            },\n\n            QuicSessionBlockedFrameReceived(e) => {\n                let s = self\n                    .netlog_quic_server_window_blocked\n                    .entry(e.params.stream_id)\n                    .or_default();\n                s.push(rel_event_time);\n            },\n\n            QuicSessionWindowUpdateFrameSent(e) => {\n                let s = self\n                    .netlog_quic_client_side_window_updates\n                    .entry(e.params.stream_id)\n                    .or_default();\n\n                s.push((rel_event_time, e.params.byte_offset));\n            },\n\n            QuicSessionTransportParametersSent(e) => {\n                self.client_quic_tps =\n                    e.params.quic_transport_parameters.clone().into();\n\n                let s = self\n                    .netlog_quic_client_side_window_updates\n                    .entry(-1)\n                    .or_default();\n\n                s.push((\n                    rel_event_time,\n                    self.client_quic_tps.initial_max_data.unwrap_or_default(),\n                ));\n            },\n\n            // ignore the other events for now\n            QuicSession(_) | QuicSessionTransportParametersReceived(_) => (),\n        }\n    }\n\n    fn consume_netlog_h3(\n        &mut self, session_start_time: u64, ev_hdr: &netlog::EventHeader,\n        ev: &h3::Event, h3_session_requests: Option<&Vec<ReqOverH3>>,\n    ) {\n        let rel_event_time = (ev_hdr.time_num - session_start_time) as f64;\n\n        match ev {\n            Http3PriorityUpdateSent(e) => {\n                let req =\n                    self.get_or_insert_http_req(e.params.prioritized_element_id);\n                req.priority_updates\n                    .push(e.params.priority_field_value.clone());\n            },\n\n            Http3HeadersSent(e) => {\n                let req = self.get_or_insert_http_req(e.params.stream_id);\n                req.time_first_headers_tx.get_or_insert(rel_event_time);\n                req.set_request_info_from_netlog(&e.params.headers);\n\n                if let Some(reqs) = h3_session_requests {\n                    for r in reqs {\n                        if let Some(stream_id) = r.quic_stream_id {\n                            if stream_id == e.params.stream_id {\n                                // Hat-tip Olivia Trewin: this one cool trick\n                                // allows u64's to be substracted into a correct\n                                // i64.\n                                req.time_discovery = Some(\n                                    (r.discover_time\n                                        .wrapping_sub(session_start_time)\n                                        as i64)\n                                        as f64,\n                                );\n                                break;\n                            }\n                        }\n                    }\n                }\n            },\n\n            // TODO: this is reception of headers frame, before the field\n            // section is decoded. Ignore for now and just use the decoded event.\n            Http3HeadersReceived(_) => (),\n\n            Http3HeadersDecoded(e) => {\n                let req = self.get_or_insert_http_req(e.params.stream_id);\n                req.time_first_headers_rx.get_or_insert(rel_event_time);\n                req.set_response_info_from_netlog(&e.params.headers);\n            },\n\n            Http3DataFrameReceived(e) => {\n                let req = self.get_or_insert_http_req(e.params.stream_id);\n\n                req.time_first_data_rx.get_or_insert(rel_event_time);\n\n                let _ = req.time_last_data_rx.insert(rel_event_time);\n\n                let length = e.params.payload_length;\n                req.time_data_rx_set.push((rel_event_time, length));\n                self.largest_data_frame_rx_length_global = std::cmp::max(\n                    self.largest_data_frame_rx_length_global,\n                    length,\n                );\n\n                let s = self\n                    .received_data_frames\n                    .entry(e.params.stream_id)\n                    .or_default();\n\n                s.push((rel_event_time, e.params.payload_length));\n\n                let s = self\n                    .received_data_frames_count_based\n                    .entry(e.params.stream_id)\n                    .or_default();\n                s.push((\n                    self.total_received_data_frame_count,\n                    e.params.payload_length,\n                ));\n\n                self.total_received_data_frame_count += 1;\n\n                let s = self\n                    .received_data_cumulative\n                    .entry(e.params.stream_id)\n                    .or_default();\n\n                let received_data_cumulative = if let Some(last) = s.last() {\n                    last.1 + e.params.payload_length\n                } else {\n                    // insert a 0'th point\n                    e.params.payload_length\n                };\n\n                s.push((rel_event_time, received_data_cumulative));\n\n                let s = self\n                    .received_data_cumulative_max\n                    .entry(e.params.stream_id)\n                    .or_default();\n\n                *s = std::cmp::max(*s, received_data_cumulative);\n\n                let s = self.http_requests.entry(e.params.stream_id).or_default();\n\n                s.server_transferred_bytes = std::cmp::max(\n                    s.server_transferred_bytes,\n                    NaOption::new(Some(received_data_cumulative)),\n                )\n            },\n\n            // TODO: add support for logging HTTP/2 sending\n            Http3DataSent(_) => (),\n        }\n    }\n\n    fn consume_netlog_h2(\n        &mut self, session_start_time: u64, ev_hdr: &netlog::EventHeader,\n        ev: &h2::Event, constants: &netlog::constants::Constants,\n        stream_bind: &StreamBindingMap,\n    ) {\n        let rel_event_time = (ev_hdr.time_num - session_start_time) as f64;\n\n        match ev {\n            Http2SessionSendSettings(e) => {\n                match Http2Settings::try_from(e.params.settings.as_slice()) {\n                    Ok(v) => self.h2_client_settings = v,\n\n                    Err(e) => error!(\"{}\", e),\n                }\n            },\n\n            Http2SessionRecvSetting(e) => {\n                let re = Regex::new(H2_RECV_SETTING_PATTERN).unwrap();\n\n                if let Some(m) =\n                    re.captures(&e.params.id).and_then(|caps| caps.get(1))\n                {\n                    if let Ok(id) = m.as_str().parse::<u16>() {\n                        self.h2_server_settings.set_from_wire(id, e.params.value);\n                    } else {\n                        error!(\"parsing H2 setting {:?}\", e.params);\n                    }\n                } else {\n                    error!(\"parsing H2 setting {:?}\", e.params);\n                }\n            },\n            Http2SessionSendHeaders(e) => {\n                let req = self.get_or_insert_http_req(e.params.stream_id as u64);\n                req.time_first_headers_tx.get_or_insert(rel_event_time);\n                req.set_request_info_from_netlog(&e.params.headers);\n\n                if let Some(sb) = stream_bind.get(&e.params.source_dependency.id)\n                {\n                    // Hat-tip Olivia Trewin: this one cool trick allows u64's to\n                    // be substracted into a correct i64.\n                    req.time_discovery = Some(\n                        (sb.request_discovery_time\n                            .wrapping_sub(session_start_time)\n                            as i64) as f64,\n                    );\n                }\n\n                self.h2_concurrent_requests += 1;\n            },\n\n            Http2SessionRecvHeaders(e) => {\n                let req = self.get_or_insert_http_req(e.params.stream_id as u64);\n                req.time_first_headers_rx.get_or_insert(rel_event_time);\n                req.set_response_info_from_netlog(&e.params.headers);\n\n                if e.params.fin {\n                    self.h2_concurrent_requests -= 1;\n                }\n            },\n\n            Http2SessionRecvData(e) => {\n                let req = self.get_or_insert_http_req(e.params.stream_id as u64);\n\n                req.time_first_data_rx.get_or_insert(rel_event_time);\n\n                let _ = req.time_last_data_rx.insert(rel_event_time);\n\n                let length = e.params.size as u64;\n                req.time_data_rx_set.push((rel_event_time, length));\n                self.largest_data_frame_rx_length_global = std::cmp::max(\n                    self.largest_data_frame_rx_length_global,\n                    length,\n                );\n\n                let s = self\n                    .received_data_frames\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n\n                s.push((rel_event_time, e.params.size as u64));\n\n                let s = self\n                    .received_data_frames_count_based\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n                s.push((\n                    self.total_received_data_frame_count,\n                    e.params.size as u64,\n                ));\n\n                self.total_received_data_frame_count += 1;\n\n                let s = self\n                    .received_data_cumulative\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n\n                let received_data_cumulative = if let Some(last) = s.last() {\n                    last.1 + e.params.size as u64\n                } else {\n                    // insert a 0'th point\n                    e.params.size as u64\n                };\n\n                s.push((rel_event_time, received_data_cumulative));\n\n                let s = self\n                    .received_data_cumulative_max\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n\n                *s = std::cmp::max(*s, received_data_cumulative);\n\n                let s = self\n                    .http_requests\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n\n                s.server_transferred_bytes = std::cmp::max(\n                    s.server_transferred_bytes,\n                    NaOption::new(Some(received_data_cumulative)),\n                );\n\n                if e.params.fin {\n                    self.h2_concurrent_requests -= 1;\n                }\n\n                let cumulative = if let Some(last) =\n                    self.netlog_h2_stream_received_connection_cumulative.last()\n                {\n                    last.1 + e.params.size as u64\n                } else {\n                    // insert a 0'th point\n                    e.params.size as u64\n                };\n\n                self.netlog_h2_stream_received_connection_cumulative\n                    .push((rel_event_time, cumulative));\n            },\n\n            Http2SessionSendData(e) => {\n                let req = self.get_or_insert_http_req(e.params.stream_id as u64);\n\n                req.time_first_data_tx.get_or_insert(rel_event_time);\n\n                let _ = req.time_last_data_tx.insert(rel_event_time);\n\n                let length = e.params.size as u64;\n                req.time_data_tx_set.push((rel_event_time, length));\n                self.largest_data_frame_tx_length_global = std::cmp::max(\n                    self.largest_data_frame_tx_length_global,\n                    length,\n                );\n\n                let s = self\n                    .sent_data_frames\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n\n                s.push((rel_event_time, e.params.size as u64));\n\n                let s = self\n                    .sent_data_frames_count_based\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n                s.push((self.total_sent_data_frame_count, e.params.size as u64));\n\n                self.total_sent_data_frame_count += 1;\n\n                // counterintuitively, reduces our local send window\n                let s = self\n                    .h2_send_window_updates_balanced\n                    .entry(e.params.stream_id)\n                    .or_default();\n                s.push((rel_event_time, -(e.params.size as i32)));\n\n                let s = self\n                    .sent_data_cumulative\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n\n                let sent_data_cumulative = if let Some(last) = s.last() {\n                    last.1 + e.params.size as u64\n                } else {\n                    // insert a 0'th point\n                    e.params.size as u64\n                };\n\n                s.push((rel_event_time, sent_data_cumulative));\n\n                let s = self\n                    .sent_data_cumulative_max\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n\n                *s = std::cmp::max(*s, sent_data_cumulative);\n\n                let s = self\n                    .http_requests\n                    .entry(e.params.stream_id as u64)\n                    .or_default();\n\n                s.client_transferred_bytes = std::cmp::max(\n                    s.client_transferred_bytes,\n                    NaOption::new(Some(sent_data_cumulative)),\n                );\n            },\n\n            // counterintuitively, updates our local send window\n            Http2SessionRecvWindowUpdate(e) => {\n                let s = self\n                    .h2_send_window_updates_balanced\n                    .entry(e.params.stream_id)\n                    .or_default();\n                s.push((rel_event_time, e.params.delta));\n            },\n\n            // counterintuitively, updates our local receive window\n            Http2SessionSendWindowUpdate(e) => {\n                let s = self\n                    .h2_send_window_updates_balanced\n                    .entry(e.params.stream_id)\n                    .or_default();\n                s.push((rel_event_time, e.params.delta));\n\n                let s = self\n                    .h2_send_window_updates_absolute\n                    .entry(e.params.stream_id)\n                    .or_default();\n                // Window updates always have a positive delta, so this is fine.\n                s.push((rel_event_time, e.params.delta as u64));\n            },\n\n            Http2SessionClose(e) => {\n                self.h2_session_close = Some(H2SessionClose {\n                    session_id: self.session_id.unwrap_or(-1),\n                    sni: self.host.clone().unwrap_or(\"ERROR UNKNOWN\".to_string()),\n                    details: e.params.description.clone(),\n                    net_err: e.params.net_error,\n                    net_err_pretty: NaOption::new(\n                        constants\n                            .net_error_id_keyed\n                            .get(&e.params.net_error)\n                            .cloned(),\n                    ),\n                });\n            },\n\n            Http2SessionSendRstStream(e) => {\n                if let Some(req) =\n                    self.http_requests.get_mut(&(e.params.stream_id as u64))\n                {\n                    req.h2_stream_reset_sent = Some(H2StreamReset {\n                        error: e.params.error_code.clone(),\n                        description: e.params.description.clone(),\n                    });\n                }\n            },\n\n            Http2SessionRecvRstStream(e) => {\n                if let Some(req) =\n                    self.http_requests.get_mut(&(e.params.stream_id as u64))\n                {\n                    req.h2_stream_reset_receive = Some(H2StreamReset {\n                        error: e.params.error_code.clone(),\n                        description: \"\".to_string(),\n                    });\n                }\n                self.h2_concurrent_requests -= 1;\n            },\n\n            // ignore the other events for now\n            Http2Session(_) => (),\n            Http2SessionInitialized(_) => (),\n            Http2SessionUpdateRecvWindow(_) => (),\n            Http2SessionUpdateSendWindow(_) => (),\n            Http2SessionUpdateStreamsSendWindowSize(_) => (),\n            Http2SessionStalledMaxStreams(_) => (),\n\n            Http2StreamUpdateSendWindow(_) => (),\n            Http2StreamUpdateRecvWindow(_) => (),\n            Http2StreamStalledByStreamSendWindow(_) => (),\n            Http2SessionPing(_) => (),\n\n            Http2SessionRecvGoaway(_) => (),\n        }\n    }\n\n    pub fn consume_qlog_event(\n        &mut self, event: &qlog::events::Event, process_acks: bool,\n    ) {\n        let ev_time = event.time;\n\n        if ev_time > self.last_event_time {\n            self.last_event_time = ev_time;\n        }\n\n        match &event.data {\n            EventData::QuicParametersSet(v) =>\n                self.consume_qlog_transport_parameters_set(v),\n\n            EventData::QuicPacketReceived(v) =>\n                self.consume_qlog_packet_received(v, ev_time, process_acks),\n\n            EventData::QuicPacketSent(v) =>\n                self.consume_qlog_packet_sent(v, ev_time),\n\n            EventData::QuicStreamDataMoved(v) =>\n                self.consume_qlog_stream_data_moved(v, ev_time),\n\n            EventData::QuicMetricsUpdated(v) =>\n                self.consume_qlog_metrics_updated(v, ev_time),\n\n            EventData::QuicCongestionStateUpdated(v) =>\n                self.consume_qlog_congestion_state_updated(v, ev_time),\n\n            EventData::Http3FrameCreated(v) => match self.vantage_point {\n                VantagePoint::Client =>\n                    self.consume_qlog_h3_frame_created_client(v, ev_time),\n                VantagePoint::Server =>\n                    self.consume_qlog_h3_frame_created_server(v, ev_time),\n            },\n\n            EventData::Http3FrameParsed(v) => match self.vantage_point {\n                VantagePoint::Client =>\n                    self.consume_qlog_h3_frame_parsed_client(v, ev_time),\n                VantagePoint::Server =>\n                    self.consume_qlog_h3_frame_parsed_server(v, ev_time),\n            },\n\n            _ => (), // trace!(\"skipping {:?}\", event.data),\n        }\n    }\n\n    pub fn with_qlog_events(\n        events: &[qlog::events::Event], vantage_point: &qlog::VantagePointType,\n        process_acks: bool,\n    ) -> Self {\n        let vp = match vantage_point {\n            qlog::VantagePointType::Client => VantagePoint::Client,\n            qlog::VantagePointType::Server => VantagePoint::Server,\n            _ => panic!(\"unknown vantage point type\"),\n        };\n\n        let mut ds = Datastore {\n            vantage_point: vp,\n            ..Default::default()\n        };\n\n        for event in events {\n            ds.consume_qlog_event(event, process_acks);\n        }\n\n        ds.hydrate_http_requests();\n        ds.finalize();\n\n        ds\n    }\n\n    pub fn finalize(&mut self) {\n        if let Some(last) = self.local_cwnd.last().cloned() {\n            self.local_cwnd.push((self.last_event_time, last.1));\n        }\n\n        if let Some(last) = self.local_pacing_rate.last().cloned() {\n            trace!(\"pushing last {:?}\", last);\n            self.local_pacing_rate.push((self.last_event_time, last.1));\n        }\n    }\n\n    pub fn hydrate_http_requests(&mut self) {\n        for req in self.http_requests.values_mut() {\n            req.calculate_deltas();\n            req.calculate_upload_download_rate();\n        }\n    }\n\n    fn consume_qlog_transport_parameters_set(\n        &mut self, tp: &qlog::events::quic::ParametersSet,\n    ) {\n        match tp.initiator {\n            Some(TransportInitiator::Local) => {\n                if let Some(max_data) = tp.initial_max_data {\n                    self.sent_max_data.push((0.0, max_data));\n                }\n\n                if let Some(max_stream_data) =\n                    tp.initial_max_stream_data_bidi_local\n                {\n                    self.local_init_max_stream_data_bidi_local = max_stream_data;\n                }\n\n                if let Some(max_stream_data) = tp.initial_max_stream_data_uni {\n                    self.local_init_max_stream_data_uni = max_stream_data;\n                }\n            },\n\n            Some(TransportInitiator::Remote) => {\n                if let Some(max_data) = tp.initial_max_data {\n                    self.received_max_data.push((0.0, max_data));\n                }\n\n                if let Some(max_stream_data) =\n                    tp.initial_max_stream_data_bidi_local\n                {\n                    self.peer_init_max_stream_data_bidi_local = max_stream_data;\n                }\n\n                if let Some(max_stream_data) =\n                    tp.initial_max_stream_data_bidi_remote\n                {\n                    self.peer_init_max_stream_data_bidi_remote = max_stream_data;\n                }\n\n                if let Some(max_stream_data) = tp.initial_max_stream_data_uni {\n                    self.peer_init_max_stream_data_uni = max_stream_data;\n                }\n            },\n\n            _ => unimplemented!(),\n        }\n    }\n\n    fn consume_qlog_packet_received(\n        &mut self, pr: &qlog::events::quic::PacketReceived, ev_time: f64,\n        process_acks: bool,\n    ) {\n        if let Some(frames) = &pr.frames {\n            for frame in frames {\n                match frame {\n                    QuicFrame::Ack { acked_ranges, .. } => {\n                        if process_acks {\n                            if let Some(ack_ranges) = acked_ranges {\n                                let ty = PacketType::from_qlog_packet_type(\n                                    &pr.header.packet_type,\n                                );\n                                if let Some(pkt_space) =\n                                    self.packet_sent.get_mut(&ty)\n                                {\n                                    match ack_ranges {\n                                        AckedRanges::Single(ranges) => {\n                                            // TODO: qlog deserializer seems to\n                                            // get confused (bug?) so work\n                                            // around it detecting single or\n                                            // pairs\n\n                                            for pkt_nums in ranges {\n                                                if pkt_nums.len() == 1 {\n                                                    if let Some(pkt) = pkt_space\n                                                        .get_mut(&pkt_nums[0])\n                                                    {\n                                                        pkt.acked = Some(true);\n                                                    }\n                                                } else if pkt_nums.len() == 2 {\n                                                    // TODO: check ack ranges and\n                                                    // rust\n                                                    // Range mapping is correct\n                                                    let actual_range = pkt_nums\n                                                        [0]..\n                                                        pkt_nums[1] + 1;\n\n                                                    pkt_space\n                                                        .range_mut(actual_range)\n                                                        .for_each(|e| {\n                                                            e.1.acked = Some(true)\n                                                        });\n                                                }\n                                            }\n                                        },\n                                        AckedRanges::Double(ranges) => {\n                                            for range in ranges {\n                                                // TODO: check ack ranges and rust\n                                                // Range mapping is correct\n                                                let actual_range =\n                                                    range.0..range.1 + 1;\n\n                                                pkt_space\n                                                    .range_mut(actual_range)\n                                                    .for_each(|e| {\n                                                        e.1.acked = Some(true)\n                                                    });\n                                            }\n                                        },\n                                    }\n                                }\n                            }\n                        }\n                    },\n\n                    QuicFrame::MaxData { maximum, .. } => {\n                        self.received_max_data.push((ev_time, *maximum));\n                    },\n\n                    QuicFrame::MaxStreamData {\n                        stream_id, maximum, ..\n                    } => {\n                        let init_val = if is_bidi(*stream_id) {\n                            self.peer_init_max_stream_data_bidi_remote\n                        } else {\n                            self.peer_init_max_stream_data_uni\n                        };\n                        self.received_stream_max_data_tracker\n                            .update(*stream_id, *maximum, ev_time, init_val);\n                    },\n\n                    QuicFrame::ResetStream { stream_id, .. } => {\n                        let s = self\n                            .received_reset_stream\n                            .entry(*stream_id)\n                            .or_default();\n                        s.push(frame.clone());\n                    },\n\n                    QuicFrame::Stream {\n                        stream_id,\n                        offset,\n                        raw,\n                        ..\n                    } => {\n                        let length = raw\n                            .clone()\n                            .unwrap_or_default()\n                            .payload_length\n                            .unwrap_or_default();\n                        let s = self\n                            .received_stream_frames\n                            .entry(*stream_id)\n                            .or_default();\n                        s.push((ev_time, StreamDatapoint {\n                            length,\n                            offset: offset.unwrap_or_default(),\n                        }));\n\n                        let s = self\n                            .received_stream_frames_count_based\n                            .entry(*stream_id)\n                            .or_default();\n                        s.push((\n                            self.total_received_stream_frame_count,\n                            StreamDatapoint {\n                                length,\n                                offset: offset.unwrap_or_default(),\n                            },\n                        ));\n\n                        self.total_received_stream_frame_count += 1;\n                    },\n\n                    _ => (),\n                }\n            }\n        }\n    }\n\n    fn consume_qlog_packet_sent(\n        &mut self, ps: &qlog::events::quic::PacketSent, ev_time: f64,\n    ) {\n        // If there's no packet number we'll have to skip processing.\n        if ps.header.packet_number.is_none() {\n            return;\n        }\n\n        let packet_type =\n            PacketType::from_qlog_packet_type(&ps.header.packet_type);\n        let packet_info = PacketInfoStub {\n            acked: None,\n            raw: ps.raw.clone(),\n            created_time: ev_time,\n            send_at_time: ps.send_at_time,\n            ty: packet_type,\n            number: ps.header.packet_number.unwrap(),\n        };\n\n        let s = self.packet_sent.entry(packet_type).or_default();\n\n        s.insert(ps.header.packet_number.unwrap(), packet_info);\n\n        // Prefer to use the packet_sent send_at_time if it exists. Otherwise\n        // fallback to the event time.\n        let event_time = ps.send_at_time.unwrap_or(ev_time);\n\n        if let Some(frames) = &ps.frames {\n            for frame in frames {\n                match frame {\n                    QuicFrame::Ack { .. } => {\n                        // TODO\n                    },\n\n                    QuicFrame::MaxData { maximum, .. } => {\n                        self.sent_max_data.push((event_time, *maximum));\n                    },\n\n                    QuicFrame::MaxStreamData {\n                        stream_id, maximum, ..\n                    } => {\n                        let init_val = if is_bidi(*stream_id) {\n                            self.local_init_max_stream_data_bidi_local\n                        } else {\n                            self.local_init_max_stream_data_uni\n                        };\n                        self.sent_stream_max_data_tracker\n                            .update(*stream_id, *maximum, ev_time, init_val);\n                    },\n\n                    QuicFrame::ResetStream { stream_id, .. } => {\n                        let s =\n                            self.sent_reset_stream.entry(*stream_id).or_default();\n                        s.push(frame.clone());\n                    },\n\n                    QuicFrame::Stream { stream_id, .. } => {\n                        let s = self\n                            .sent_stream_frames\n                            .entry(*stream_id)\n                            .or_default();\n                        s.push((event_time, frame.clone()));\n\n                        let s = self\n                            .sent_stream_frames_count_based\n                            .entry(*stream_id)\n                            .or_default();\n                        s.push((\n                            self.total_sent_stream_frame_count,\n                            frame.clone(),\n                        ));\n\n                        self.total_sent_stream_frame_count += 1;\n                    },\n\n                    QuicFrame::DataBlocked { limit, .. } => {\n                        trace!(\n                            \"todo DATA_BLOCKED t={} limit={}\",\n                            event_time,\n                            limit\n                        );\n                    },\n\n                    QuicFrame::StreamDataBlocked {\n                        stream_id, limit, ..\n                    } => {\n                        trace!(\n                            \"todo STREAM_DATA_BLOCKED t={} stream={} limit={}\",\n                            event_time,\n                            stream_id,\n                            limit\n                        );\n                    },\n\n                    _ => (),\n                }\n            }\n        }\n    }\n\n    fn consume_qlog_stream_data_moved(\n        &mut self, dm: &qlog::events::quic::StreamDataMoved, ev_time: f64,\n    ) {\n        if let Some(recipient) = &dm.to {\n            let tracker = match recipient {\n                qlog::events::DataRecipient::Application =>\n                    &mut self.stream_buffer_reads_tracker,\n                qlog::events::DataRecipient::Transport =>\n                    &mut self.stream_buffer_writes_tracker,\n                qlog::events::DataRecipient::Dropped =>\n                    &mut self.stream_buffer_dropped_tracker,\n                _ => todo!(),\n            };\n\n            if let Some(stream_id) = dm.stream_id {\n                if let Some(raw) = &dm.raw {\n                    if let (Some(offset), Some(length)) = (dm.offset, raw.length)\n                    {\n                        tracker.update(\n                            stream_id,\n                            StreamAccess { offset, length },\n                            ev_time,\n                        );\n                    }\n                }\n            }\n        }\n    }\n\n    fn consume_qlog_metrics_updated(\n        &mut self, mu: &qlog::events::quic::RecoveryMetricsUpdated, ev_time: f64,\n    ) {\n        if let Some(cwnd) = mu.congestion_window {\n            self.local_cwnd.push((ev_time, cwnd));\n        }\n\n        if let Some(bif) = mu.bytes_in_flight {\n            self.local_bytes_in_flight.push((ev_time, bif));\n        }\n\n        if let Some(rtt) = mu.min_rtt {\n            self.local_min_rtt.push((ev_time, rtt));\n        }\n\n        if let Some(rtt) = mu.latest_rtt {\n            self.local_latest_rtt.push((ev_time, rtt));\n        }\n\n        if let Some(rtt) = mu.smoothed_rtt {\n            self.local_smoothed_rtt.push((ev_time, rtt));\n        }\n\n        if let Some(thresh) = mu.ssthresh {\n            self.local_ssthresh.push((ev_time, thresh));\n        }\n\n        if let Some(pacing_rate) = mu.pacing_rate {\n            self.local_pacing_rate.push((ev_time, pacing_rate));\n        }\n\n        // Extract rate metrics from ex_data\n        if let Some(rate) =\n            mu.ex_data.get(\"cf_delivery_rate\").and_then(|v| v.as_u64())\n        {\n            self.local_delivery_rate.push((ev_time, rate));\n        }\n\n        if let Some(rate) =\n            mu.ex_data.get(\"cf_send_rate\").and_then(|v| v.as_u64())\n        {\n            self.local_send_rate.push((ev_time, rate));\n        }\n\n        if let Some(rate) = mu.ex_data.get(\"cf_ack_rate\").and_then(|v| v.as_u64())\n        {\n            self.local_ack_rate.push((ev_time, rate));\n        }\n    }\n\n    fn consume_qlog_congestion_state_updated(\n        &mut self, csu: &qlog::events::quic::CongestionStateUpdated, ev_time: f64,\n    ) {\n        if let Some(point) = self.local_cwnd.last() {\n            // give this a virtual y-value of the last cwnd value recorded, we\n            // can choose to use it or not later.\n            self.congestion_state_updates.push((\n                ev_time,\n                point.1,\n                csu.new.clone(),\n            ));\n        }\n    }\n\n    fn get_or_insert_http_req(&mut self, stream_id: u64) -> &mut HttpRequestStub {\n        self.http_requests\n            .entry(stream_id)\n            .or_insert(HttpRequestStub {\n                stream_id,\n                request_actor: self.vantage_point.into(),\n                ..Default::default()\n            })\n    }\n\n    fn consume_qlog_h3_frame_created_client(\n        &mut self, fc: &qlog::events::http3::FrameCreated, ev_time: f64,\n    ) {\n        match &fc.frame {\n            Http3Frame::Headers { headers } => {\n                let req = self.get_or_insert_http_req(fc.stream_id);\n                req.time_first_headers_tx.get_or_insert(ev_time);\n                req.set_request_info_from_qlog(headers);\n            },\n\n            Http3Frame::Data { .. } => {\n                let req = self.get_or_insert_http_req(fc.stream_id);\n\n                req.time_first_data_tx.get_or_insert(ev_time);\n\n                let _ = req.time_last_data_tx.insert(ev_time);\n\n                let length = fc.length.unwrap_or_default();\n                req.time_data_tx_set.push((ev_time, length));\n                self.largest_data_frame_tx_length_global = std::cmp::max(\n                    self.largest_data_frame_tx_length_global,\n                    length,\n                );\n            },\n\n            Http3Frame::PriorityUpdate {\n                prioritized_element_id,\n                priority_field_value,\n                ..\n            } => {\n                let req = self.get_or_insert_http_req(*prioritized_element_id);\n                req.priority_updates.push(priority_field_value.clone());\n            },\n\n            // ignore other frames\n            _ => (),\n        }\n    }\n\n    fn consume_qlog_h3_frame_created_server(\n        &mut self, fc: &qlog::events::http3::FrameCreated, ev_time: f64,\n    ) {\n        match &fc.frame {\n            Http3Frame::Headers { headers } => {\n                let req = self.get_or_insert_http_req(fc.stream_id);\n                req.time_first_headers_tx.get_or_insert(ev_time);\n                req.set_response_info_from_qlog(headers);\n            },\n\n            Http3Frame::Data { .. } => {\n                let req = self.get_or_insert_http_req(fc.stream_id);\n\n                req.time_first_data_tx.get_or_insert(ev_time);\n\n                let _ = req.time_last_data_tx.insert(ev_time);\n\n                let length = fc.length.unwrap_or_default();\n                req.time_data_tx_set.push((ev_time, length));\n                self.largest_data_frame_tx_length_global = std::cmp::max(\n                    self.largest_data_frame_tx_length_global,\n                    length,\n                );\n            },\n\n            // ignore other frames\n            _ => (),\n        }\n    }\n\n    fn consume_qlog_h3_frame_parsed_client(\n        &mut self, fp: &qlog::events::http3::FrameParsed, ev_time: f64,\n    ) {\n        match &fp.frame {\n            Http3Frame::Headers { headers } => {\n                let req = self.get_or_insert_http_req(fp.stream_id);\n                req.time_first_headers_rx.get_or_insert(ev_time);\n\n                req.set_response_info_from_qlog(headers);\n            },\n\n            Http3Frame::Data { .. } => {\n                let req = self.get_or_insert_http_req(fp.stream_id);\n\n                req.time_first_data_rx.get_or_insert(ev_time);\n\n                let _ = req.time_last_data_rx.insert(ev_time);\n\n                // TODO: is default length sensible here?\n                let length = fp.length.unwrap_or_default();\n                req.time_data_rx_set.push((ev_time, length));\n                self.largest_data_frame_rx_length_global = std::cmp::max(\n                    self.largest_data_frame_rx_length_global,\n                    length,\n                );\n\n                let s =\n                    self.received_data_frames.entry(fp.stream_id).or_default();\n\n                s.push((ev_time, length));\n            },\n\n            // ignore other frames\n            _ => (),\n        }\n    }\n\n    fn consume_qlog_h3_frame_parsed_server(\n        &mut self, fp: &qlog::events::http3::FrameParsed, ev_time: f64,\n    ) {\n        match &fp.frame {\n            Http3Frame::Headers { headers } => {\n                let req = self.get_or_insert_http_req(fp.stream_id);\n                req.time_first_headers_rx.get_or_insert(ev_time);\n                req.path = NaOption::new(find_header_value(headers, \":path\"));\n                req.client_pri_hdr =\n                    NaOption::new(find_header_value(headers, \"priority\"));\n            },\n\n            Http3Frame::Data { .. } => {\n                let req = self.get_or_insert_http_req(fp.stream_id);\n\n                req.time_first_data_rx.get_or_insert(ev_time);\n\n                let _ = req.time_last_data_rx.insert(ev_time);\n\n                let length = fp.length.unwrap_or_default();\n                req.time_data_rx_set.push((ev_time, length));\n                self.largest_data_frame_rx_length_global = std::cmp::max(\n                    self.largest_data_frame_rx_length_global,\n                    length,\n                );\n            },\n\n            Http3Frame::PriorityUpdate {\n                prioritized_element_id,\n                priority_field_value,\n                ..\n            } => {\n                let req = self.get_or_insert_http_req(*prioritized_element_id);\n                req.priority_updates.push(priority_field_value.clone());\n            },\n\n            // ignore other frames\n            _ => (),\n        }\n    }\n\n    pub fn with_sqlog_reader_events(\n        events: &[qlog::reader::Event], vantage_point: &qlog::VantagePointType,\n        process_acks: bool,\n    ) -> Self {\n        let vp = match vantage_point {\n            qlog::VantagePointType::Client => VantagePoint::Client,\n            qlog::VantagePointType::Server => VantagePoint::Server,\n            _ => panic!(\"unknown vantage point type\"),\n        };\n\n        let mut ds = Datastore {\n            total_sent_stream_frame_count: 0,\n            vantage_point: vp,\n            ..Default::default()\n        };\n\n        for event in events {\n            match event {\n                qlog::reader::Event::Qlog(ev) => {\n                    ds.consume_qlog_event(ev, process_acks);\n                },\n\n                qlog::reader::Event::Json(ev) => {\n                    // Just swallow the failure and move on\n                    error!(\"unhandled Json event {:?}\", ev);\n                },\n            }\n        }\n\n        ds.hydrate_http_requests();\n        ds.finalize();\n\n        ds\n    }\n}\n\n#[derive(Tabled)]\npub struct NetlogSession {\n    #[tabled(rename = \"ID\")]\n    session_id: i64,\n    #[tabled(rename = \"Protocol\")]\n    application_proto: ApplicationProto,\n    #[tabled(rename = \"SNI\")]\n    host: String,\n    start_time: u64,\n}\n\nimpl Debug for NetlogSession {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"app proto={:?}, host={}\",\n            self.application_proto, self.host\n        )\n    }\n}\n\npub struct RequestDiscovery {\n    pub time: u64,\n    pub stream_job_id: Option<i64>,\n}\n\npub struct StreamBind {\n    pub time: u64,\n    pub request_discovery_id: i64,\n    pub request_discovery_time: u64,\n}\n\npub type RequestDiscoveryMap = BTreeMap<i64, RequestDiscovery>;\npub type StreamBindingMap = BTreeMap<i64, StreamBind>;\n\n#[derive(Debug)]\npub struct ReqOverH3 {\n    pub id: i64,\n    pub discover_time: u64,\n    pub session_id: Option<i64>,\n    pub quic_stream_id: Option<u64>,\n}\n\npub type RequestOverH3Map = BTreeMap<i64, Vec<ReqOverH3>>;\n\npub fn with_netlog_reader<R: std::io::BufRead>(\n    reader: &mut R, hostname_filter: HashSet<String>,\n    constants: &netlog::constants::Constants,\n) -> (Vec<LogFileData>, BTreeMap<i64, NetlogSession>) {\n    // second line in a netlog is always `\"events\": [` so skip it\n    read_netlog_record(reader);\n\n    let mut sessions: BTreeMap<i64, NetlogSession> = BTreeMap::new();\n    let mut session_events: BTreeMap<\n        i64,\n        Vec<(netlog::EventHeader, netlog::Event)>,\n    > = BTreeMap::new();\n\n    let mut h3_session_requests: RequestOverH3Map = BTreeMap::new();\n\n    let mut req_id_to_session_id: BTreeMap<i64, i64> = BTreeMap::new();\n\n    let mut request_discovery: RequestDiscoveryMap = BTreeMap::new();\n    let mut stream_bind: StreamBindingMap = BTreeMap::new();\n\n    while let Some(event) = read_netlog_record(reader) {\n        let res: Result<netlog::EventHeader, serde_json::Error> =\n            serde_json::from_slice(&event);\n\n        match res {\n            Ok(mut event_hdr) => {\n                event_hdr.populate_strings(constants);\n                event_hdr.time_num = event_hdr.time.parse::<u64>().unwrap();\n\n                // If this is a session creation, store the session, so we can\n                // link events with it. The source ID of these events is the\n                // unique value that we will use to link things together.\n                // This assumes events belonging to a session do not occur\n                // before the session is created.\n                if event_hdr.phase_string == \"PHASE_BEGIN\" {\n                    match event_hdr.ty_string.as_str() {\n                        \"QUIC_SESSION\" => {\n                            let ev: QuicSessionEvent =\n                                serde_json::from_slice(&event).unwrap();\n\n                            // QUIC sessions split host and port, which\n                            // interferes with filter expression, so merge them\n                            let host =\n                                format!(\"{}:{}\", ev.params.host, ev.params.port,);\n\n                            sessions.insert(event_hdr.source.id, NetlogSession {\n                                session_id: event_hdr.source.id,\n                                application_proto: ApplicationProto::Http3,\n                                host: host.clone(),\n                                start_time: event_hdr\n                                    .time\n                                    .parse::<u64>()\n                                    .unwrap(),\n                            });\n\n                            let do_insert = hostname_filter.is_empty() ||\n                                hostname_filter.contains(&host);\n\n                            if do_insert {\n                                session_events\n                                    .insert(event_hdr.source.id, Vec::new());\n\n                                h3_session_requests\n                                    .insert(event_hdr.source.id, Vec::new());\n                            }\n                        },\n\n                        \"HTTP2_SESSION\" => {\n                            let ev: Http2SessionEvent =\n                                serde_json::from_slice(&event).unwrap();\n\n                            sessions.insert(event_hdr.source.id, NetlogSession {\n                                session_id: event_hdr.source.id,\n                                application_proto: ApplicationProto::Http2,\n                                host: ev.params.host.clone(),\n                                start_time: event_hdr\n                                    .time\n                                    .parse::<u64>()\n                                    .unwrap(),\n                            });\n\n                            let do_insert = hostname_filter.is_empty() ||\n                                hostname_filter.contains(&ev.params.host);\n\n                            if do_insert {\n                                session_events\n                                    .insert(event_hdr.source.id, Vec::new());\n                            }\n                        },\n\n                        \"CORS_REQUEST\" => {\n                            // Seems to be the earliest netlog event related to\n                            // any request.\n                            request_discovery.insert(\n                                event_hdr.source.id,\n                                RequestDiscovery {\n                                    time: event_hdr.time_num,\n                                    stream_job_id: None,\n                                },\n                            );\n                        },\n\n                        _ => (),\n                    }\n                }\n\n                if event_hdr.ty_string.starts_with(\"HTTP_\") {\n                    let event = netlog::http::parse_event(&event_hdr, &event);\n\n                    // This will eventually deal with other events, and having\n                    // to refactor back and forth is a waste.\n                    #[allow(clippy::single_match)]\n                    match event {\n                        Some(netlog::Event::Http(e)) => {\n                            match e {\n                                http::Event::HttpStreamJobBoundToRequest(v)  => {\n                                    let request_discovery_id = v.params.source_dependency.id;\n                                    if let Some(rd) = request_discovery.get_mut(&request_discovery_id) {\n                                        stream_bind.insert(event_hdr.source.id, StreamBind{time: event_hdr.time_num, request_discovery_id, request_discovery_time: rd.time});\n                                        rd.stream_job_id = Some(event_hdr.source.id);\n                                    }\n                                },\n\n                                http::Event::HttpStreamRequestBoundToQuicSession(v) => {\n                                    let request_discovery_id = event_hdr.source.id;\n                                    if let Some(rd) = request_discovery.get(&request_discovery_id) {\n\n                                        if let Some(session_requests) = h3_session_requests.get_mut(&v.params.source_dependency.id) {\n                                            let req = ReqOverH3{id: event_hdr.source.id, discover_time: rd.time, session_id: Some(v.params.source_dependency.id), quic_stream_id: None };\n                                            session_requests.push(req);\n\n                                            // populate reverse mapping, each unique request ID has a session ID\n                                            req_id_to_session_id.insert(event_hdr.source.id, v.params.source_dependency.id);\n                                        }\n                                    }\n                                }\n\n                                http::Event::HttpTransactionQuicSendRequestHeaders(v) => {\n                                    let req_id = event_hdr.source.id;\n                                    if let Some(session_id) = req_id_to_session_id.get(&req_id) {\n                                        if let Some(reqs) = h3_session_requests.get_mut(session_id) {\n                                            // todo replace vec with map?\n                                            for req in reqs {\n                                                if req.id == req_id {\n                                                    req.quic_stream_id = Some(v.params.quic_stream_id);\n                                                    break;\n                                                }\n                                            }\n                                        }\n                                    }\n                                }\n\n                                _ => (),\n                            }\n                        },\n\n                        // ignore other events\n                        _ => (),\n                    }\n                }\n\n                if let Some(session) =\n                    session_events.get_mut(&event_hdr.source.id)\n                {\n                    if let Some(ev) = netlog::parse_event(&event_hdr, &event) {\n                        session.push((event_hdr, ev));\n                    }\n                }\n            },\n\n            Err(e) => {\n                error!(\"Error deserializing: {}\", e);\n                error!(\"input value {}\", String::from_utf8_lossy(&event));\n\n                // Just swallow the failure and move on\n            },\n        }\n    }\n\n    println!(\"All sessions in this netlog = {:#?}\", sessions);\n\n    let mut log_file_data = Vec::new();\n\n    for (session_id, details) in &sessions {\n        if let Some(events) = session_events.get(session_id) {\n            let mut ds = Datastore {\n                session_id: Some(*session_id),\n                application_proto: details.application_proto,\n                host: Some(details.host.clone()),\n                total_sent_stream_frame_count: 0,\n                ..Default::default()\n            };\n\n            for (ev_hdr, event) in events {\n                ds.consume_netlog_event(\n                    details.start_time,\n                    ev_hdr,\n                    event,\n                    constants,\n                    &stream_bind,\n                    h3_session_requests.get(session_id),\n                );\n            }\n\n            ds.hydrate_http_requests();\n            ds.finalize();\n            log_file_data.push(LogFileData {\n                datastore: ds,\n                raw: Netlog,\n            });\n        }\n    }\n\n    (log_file_data, sessions)\n}\n"
  },
  {
    "path": "qlog-dancer/src/lib.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\nuse std::error::Error;\nuse std::fs::File;\nuse std::io::BufReader;\nuse std::path::Path;\n\nuse config::AppConfig;\nuse datastore::Datastore;\nuse datastore::NetlogSession;\nuse log::debug;\nuse log::error;\n\nuse qlog::reader::QlogSeqReader;\nuse qlog::Qlog;\n\nuse serde::ser::Serialize;\n\nuse crate::wirefilter::filter_sqlog_events;\n\npub type QlogPointu64 = (f64, u64);\npub type QlogPointRtt = (f64, f32);\n\n#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Copy, Clone)]\npub enum PacketType {\n    Initial,\n    Handshake,\n    ZeroRtt,\n    OneRtt,\n    Retry,\n    VersionNegotiation,\n    Unknown,\n}\n\nimpl PacketType {\n    pub fn from_qlog_packet_type(ty: &qlog::events::quic::PacketType) -> Self {\n        match ty {\n            qlog::events::quic::PacketType::Initial => PacketType::Initial,\n            qlog::events::quic::PacketType::Handshake => PacketType::Handshake,\n            qlog::events::quic::PacketType::ZeroRtt => PacketType::ZeroRtt,\n            qlog::events::quic::PacketType::OneRtt => PacketType::OneRtt,\n            qlog::events::quic::PacketType::Retry => PacketType::Retry,\n            qlog::events::quic::PacketType::VersionNegotiation =>\n                PacketType::VersionNegotiation,\n            qlog::events::quic::PacketType::StatelessReset => PacketType::Unknown,\n            qlog::events::quic::PacketType::Unknown => PacketType::Unknown,\n        }\n    }\n\n    pub fn from_netlog_packet_header(\n        header_format: &str, long_header_type: &Option<String>,\n    ) -> Self {\n        match header_format {\n            \"IETF_QUIC_LONG_HEADER_PACKET\" => match long_header_type {\n                Some(v) => match v.as_str() {\n                    \"INITIAL\" => PacketType::Initial,\n                    \"HANDSHAKE\" => PacketType::Handshake,\n                    _ => PacketType::Unknown,\n                },\n\n                None => PacketType::Unknown,\n            },\n\n            \"IETF_QUIC_SHORT_HEADER_PACKET\" => PacketType::OneRtt,\n\n            _ => PacketType::Unknown,\n        }\n    }\n\n    pub fn from_netlog_encryption_level(encryption_level: &str) -> Self {\n        match encryption_level {\n            \"ENCRYPTION_INITIAL\" => PacketType::Initial,\n\n            \"ENCRYPTION_HANDSHAKE\" => PacketType::Handshake,\n\n            \"ENCRYPTION_ZERO_RTT\" => PacketType::ZeroRtt,\n\n            \"ENCRYPTION_FORWARD_SECURE\" => PacketType::OneRtt,\n\n            _ => PacketType::Unknown,\n        }\n    }\n}\n\n#[derive(Debug)]\npub enum SerializationFormat {\n    QlogJson,\n    QlogJsonSeq,\n    NetlogJson,\n    Unknown,\n}\n\nimpl SerializationFormat {\n    pub fn from_file_extension(extension: &str) -> Self {\n        match extension {\n            \"qlog\" => SerializationFormat::QlogJson,\n            \"sqlog\" => SerializationFormat::QlogJsonSeq,\n            \"json\" => Self::NetlogJson,\n            _ => SerializationFormat::Unknown,\n        }\n    }\n}\n\npub struct VantagePointTypeShim {\n    pub inner: qlog::VantagePointType,\n}\n\nimpl Default for VantagePointTypeShim {\n    fn default() -> Self {\n        VantagePointTypeShim {\n            inner: qlog::VantagePointType::Unknown,\n        }\n    }\n}\n\nimpl std::fmt::Display for VantagePointTypeShim {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{:?}\", self.inner)\n    }\n}\n\npub struct LogFileDetails {\n    pub file_schema: String,\n    pub serialization_format: String,\n    pub qlog_vantage_point_type: VantagePointTypeShim,\n    pub sessions: BTreeMap<i64, NetlogSession>,\n}\n\npub enum RawLogEvents {\n    QlogJson { events: Vec<qlog::events::Event> },\n    QlogJsonSeq { events: Vec<qlog::reader::Event> },\n    Netlog,\n}\n\npub struct LogFileData {\n    pub datastore: Datastore,\n    pub raw: RawLogEvents,\n}\n\npub struct LogFileParseResult {\n    pub details: LogFileDetails,\n    pub data: Vec<LogFileData>,\n}\n\npub fn parse_log_file(\n    config: &AppConfig,\n) -> Result<LogFileParseResult, Box<dyn Error>> {\n    match config.log_format {\n        SerializationFormat::QlogJson => {\n            println!(\"parsing qlog as JSON...\");\n            let mark = std::time::Instant::now();\n            let qlog = read_qlog_from_file(config.file.clone())?;\n            let vp = qlog.traces[0].vantage_point.clone().unwrap_or_default().ty;\n\n            let details = LogFileDetails {\n                file_schema: qlog.file_schema.clone(),\n                serialization_format: qlog.serialization_format.clone(),\n                qlog_vantage_point_type: VantagePointTypeShim { inner: vp },\n                sessions: BTreeMap::new(),\n            };\n            debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n\n            println!(\"populating datastore...\");\n            let mark = std::time::Instant::now();\n            // TODO: support more than one trace in a file\n            let datastore = Datastore::with_qlog_events(\n                &qlog.traces[0].events,\n                &details.qlog_vantage_point_type.inner,\n                !config.ignore_acks,\n            );\n            debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n\n            let raw = RawLogEvents::QlogJson {\n                events: qlog.traces[0].events.clone(),\n            };\n\n            Ok(LogFileParseResult {\n                details,\n                data: vec![LogFileData { datastore, raw }],\n            })\n        },\n\n        SerializationFormat::QlogJsonSeq => {\n            println!(\"parsing qlog as JSON-SEQ...\");\n            let mark = std::time::Instant::now();\n\n            let (qlog_reader, details) = qlog_seq_reader(config)?;\n\n            debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n\n            println!(\"populating datastore...\");\n            let mark = std::time::Instant::now();\n            let events: Vec<qlog::reader::Event> =\n                qlog_reader.into_iter().collect();\n            let datastore: Datastore = Datastore::with_sqlog_reader_events(\n                &events,\n                &details.qlog_vantage_point_type.inner,\n                !config.ignore_acks,\n            );\n            debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n\n            let events = if let Some(filter) = &config.qlog_wirefilter {\n                filter_sqlog_events(events, filter)\n            } else {\n                events\n            };\n\n            let raw = RawLogEvents::QlogJsonSeq { events };\n\n            Ok(LogFileParseResult {\n                details,\n                data: vec![LogFileData { datastore, raw }],\n            })\n        },\n\n        SerializationFormat::NetlogJson => {\n            println!(\"setting up parsing file as netlog...\");\n            let mark = std::time::Instant::now();\n\n            let file = std::fs::File::open(config.file.clone())?;\n            let mut reader = BufReader::new(file);\n\n            let constants = netlog_with_reader(&mut reader).unwrap();\n\n            // trace!(\"{:?}\", constants);\n            debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n\n            println!(\"parsing file ...\");\n            println!(\"Filtering on hostnames: {:#?}\", config.netlog_filter);\n            let mark = std::time::Instant::now();\n\n            let (data, sessions) = datastore::with_netlog_reader(\n                &mut reader,\n                config.netlog_filter.clone(),\n                &constants,\n            );\n            debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n\n            let details = LogFileDetails {\n                file_schema: constants.log_format_version.to_string(),\n                serialization_format: \"Chrome netlog\".to_string(),\n                qlog_vantage_point_type: VantagePointTypeShim {\n                    inner: qlog::VantagePointType::Client,\n                },\n                sessions,\n            };\n\n            Ok(LogFileParseResult { details, data })\n        },\n\n        _ => {\n            error!(\"Unknown log file format for {}\", config.filename);\n            Err(\"total fail\".into())\n        },\n    }\n}\n\npub fn read_qlog_from_file<P: AsRef<Path>>(\n    path: P,\n) -> Result<Qlog, Box<dyn Error>> {\n    let file = File::open(path)?;\n    let reader = BufReader::new(file);\n\n    let qlog = serde_json::from_reader(reader)?;\n\n    Ok(qlog)\n}\n\npub fn qlog_seq_reader(\n    config: &AppConfig,\n) -> Result<(QlogSeqReader<'_>, LogFileDetails), Box<dyn Error>> {\n    let file = std::fs::File::open(config.file.clone())?;\n    let reader = BufReader::new(file);\n\n    let qlog_reader = QlogSeqReader::new(Box::new(reader)).map_err(|e| {\n        std::io::Error::other(format!(\"problem reading file! {}\", e))\n    })?;\n\n    let vp = qlog_reader\n        .qlog\n        .trace\n        .vantage_point\n        .clone()\n        .unwrap_or_default()\n        .ty;\n    let log_file_details = LogFileDetails {\n        file_schema: qlog_reader.qlog.file_schema.clone(),\n        serialization_format: qlog_reader.qlog.serialization_format.clone(),\n        qlog_vantage_point_type: VantagePointTypeShim { inner: vp },\n        sessions: BTreeMap::new(),\n    };\n\n    Ok((qlog_reader, log_file_details))\n}\n\npub fn netlog_with_reader<R: std::io::BufRead>(\n    reader: &mut R,\n) -> Result<netlog::constants::Constants, Box<dyn Error>> {\n    // Netlog format is sort of newline-delimited. It starts off creating a JSON\n    // object, within that is an object containing constants, followed by an\n    // array of line-delimited events. This franken-JSON needs a bit of molding\n    // to fit serde parsing.\n    let mut buf = Vec::<u8>::new();\n\n    // read the constants line\n    let len = reader.read_until(b'\\n', &mut buf).unwrap();\n\n    // replace the trailing comma (,) with a brace (}) to close the object and\n    // make it parseable.\n    buf[len - 2] = b'}';\n\n    let res: Result<netlog::constants::ConstantsLine, serde_json::Error> =\n        serde_json::from_slice(&buf);\n\n    match res {\n        Ok(mut line) => {\n            line.constants.populate_id_keyed();\n\n            Ok(line.constants)\n        },\n\n        Err(e) => {\n            error!(\"Error deserializing: {}\", e);\n\n            // Just swallow the failure and move on\n\n            Err(e.into())\n        },\n    }\n}\n\npub fn stringify_last<T>(src: &[T]) -> String\nwhere\n    T: std::fmt::Debug,\n{\n    if src.len() == 1 {\n        \"n/a\".to_string()\n    } else {\n        format!(\"{:?}\", src.last().unwrap())\n    }\n}\n\n// slight hack: duplicate the previous point so\n// that no misleading line interpolation occurs\nfn push_interp<X: Clone, Y: Clone>(collection: &mut Vec<(X, Y)>, value: (X, Y)) {\n    let prev = collection.last().cloned();\n    let new_time = value.0.clone();\n\n    if let Some((_, y)) = prev {\n        collection.push((new_time, y));\n    }\n\n    collection.push(value)\n}\n\nfn create_file_recursive(filename: &str) -> std::io::Result<File> {\n    let path = std::path::Path::new(filename);\n    if let Some(dir) = path.parent() {\n        std::fs::create_dir_all(dir)?;\n    }\n\n    File::create(filename)\n}\n\npub fn category_and_type_from_name(name: &str) -> (String, String) {\n    let mut category = \"\".to_string();\n    let mut ty = \"\".to_string();\n\n    let split: Vec<&str> = name.split(':').collect();\n    if let Some(cat) = split.first() {\n        category = cat.to_string();\n    }\n    if let Some(t) = split.get(1) {\n        ty = t.to_string();\n    }\n\n    (category, ty)\n}\npub fn category_and_type_from_event<T: Serialize>(ev: &T) -> (String, String) {\n    let name = serde_json::to_value(ev).unwrap()[\"name\"]\n        .to_string()\n        .replace(\"\\\"\", \"\");\n    category_and_type_from_name(&name)\n}\n\npub mod config;\npub mod datastore;\npub mod plots;\npub mod reports;\npub mod request_stub;\npub mod seriesstore;\npub mod trackers;\n#[cfg(target_arch = \"wasm32\")]\npub mod web;\npub mod wirefilter;\n"
  },
  {
    "path": "qlog-dancer/src/main.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nextern crate clap;\n\nuse std::process::exit;\n\nuse log::debug;\nuse log::error;\nuse log::info;\nuse log::warn;\nuse qlog_dancer::config::AppConfig;\nuse qlog_dancer::parse_log_file;\nuse qlog_dancer::plots;\nuse qlog_dancer::plots::conn_flow_control;\nuse qlog_dancer::plots::conn_overview;\nuse qlog_dancer::plots::conn_overview::OverviewChartOutputType;\nuse qlog_dancer::plots::packet_received;\nuse qlog_dancer::plots::packet_sent;\nuse qlog_dancer::plots::pending::PendingPlotParams;\nuse qlog_dancer::plots::stream_multiplex;\nuse qlog_dancer::plots::stream_multiplex::MultiplexPlotsParams;\nuse qlog_dancer::plots::stream_sparks;\nuse qlog_dancer::plots::AreaMargin;\nuse qlog_dancer::plots::ChartMargin;\nuse qlog_dancer::plots::ChartOutputType;\nuse qlog_dancer::plots::ChartSize;\nuse qlog_dancer::plots::ClampParams;\nuse qlog_dancer::plots::PlotParameters;\nuse qlog_dancer::reports::report;\nuse qlog_dancer::seriesstore::SeriesStore;\nuse qlog_dancer::SerializationFormat;\n\nfn main() {\n    let rc = run();\n\n    exit(rc);\n}\n\nfn run() -> i32 {\n    env_logger::builder().init();\n\n    let mut config = match AppConfig::from_clap() {\n        Ok(v) => v,\n\n        Err(e) => {\n            error!(\"Error loading configuration, exiting: {}\", e);\n            return 1;\n        },\n    };\n\n    let mut log_file = match parse_log_file(&config) {\n        Ok(v) => v,\n\n        Err(_) => {\n            // Failed, so try a fallback once\n            match config.log_format {\n                SerializationFormat::QlogJson => {\n                    warn!(\"Failed to parse as qlog, trying sqlog\");\n                    config.log_format = SerializationFormat::QlogJsonSeq;\n                    parse_log_file(&config).unwrap()\n                },\n\n                SerializationFormat::QlogJsonSeq => {\n                    warn!(\"Failed to parse as sqlog, trying qlog\");\n                    config.log_format = SerializationFormat::QlogJson;\n                    parse_log_file(&config).unwrap()\n                },\n\n                _ => {\n                    error!(\"Can't parse your file sorry. Check the extension\");\n                    return -1;\n                },\n            }\n        },\n    };\n\n    println!();\n    println!(\"== Log details ==\");\n    println!(\"  Format: {}\", log_file.details.serialization_format);\n    println!(\"  Version: {}\", log_file.details.file_schema);\n    println!(\n        \"  Vantage point type: {}\",\n        log_file.details.qlog_vantage_point_type\n    );\n    println!();\n\n    if config.plot_conn_overview ||\n        config.plot_pkt_sent ||\n        config.plot_pkt_received ||\n        config.plot_sparks ||\n        config.plot_multiplex ||\n        config.plot_pending\n    {\n        if log_file.data.is_empty() {\n            error!(\"File exists but trace information was empty or invalid. If you used a netlog-filter, check it exactly matched session host in the file.\");\n            return 1;\n        }\n\n        let mut series_store = vec![];\n        info!(\"populating plot series data...\");\n        let mark = std::time::Instant::now();\n        for data in log_file.data.iter_mut() {\n            let ss = SeriesStore::from_datastore(&data.datastore);\n            series_store.push(ss);\n        }\n        debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n\n        let plot_params = PlotParameters {\n            clamp: ClampParams {\n                start: config.start,\n                end: config.end,\n                stream_y_max: config.stream_y_max,\n            },\n            cwnd_y_max: config.cwnd_y_max,\n            chart_size: ChartSize {\n                width: 1600,\n                height: 1200,\n            },\n            colors: AppConfig::colors(config.dark_mode),\n            chart_margin: ChartMargin {\n                top: 20,\n                bottom: 20,\n                left: 20,\n                right: 20,\n            },\n            area_margin: AreaMargin { x: 40, y: 80 },\n            display_chart_title: true,\n            display_legend: true,\n            display_minor_lines: true,\n        };\n\n        let overview_chart_config = OverviewChartOutputType::Png {\n            output_dir: config.charts_dir.clone(),\n            cwnd_y_max: plot_params.cwnd_y_max,\n            stream_y_max: plot_params.clamp.stream_y_max,\n        };\n\n        let chart_config = ChartOutputType::Png {\n            output_dir: config.charts_dir.clone(),\n            cwnd_y_max: plot_params.cwnd_y_max,\n            stream_y_max: plot_params.clamp.stream_y_max,\n        };\n\n        let zipper = log_file.data.iter().zip(series_store.iter());\n        for (data, ss) in zipper {\n            let label = format!(\n                \"session ID: {:?}, app proto: {:?} host: {:?}\",\n                data.datastore.session_id,\n                data.datastore.application_proto,\n                data.datastore.host\n            );\n\n            if config.plot_conn_overview {\n                let mark = std::time::Instant::now();\n                info!(\"drawing overview for {}...\", label);\n\n                conn_overview::plot_connection_overview(\n                    &plot_params,\n                    &config.filename,\n                    ss,\n                    &data.datastore,\n                    &overview_chart_config,\n                );\n\n                debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n            }\n\n            if config.plot_pkt_sent {\n                let mark = std::time::Instant::now();\n                info!(\"drawing packet sent chart for {}...\", label);\n                packet_sent::plot_packet_sent(\n                    &plot_params,\n                    &config.filename,\n                    ss,\n                    &data.datastore,\n                    &chart_config,\n                );\n\n                debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n            }\n\n            if config.plot_pkt_received {\n                let mark = std::time::Instant::now();\n                info!(\"drawing packet received chart for {}...\", label);\n                packet_received::plot_packet_received(\n                    &plot_params,\n                    &config.filename,\n                    ss,\n                    &data.datastore,\n                    &chart_config,\n                );\n\n                debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n            }\n\n            if config.plot_conn_flow_control {\n                let mark = std::time::Instant::now();\n                info!(\"drawing connection flow control chart for {}...\", label);\n                conn_flow_control::plot_conn_flow_control(\n                    &plot_params,\n                    &config.filename,\n                    ss,\n                    &data.datastore,\n                    &chart_config,\n                );\n                debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n            }\n\n            debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n\n            if config.plot_sparks {\n                let mark = std::time::Instant::now();\n                info!(\"drawing sparks for {}...\", label);\n\n                stream_sparks::plot_sparks(\n                    &config.sparks_layout,\n                    &config.filename,\n                    ss,\n                    &data.datastore,\n                    &chart_config,\n                    &chart_config,\n                    &chart_config,\n                    &chart_config,\n                );\n\n                debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n            }\n\n            if config.plot_multiplex {\n                let multiplex_params = MultiplexPlotsParams {\n                    clamp: ClampParams {\n                        start: config.start,\n                        end: config.end,\n                        stream_y_max: config.stream_y_max,\n                    },\n                    colors: AppConfig::colors(config.dark_mode),\n                    ..Default::default()\n                };\n                let mark = std::time::Instant::now();\n                info!(\"drawing multiplex for {}...\", label);\n                stream_multiplex::plot_stream_multiplexing(\n                    &multiplex_params,\n                    &config.filename,\n                    ss,\n                    &data.datastore,\n                    &chart_config,\n                );\n                debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n            }\n\n            if config.plot_pending {\n                let pending_params = PendingPlotParams {\n                    clamp: ClampParams {\n                        start: config.start,\n                        end: config.end,\n                        stream_y_max: config.stream_y_max,\n                    },\n                    chart_size: ChartSize {\n                        width: 1600,\n                        height: 600,\n                    },\n                    colors: AppConfig::colors(config.dark_mode),\n                    display_chart_title: true,\n                };\n\n                info!(\"drawing pending chart for {}...\", label);\n                let mark = std::time::Instant::now();\n\n                plots::pending::plot_pending(\n                    &pending_params,\n                    &config.filename,\n                    ss,\n                    &data.datastore,\n                    &chart_config,\n                );\n\n                debug!(\"\\tcomplete in {:?}\", std::time::Instant::now() - mark);\n            }\n        }\n    }\n\n    println!();\n\n    report(&log_file, &config);\n\n    print!(\"input logs parsed successfully, check output(s)\");\n\n    0\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/colors.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse plotters::style::RGBColor;\nuse plotters::style::BLACK;\nuse plotters::style::WHITE;\n\npub const FOREST_GREEN: RGBColor = RGBColor(15, 122, 27);\npub const PURPLE: RGBColor = RGBColor(54, 2, 89);\npub const TAUPE: RGBColor = RGBColor(133, 104, 3);\npub const MID_GREY: RGBColor = RGBColor(172, 172, 172);\npub const ORANGE: RGBColor = RGBColor(201, 93, 4);\npub const MUSTARD: RGBColor = RGBColor(158, 150, 2);\npub const SOFT_PINK: RGBColor = RGBColor(158, 82, 94);\npub const BROWN: RGBColor = RGBColor(74, 38, 2);\npub const BLUEY_BLACK: RGBColor = RGBColor(23, 32, 42);\npub const BLUEY_GREY: RGBColor = RGBColor(128, 139, 150);\npub const TEAL: RGBColor = RGBColor(0, 128, 128);\n\n/// Cycle through a set of colors\n///\n/// By default, the set is based on a modified version of the set from\n/// https://stackoverflow.com/a/13781114\npub struct ColorCycle {\n    pub colors: Vec<RGBColor>,\n    pub initial_index: usize,\n    pub tracking_index: usize,\n}\n\nimpl ColorCycle {\n    pub fn next_color(&mut self) -> RGBColor {\n        let color = self.colors[self.tracking_index];\n        if self.tracking_index == self.colors.len() - 1 {\n            self.tracking_index = 0\n        } else {\n            self.tracking_index += 1\n        }\n\n        color\n    }\n\n    pub fn reset(&mut self) {\n        self.tracking_index = self.initial_index;\n    }\n}\n\nimpl Default for ColorCycle {\n    fn default() -> Self {\n        let colors = vec![\n            RGBColor(204, 81, 81),\n            RGBColor(127, 51, 51),\n            RGBColor(81, 204, 204),\n            RGBColor(51, 127, 127),\n            RGBColor(142, 204, 81),\n            RGBColor(89, 127, 51),\n            RGBColor(142, 81, 204),\n            RGBColor(89, 51, 127),\n            RGBColor(204, 173, 81),\n            RGBColor(127, 108, 51),\n            RGBColor(81, 204, 112),\n            RGBColor(51, 127, 70),\n            RGBColor(81, 112, 204),\n            RGBColor(51, 70, 127),\n            RGBColor(204, 81, 173),\n            RGBColor(127, 51, 108),\n            RGBColor(204, 127, 81),\n            RGBColor(127, 79, 51),\n            RGBColor(188, 204, 81),\n            RGBColor(117, 127, 51),\n            RGBColor(96, 204, 81),\n            RGBColor(60, 127, 51),\n            RGBColor(81, 204, 158),\n            RGBColor(51, 127, 98),\n            RGBColor(81, 158, 204),\n            RGBColor(51, 98, 127),\n            RGBColor(96, 81, 204),\n            RGBColor(60, 51, 127),\n            RGBColor(188, 81, 204),\n            RGBColor(117, 51, 127),\n            RGBColor(204, 81, 127),\n            RGBColor(127, 51, 79),\n            RGBColor(204, 104, 81),\n            RGBColor(127, 65, 51),\n            RGBColor(204, 150, 81),\n            RGBColor(127, 94, 51),\n            RGBColor(204, 196, 81),\n            RGBColor(127, 122, 51),\n            RGBColor(165, 204, 81),\n            RGBColor(103, 127, 51),\n            RGBColor(119, 204, 81),\n            RGBColor(74, 127, 51),\n            RGBColor(81, 204, 89),\n            RGBColor(51, 127, 55),\n            RGBColor(81, 204, 135),\n            RGBColor(51, 127, 84),\n            RGBColor(81, 204, 181),\n            RGBColor(51, 127, 113),\n            RGBColor(81, 181, 204),\n            RGBColor(51, 113, 127),\n            RGBColor(81, 135, 204),\n            RGBColor(51, 84, 127),\n            RGBColor(81, 89, 204),\n            RGBColor(51, 55, 127),\n            RGBColor(119, 81, 204),\n            RGBColor(74, 51, 127),\n            RGBColor(165, 81, 204),\n            RGBColor(103, 51, 127),\n            RGBColor(204, 81, 196),\n            RGBColor(127, 51, 122),\n            RGBColor(204, 81, 150),\n            RGBColor(127, 51, 94),\n            RGBColor(204, 81, 104),\n            RGBColor(127, 51, 65),\n            RGBColor(204, 93, 81),\n            RGBColor(127, 58, 51),\n            RGBColor(204, 116, 81),\n            RGBColor(127, 72, 51),\n            RGBColor(204, 138, 81),\n            RGBColor(127, 86, 51),\n            RGBColor(204, 161, 81),\n            RGBColor(127, 101, 51),\n            RGBColor(204, 184, 81),\n            RGBColor(127, 115, 51),\n            RGBColor(200, 204, 81),\n            RGBColor(125, 127, 51),\n            RGBColor(177, 204, 81),\n            RGBColor(110, 127, 51),\n            RGBColor(154, 204, 81),\n            RGBColor(96, 127, 51),\n            RGBColor(131, 204, 81),\n            RGBColor(82, 127, 51),\n            RGBColor(108, 204, 81),\n            RGBColor(67, 127, 51),\n            RGBColor(85, 204, 81),\n            RGBColor(53, 127, 51),\n            RGBColor(81, 204, 100),\n            RGBColor(51, 127, 62),\n            RGBColor(81, 204, 123),\n            RGBColor(51, 127, 77),\n            RGBColor(81, 204, 146),\n            RGBColor(51, 127, 91),\n            RGBColor(81, 204, 169),\n            RGBColor(51, 127, 105),\n            RGBColor(81, 204, 192),\n            RGBColor(51, 127, 120),\n            RGBColor(81, 192, 204),\n            RGBColor(51, 120, 127),\n            RGBColor(81, 169, 204),\n            RGBColor(51, 105, 127),\n        ];\n\n        Self {\n            colors,\n            initial_index: 0,\n            tracking_index: 0,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct PlotColors {\n    pub fill: RGBColor,\n    pub axis: RGBColor,\n    pub bold_line: RGBColor,\n    pub light_line: RGBColor,\n    pub caption: RGBColor,\n}\n\nimpl Default for PlotColors {\n    fn default() -> Self {\n        LIGHT_MODE\n    }\n}\n\npub const LIGHT_MODE: PlotColors = PlotColors {\n    fill: WHITE,\n    axis: BLUEY_GREY,\n    bold_line: BLUEY_GREY,\n    light_line: BLUEY_GREY,\n    caption: BLACK,\n};\n\npub const DARK_MODE: PlotColors = PlotColors {\n    fill: BLUEY_BLACK,\n    axis: BLUEY_GREY,\n    bold_line: BLUEY_GREY,\n    light_line: BLUEY_GREY,\n    caption: WHITE,\n};\n"
  },
  {
    "path": "qlog-dancer/src/plots/congestion_control.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse full_palette::PURPLE_500;\nuse plotters::coord::types::RangedCoordf64;\nuse plotters::coord::types::RangedCoordu64;\nuse plotters::coord::Shift;\nuse plotters::prelude::*;\n\nuse crate::plots::colors::*;\nuse crate::plots::*;\n\nuse crate::datastore::Datastore;\nuse crate::seriesstore::SeriesStore;\n\nuse super::minmax::XYMinMax;\n\npub fn draw_congestion_plot<'a, DB: DrawingBackend + 'a>(\n    params: &PlotParameters, axis: &XYMinMax<u64>, ss: &SeriesStore,\n    ds: &Datastore, plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {\n    let mut builder = ChartBuilder::on(plot);\n\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        builder\n            .caption(\"Congestion\", chart_subtitle_style(&params.colors.caption));\n    }\n\n    let extended_y =\n        axis.y_range.start..axis.y_range.end + (axis.y_range.end / 10) * 5;\n\n    let mut plot = builder\n        .build_cartesian_2d(axis.x.range(), extended_y.clone())\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Data (bytes)\",\n        false,\n        &mut plot,\n    );\n\n    draw_cc_updates(\n        &ds.congestion_state_updates,\n        axis.y_range.clone(),\n        extended_y,\n        &mut plot,\n    );\n    draw_bytes_in_flight(&ss.local_bytes_in_flight, &mut plot);\n    draw_cwnd(&ss.local_cwnd, &mut plot);\n    draw_ssthresh(&ss.local_ssthresh, &mut plot);\n\n    if params.display_legend {\n        plot.configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::UpperLeft)\n            .draw()\n            .unwrap();\n    }\n\n    plot\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn plot_cc_plot<'a>(\n    params: &PlotParameters, ss: &SeriesStore, ds: &Datastore, canvas_id: &str,\n) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>\n{\n    let root =\n        make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);\n\n    let cwnd_y_max = if let Some(y_max) = params.cwnd_y_max {\n        y_max\n    } else {\n        // add a bit of margin\n        ss.y_max_congestion_plot + ss.y_max_congestion_plot / 10\n    };\n\n    // TODO set minimum\n    let axis = super::minmax::XYMinMax::init(\n        ss.sent_x_min..ss.sent_x_max,\n        params.clamp.start,\n        params.clamp.end,\n        0..cwnd_y_max,\n    );\n\n    draw_congestion_plot(params, &axis, ss, ds, &root)\n}\n\nfn draw_cc_updates<DB: DrawingBackend>(\n    data: &[(f64, u64, String)], y_range: std::ops::Range<u64>,\n    y_range_extended: std::ops::Range<u64>,\n    congestion_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    let my_label = |x: f64, y: u64, name: &str| {\n        let color = cc_state_to_color(name);\n        let text_width = name.len() as i32 * 6; // Rough estimate\n        let text_height = 12;\n\n        return EmptyElement::at((x, y))\n            //+ Circle::new((0, 0), 1, ShapeStyle::from(color).filled())\n            + Rectangle::new([(1, -2), (1 + text_width, text_height)],\n                        WHITE.mix(0.7).filled()) // 0.7 alpha for transparency\n            + Text::new(\n                name.to_owned(),\n                (1, 0),\n                (\"sans-serif\", 12.0).into_font().color(&color),\n            );\n    };\n\n    let mut woggle = vec![0];\n\n    for i in 1..5 {\n        woggle.push((y_range.end / 10) * i);\n    }\n\n    // Add labels from right to left, to ensure the z-ordering doesn't add lines\n    // over the labels\n    let iter = data.iter().rev();\n    for cc_state in iter {\n        let color = cc_state_to_color(&cc_state.2);\n        let x = cc_state.0;\n\n        let line_coords =\n            [(x, y_range_extended.start), (x, y_range_extended.end)];\n\n        congestion_chart\n            .draw_series(LineSeries::new(line_coords, color))\n            .unwrap();\n\n        let area = congestion_chart.plotting_area();\n\n        // control the vertical position of the text label to avoid overlaps.\n        let y = y_range.end + woggle[0];\n        area.draw(&my_label(x, y, &cc_state.2)).unwrap();\n\n        woggle.rotate_left(1);\n    }\n}\n\nfn draw_bytes_in_flight<DB: DrawingBackend>(\n    data: &[(f64, u64)],\n    congestion_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    draw_line(data, Some(\"bytes in flight\"), TAUPE, congestion_chart);\n}\n\nfn draw_cwnd<DB: DrawingBackend>(\n    data: &[(f64, u64)],\n    congestion_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    draw_line(data, Some(\"cwnd\"), PURPLE_500, congestion_chart);\n}\n\nfn draw_ssthresh<DB: DrawingBackend>(\n    data: &[(f64, u64)],\n    congestion_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    draw_line(data, Some(\"ssthresh\"), ORANGE, congestion_chart);\n}\n\n// Colors from ColorCycle list\nfn cc_state_to_color(cc_state: &str) -> RGBColor {\n    match cc_state {\n        // Cubic and Reno\n        \"slow_start\" => RGBColor(204, 81, 81),\n        \"recovery\" => RGBColor(127, 51, 51),\n        \"congestion_avoidance\" => RGBColor(81, 204, 204),\n\n        // BBR\n        \"bbr_startup\" => RGBColor(204, 81, 81),\n        \"bbr_drain\" => RGBColor(127, 51, 51),\n        \"bbr_probe_bw\" => RGBColor(81, 204, 204),\n        \"bbr_probe_rtt\" => RGBColor(51, 127, 127),\n\n        _ => BLACK,\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/conn_flow_control.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse minmax::XMinMax;\nuse plotters::chart::ChartContext;\nuse plotters::coord::types::RangedCoordf64;\nuse plotters::coord::types::RangedCoordu64;\nuse plotters::prelude::Cartesian2d;\nuse plotters::prelude::*;\n\nuse crate::plots::colors::*;\nuse crate::plots::*;\n\nuse crate::datastore::Datastore;\nuse crate::seriesstore::SeriesStore;\n\nconst Y_WIGGLE: f64 = 1.2;\n\nstruct XYMinMax {\n    pub x: XMinMax,\n    pub y_min: u64,\n    pub y_max: u64,\n}\n\nimpl XYMinMax {\n    fn init(params: &PlotParameters, ss: &SeriesStore, ds: &Datastore) -> Self {\n        let x = XMinMax::new(\n            ss.received_x_min,\n            ss.received_x_max,\n            params.clamp.start,\n            params.clamp.end,\n        );\n\n        Self {\n            x,\n            y_min: 0,\n            y_max: Self::y_max(ss, &ds.application_proto),\n        }\n    }\n\n    fn y_range(&self) -> std::ops::Range<u64> {\n        self.y_min..self.y_max\n    }\n\n    fn y_max(ss: &SeriesStore, proto: &ApplicationProto) -> u64 {\n        let y = match proto {\n            ApplicationProto::Http2 =>\n                *(ss.h2_send_window_absolute_max.get(&0).unwrap_or(&0)),\n            ApplicationProto::Http3 =>\n                ss.netlog_quic_stream_received_connection_cumulative\n                    .last()\n                    .unwrap_or(&(0.0, 0))\n                    .1,\n        };\n\n        (y as f64 * Y_WIGGLE) as u64\n    }\n}\n\nfn blocked_lines<DB: DrawingBackend>(\n    ds: &Datastore, y_max: u64,\n    chart: &mut ChartContext<'_, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n) {\n    if let Some(blocked) = ds.netlog_quic_server_window_blocked.get(&-1).cloned()\n    {\n        let blocked_lines = blocked\n            .iter()\n            .map(|t| PathElement::new([(*t, 0), (*t, y_max)], GREEN));\n\n        chart\n            .draw_series(blocked_lines)\n            .unwrap()\n            .label(\"server conn blocked\")\n            .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], GREEN));\n    }\n}\n\nfn received_data<DB: DrawingBackend>(\n    ss: &SeriesStore, proto: &ApplicationProto,\n    chart: &mut ChartContext<'_, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n) {\n    let points = match proto {\n        ApplicationProto::Http2 =>\n            ss.netlog_h2_stream_received_connection_cumulative.clone(),\n        ApplicationProto::Http3 =>\n            ss.netlog_quic_stream_received_connection_cumulative.clone(),\n    };\n\n    chart\n        .draw_series(LineSeries::new(points, ORANGE))\n        .unwrap()\n        .label(\"cumulative received stream data\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], ORANGE));\n}\n\nfn window_updates<DB: DrawingBackend>(\n    ss: &SeriesStore, ds: &Datastore,\n    chart: &mut ChartContext<'_, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n) {\n    let points = match ds.application_proto {\n        ApplicationProto::Http2 => {\n            if let Some(conn_win_updates) =\n                ss.h2_send_window_series_absolute.get(&0).cloned()\n            {\n                conn_win_updates\n            } else {\n                return;\n            }\n        },\n\n        ApplicationProto::Http3 => {\n            if let Some(conn_win_updates) =\n                ss.netlog_quic_client_side_window_updates.get(&-1).cloned()\n            {\n                conn_win_updates\n            } else {\n                return;\n            }\n        },\n    };\n\n    // Help us see the exact time a window update happened.\n    let circles: Vec<Circle<(f64, u64), i32>> = points\n        .iter()\n        .map(|point| Circle::new(*point, 2, BLUE))\n        .collect();\n\n    chart\n        .draw_series(LineSeries::new(points, BLUE))\n        .unwrap()\n        .label(\"sent window updates\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE));\n\n    chart.draw_series(circles).unwrap();\n}\n\nfn draw_series<'a, DB: DrawingBackend + 'a>(\n    chart: &mut ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n    params: &PlotParameters, ss: &SeriesStore, ds: &Datastore, axis: XYMinMax,\n) {\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Bytes\",\n        params.display_minor_lines,\n        chart,\n    );\n\n    // Draw the series\n    blocked_lines(ds, axis.y_max, chart);\n    received_data(ss, &ds.application_proto, chart);\n    window_updates(ss, ds, chart);\n\n    if params.display_legend {\n        // Draw the series legend\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::LowerRight)\n            .draw()\n            .unwrap();\n    }\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn plot_conn_flow_control_canvas<'a>(\n    params: &PlotParameters, filename: &str, ss: &SeriesStore, ds: &Datastore,\n    ty: &ChartOutputType,\n) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>\n{\n    let chart_config =\n        make_chart_config(\"flow_control\", params, filename, ds, ty);\n\n    let canvas_id: String = chart_config.canvas_id().unwrap_or_default();\n    let root =\n        make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);\n\n    let axis = XYMinMax::init(params, ss, ds);\n\n    let mut builder = ChartBuilder::on(&root);\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        let caption = format!(\"{} Connection Flow Control timeline\", filename);\n        builder.caption(caption, chart_title_style(&params.colors.caption));\n    }\n\n    let mut fc_chart = builder\n        .build_cartesian_2d(axis.x.range(), axis.y_range())\n        .unwrap();\n\n    draw_series(&mut fc_chart, params, ss, ds, axis);\n\n    fc_chart\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\npub fn plot_conn_flow_control(\n    params: &PlotParameters, filename: &str, ss: &SeriesStore, ds: &Datastore,\n    ty: &ChartOutputType,\n) {\n    let chart_config =\n        make_chart_config(\"flow_control\", params, filename, ds, ty);\n    let chart_path = chart_config.chart_filepath();\n\n    let root = make_chart_bitmap_area(\n        &chart_path,\n        params.chart_size,\n        params.colors,\n        params.chart_margin,\n    );\n\n    let axis = XYMinMax::init(params, ss, ds);\n\n    let mut builder = ChartBuilder::on(&root);\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        let caption = format!(\"{} Connection Flow Control timeline\", filename);\n        builder.caption(caption, chart_title_style(&params.colors.caption));\n    }\n\n    let mut fc_chart = builder\n        .build_cartesian_2d(axis.x.range(), axis.y_range())\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Bytes\",\n        params.display_minor_lines,\n        &mut fc_chart,\n    );\n\n    draw_series(&mut fc_chart, params, ss, ds, axis);\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/conn_overview.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse full_palette::PURPLE_500;\nuse minmax::XMinMax;\nuse plotters::coord::types::RangedCoordf64;\nuse plotters::coord::types::RangedCoordu64;\nuse plotters::coord::Shift;\nuse plotters::prelude::*;\n\nuse crate::plots::colors::*;\nuse crate::plots::*;\n\n#[cfg(not(target_arch = \"wasm32\"))]\nuse crate::datastore::Datastore;\nuse crate::seriesstore::SeriesStore;\n\n#[cfg(not(target_arch = \"wasm32\"))]\nuse super::congestion_control::draw_congestion_plot;\n#[cfg(not(target_arch = \"wasm32\"))]\nuse super::rtt::draw_rtt_plot;\n\nstruct XYMinMax {\n    pub x: XMinMax,\n    pub y_min: u64,\n    pub y_max: u64,\n}\n\nimpl XYMinMax {\n    fn init(params: &PlotParameters, ss: &SeriesStore, y_max: u64) -> Self {\n        let x = XMinMax::new(\n            ss.sent_x_min,\n            ss.sent_x_max,\n            params.clamp.start,\n            params.clamp.end,\n        );\n\n        Self { x, y_min: 0, y_max }\n    }\n\n    fn y_range(&self) -> std::ops::Range<u64> {\n        self.y_min..self.y_max\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum OverviewChartOutputType {\n    Png {\n        output_dir: String,\n        cwnd_y_max: Option<u64>,\n        stream_y_max: Option<u64>,\n    },\n\n    Canvas {\n        main_plot_canvas_id: String,\n        congestion_plot_canvas_id: String,\n        rtt_plot_canvas_id: String,\n    },\n}\n\nimpl From<OverviewChartOutputType> for ChartOutputType {\n    fn from(val: OverviewChartOutputType) -> Self {\n        match val {\n            OverviewChartOutputType::Png {\n                output_dir,\n                cwnd_y_max,\n                stream_y_max,\n            } => ChartOutputType::Png {\n                output_dir,\n                cwnd_y_max,\n                stream_y_max,\n            },\n\n            // Where we use this type in this module, this variant is a no-op,\n            // so we can put anything in the output TBH\n            OverviewChartOutputType::Canvas {\n                main_plot_canvas_id,\n                ..\n            } => ChartOutputType::Canvas {\n                canvas_id: main_plot_canvas_id,\n            },\n        }\n    }\n}\n\nfn draw_sent_max_data<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    draw_line(\n        &ss.sent_max_data,\n        Some(\"Sent MAX_DATA\"),\n        BLACK,\n        stream_chart,\n    );\n}\n\nfn draw_cumulative_sent_max_data<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    draw_line(\n        &ss.sum_sent_stream_max_data,\n        Some(\"Cumulative sent MAX_STREAM_DATA\"),\n        CYAN,\n        stream_chart,\n    );\n}\n\nfn draw_sent_max_stream_data<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    let mut label = Some(\"Sent MAX_STREAM_DATA\");\n\n    let streams = &ss.sent_stream_max_data;\n    for series in streams {\n        draw_line(series.1, label, BLUE, stream_chart);\n        label = None;\n    }\n}\n\nfn draw_buffer_reads<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    let mut label = Some(\"Stream buffer read\");\n\n    let streams = &ss.stream_buffer_reads;\n    for series in streams {\n        draw_line(series.1, label, FOREST_GREEN, stream_chart);\n        label = None;\n    }\n}\n\nfn draw_cumulative_buffer_reads<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    draw_line(\n        &ss.sum_stream_buffer_reads,\n        Some(\"Cumulative stream buffer read\"),\n        GREEN,\n        stream_chart,\n    );\n}\n\nfn draw_buffer_writes<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    let mut label = Some(\"Stream buffer write\");\n\n    let streams = &ss.stream_buffer_writes;\n    for series in streams {\n        draw_line(series.1, label, MAGENTA, stream_chart);\n        label = None;\n    }\n}\n\nfn draw_cumulative_buffer_writes<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    let data = &ss.sum_stream_buffer_writes;\n    let label = Some(\"Cumulative stream buffer writes\");\n\n    draw_line(data, label, RGBColor(255, 0, 0), stream_chart);\n}\n\nfn draw_buffer_dropped<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    let mut label = Some(\"Stream buffer dropped\");\n\n    let streams = &ss.stream_buffer_dropped;\n    for series in streams {\n        draw_line(series.1, label, ORANGE, stream_chart);\n        label = None;\n    }\n}\n\nfn draw_cumulative_buffer_dropped<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    let data = &ss.sum_stream_buffer_dropped;\n    let label = Some(\"Cumulative stream buffer dropped\");\n\n    draw_line(data, label, RGBColor(0, 0, 255), stream_chart);\n}\n\nfn draw_sent_stream_data<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    let mut label = Some(\"Sent stream data\");\n\n    let streams = &ss.sent_stream_frames_series;\n    for series in streams {\n        draw_line(series.1, label, PURPLE_500, stream_chart);\n        label = None;\n    }\n}\n\nfn draw_received_max_data<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    draw_line(\n        &ss.received_max_data,\n        Some(\"Cumulative received MAX_DATA\"),\n        MID_GREY,\n        stream_chart,\n    );\n}\n\nfn draw_cumulative_received_stream_max_data<DB: DrawingBackend>(\n    ss: &SeriesStore,\n    stream_chart: &mut ChartContext<\n        DB,\n        Cartesian2d<RangedCoordf64, RangedCoordu64>,\n    >,\n) {\n    draw_line(\n        &ss.sum_received_stream_max_data,\n        Some(\"Cumulative received MAX_STREAM_DATA\"),\n        MUSTARD,\n        stream_chart,\n    );\n}\n\n#[cfg(target_arch = \"wasm32\")]\nfn draw_main_plot<'a, DB: DrawingBackend + 'a>(\n    filename: &str, params: &PlotParameters, axis: XYMinMax, ss: &SeriesStore,\n    plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {\n    let mut builder = ChartBuilder::on(plot);\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        let caption = format!(\"{} Connection overview\", filename);\n        builder.caption(caption, chart_title_style(&params.colors.caption));\n    }\n\n    let mut chart = builder\n        .build_cartesian_2d(axis.x.range(), axis.y_range())\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Data (bytes)\",\n        params.display_minor_lines,\n        &mut chart,\n    );\n\n    draw_sent_max_data(ss, &mut chart);\n    draw_cumulative_sent_max_data(ss, &mut chart);\n    draw_sent_max_stream_data(ss, &mut chart);\n    draw_buffer_reads(ss, &mut chart);\n    draw_cumulative_buffer_reads(ss, &mut chart);\n    draw_buffer_writes(ss, &mut chart);\n    draw_cumulative_buffer_writes(ss, &mut chart);\n    draw_buffer_dropped(ss, &mut chart);\n    draw_cumulative_buffer_dropped(ss, &mut chart);\n    draw_sent_stream_data(ss, &mut chart);\n    draw_received_max_data(ss, &mut chart);\n    draw_cumulative_received_stream_max_data(ss, &mut chart);\n\n    if params.display_legend {\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::UpperLeft)\n            .draw()\n            .unwrap();\n    }\n\n    chart\n}\n\nfn draw_stream_send_plot<'a, DB: DrawingBackend + 'a>(\n    params: &PlotParameters, axis: XYMinMax, ss: &SeriesStore,\n    plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {\n    let mut builder = ChartBuilder::on(plot);\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        builder.caption(\n            \"Stream sends\",\n            chart_subtitle_style(&params.colors.caption),\n        );\n    }\n\n    let mut chart = builder\n        .build_cartesian_2d(axis.x.range(), axis.y_range())\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Data (bytes)\",\n        false,\n        &mut chart,\n    );\n\n    draw_cumulative_buffer_writes(ss, &mut chart);\n    draw_cumulative_buffer_dropped(ss, &mut chart);\n    draw_buffer_writes(ss, &mut chart);\n    draw_buffer_dropped(ss, &mut chart);\n    draw_sent_stream_data(ss, &mut chart);\n    draw_received_max_data(ss, &mut chart);\n    draw_cumulative_received_stream_max_data(ss, &mut chart);\n\n    if params.display_legend {\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::UpperLeft)\n            .draw()\n            .unwrap();\n    }\n\n    chart\n}\n\nfn draw_stream_recv_plot<'a, DB: DrawingBackend + 'a>(\n    params: &PlotParameters, axis: XYMinMax, ss: &SeriesStore,\n    plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {\n    let mut builder = ChartBuilder::on(plot);\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        builder.caption(\n            \"Stream receives\",\n            chart_subtitle_style(&params.colors.caption),\n        );\n    }\n\n    let mut chart = builder\n        .build_cartesian_2d(axis.x.range(), axis.y_range())\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Data (bytes)\",\n        false,\n        &mut chart,\n    );\n\n    draw_sent_max_data(ss, &mut chart);\n    draw_cumulative_sent_max_data(ss, &mut chart);\n    draw_sent_max_stream_data(ss, &mut chart);\n    draw_buffer_reads(ss, &mut chart);\n    draw_cumulative_buffer_reads(ss, &mut chart);\n\n    if params.display_legend {\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::UpperLeft)\n            .draw()\n            .unwrap();\n    }\n\n    chart\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\npub fn plot_connection_overview(\n    params: &PlotParameters, filename: &str, ss: &SeriesStore, ds: &Datastore,\n    ty: &OverviewChartOutputType,\n) {\n    let chart_config = ChartConfig {\n        title: \"conn-overview\".into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: ty.clone().into(),\n    };\n\n    chart_config.init_chart_dir();\n\n    let chart_path = chart_config.chart_filepath();\n\n    let root = make_chart_bitmap_area(\n        &chart_path,\n        params.chart_size,\n        params.colors,\n        params.chart_margin,\n    );\n\n    let (top_margin, bottom) = root.split_vertically((5).percent());\n    let (stream_plots, cwnd_rtt_area) = bottom.split_vertically((60).percent());\n    let (stream_send_plot, stream_recv_plot) =\n        stream_plots.split_vertically((50).percent());\n    let (congestion_plot, rtt_plot) =\n        cwnd_rtt_area.split_vertically((60).percent());\n\n    let stream_send_y_max = if let Some(y_max) = params.clamp.stream_y_max {\n        y_max\n    } else {\n        ss.y_max_stream_send_plot\n    };\n\n    let stream_recv_y_max = if let Some(y_max) = params.clamp.stream_y_max {\n        y_max\n    } else {\n        ss.y_max_stream_recv_plot\n    };\n\n    let stream_send_axis = XYMinMax::init(params, ss, stream_send_y_max);\n    let stream_recv_axis = XYMinMax::init(params, ss, stream_recv_y_max);\n\n    let cwnd_y_max = if let Some(y_max) = params.cwnd_y_max {\n        y_max\n    } else {\n        // add a bit of margin\n        ss.y_max_congestion_plot + ss.y_max_congestion_plot / 10\n    };\n\n    let common_axis = super::minmax::XYMinMax::init(\n        ss.sent_x_min..ss.sent_x_max,\n        params.clamp.start,\n        params.clamp.end,\n        0..cwnd_y_max,\n    );\n\n    top_margin\n        .draw_text(\n            format!(\"{} Connection overview\", filename).as_str(),\n            &chart_title_style(&params.colors.caption),\n            (0, 0),\n        )\n        .unwrap();\n    draw_stream_send_plot(params, stream_send_axis, ss, &stream_send_plot);\n    draw_stream_recv_plot(params, stream_recv_axis, ss, &stream_recv_plot);\n    draw_congestion_plot(params, &common_axis, ss, ds, &congestion_plot);\n    draw_rtt_plot(params, &common_axis, ss, &rtt_plot);\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn plot_main_plot<'a>(\n    params: &PlotParameters, filename: &str, ss: &SeriesStore, canvas_id: &str,\n) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>\n{\n    let root =\n        make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);\n\n    let stream_y_max = if let Some(y_max) = params.clamp.stream_y_max {\n        y_max\n    } else {\n        ss.y_max_stream_send_plot.max(ss.y_max_stream_recv_plot)\n    };\n\n    let stream_axis = XYMinMax::init(params, ss, stream_y_max);\n\n    draw_main_plot(filename, params, stream_axis, ss, &root)\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/minmax.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[derive(Clone, Copy)]\npub struct XMinMax {\n    pub min: f64,\n    pub max: f64,\n}\n\nimpl XMinMax {\n    pub fn new(min: f64, max: f64, start: Option<f64>, end: Option<f64>) -> Self {\n        let mut minmax = Self { min, max };\n\n        if let Some(s) = start {\n            minmax.min = s;\n        }\n\n        if let Some(e) = end {\n            minmax.max = minmax.max.min(e);\n        }\n\n        minmax\n    }\n\n    pub fn range(&self) -> std::ops::Range<f64> {\n        self.min..self.max\n    }\n}\n\npub struct XYMinMax<Y> {\n    pub x: XMinMax,\n    pub y_range: std::ops::Range<Y>,\n}\n\nimpl<Y> XYMinMax<Y> {\n    pub fn init(\n        x_data_range: std::ops::Range<f64>, x_start: Option<f64>,\n        x_end: Option<f64>, y_range: std::ops::Range<Y>,\n    ) -> Self {\n        let x =\n            XMinMax::new(x_data_range.start, x_data_range.end, x_start, x_end);\n\n        Self { x, y_range }\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::datastore::ApplicationProto;\nuse crate::datastore::Datastore;\nuse colors::PlotColors;\nuse plotters::coord::ranged1d::ValueFormatter;\nuse plotters::coord::types::RangedCoordf64;\nuse plotters::coord::types::RangedCoordu64;\nuse plotters::prelude::*;\n#[cfg(target_arch = \"wasm32\")]\nuse plotters_canvas::CanvasBackend;\n\n#[derive(Clone, Debug)]\npub enum ChartOutputType {\n    Png {\n        output_dir: String,\n        cwnd_y_max: Option<u64>,\n        stream_y_max: Option<u64>,\n    },\n\n    Canvas {\n        canvas_id: String,\n    },\n}\n\n#[derive(Clone, Debug)]\nstruct ChartConfig {\n    pub title: String,\n    pub input_filename: String,\n    // pub size: ChartSize,\n    // pub margin: ChartMargin,\n    // pub colors: PlotColors,\n    pub clamp: ClampParams,\n    pub ty: ChartOutputType,\n    pub app_proto: ApplicationProto,\n    pub host: Option<String>,\n    pub session_id: Option<i64>,\n}\n\nimpl ChartConfig {\n    pub fn chart_name(&self) -> String {\n        let proto = match self.app_proto {\n            ApplicationProto::Http2 => \"h2\",\n            ApplicationProto::Http3 => \"h3\",\n        };\n\n        let host = match (&self.host, &self.ty) {\n            (Some(h), ChartOutputType::Png { .. }) => {\n                // Hosts might include a port number and putting a colon in\n                // filenames might upset some systems.\n                format!(\"-{}\", h.replace(\":\", \"_\"))\n            },\n\n            (Some(h), ChartOutputType::Canvas { .. }) => h.to_string(),\n\n            _ => \"\".to_string(),\n        };\n\n        let session = if let Some(s) = self.session_id {\n            format!(\"-session{}\", s)\n        } else {\n            \"\".to_string()\n        };\n\n        let start = if let Some(s) = self.clamp.start {\n            format!(\"-s{}\", s)\n        } else {\n            \"\".to_string()\n        };\n\n        let end = if let Some(e) = self.clamp.end {\n            format!(\"-e{}\", e)\n        } else {\n            \"\".to_string()\n        };\n\n        match self.ty {\n            ChartOutputType::Png {\n                cwnd_y_max,\n                stream_y_max,\n                ..\n            } => {\n                let mut name = format!(\n                    \"{}-{}{}{}-{}{}{}\",\n                    self.input_filename,\n                    proto,\n                    host,\n                    session,\n                    self.title,\n                    start,\n                    end,\n                );\n\n                if let Some(y_max) = stream_y_max {\n                    name.push_str(&format!(\"-stream_max_y{}\", y_max));\n                }\n\n                if let Some(y_max) = cwnd_y_max {\n                    name.push_str(&format!(\"-cwnd_max_y{}\", y_max));\n                }\n\n                name\n            },\n\n            ChartOutputType::Canvas { .. } => format!(\n                \"{}-{}{}{}-{}{}{}\",\n                self.input_filename, proto, host, session, self.title, start, end\n            ),\n        }\n    }\n\n    pub fn chart_filepath(&self) -> String {\n        match &self.ty {\n            ChartOutputType::Png { output_dir, .. } => {\n                format!(\"{}/{}.png\", output_dir, self.chart_name())\n            },\n\n            // Canvas doesn't have a file\n            ChartOutputType::Canvas { .. } => self.chart_name(),\n        }\n    }\n\n    fn init_chart_dir(&self) {\n        match &self.ty {\n            ChartOutputType::Png { .. } => {\n                let output_filename = self.chart_filepath();\n                let path = std::path::Path::new(&output_filename);\n                if let Some(dir) = path.parent() {\n                    std::fs::create_dir_all(dir).unwrap();\n                }\n            },\n\n            // Canvas doesn't have a file\n            ChartOutputType::Canvas { .. } => (),\n        }\n    }\n\n    #[cfg(target_arch = \"wasm32\")]\n    fn canvas_id(&self) -> Option<String> {\n        match &self.ty {\n            ChartOutputType::Canvas { canvas_id } => Some(canvas_id.clone()),\n            ChartOutputType::Png { .. } => None,\n        }\n    }\n}\n\n#[cfg(not(target_arch = \"wasm32\"))]\npub fn make_chart_bitmap_area(\n    path: &str, size: ChartSize, colors: PlotColors, margin: ChartMargin,\n) -> DrawingArea<plotters::prelude::BitMapBackend<'_>, plotters::coord::Shift> {\n    let backend = BitMapBackend::new(path, (size.width, size.height));\n    let area = backend.into_drawing_area();\n    area.fill(&colors.fill).unwrap();\n    area.margin(margin.top, margin.bottom, margin.left, margin.right)\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn make_chart_canvas_area(\n    canvas_id: &str, colors: PlotColors, margin: ChartMargin,\n) -> plotters::drawing::DrawingArea<CanvasBackend, plotters::coord::Shift> {\n    let backend = CanvasBackend::new(canvas_id)\n        .unwrap_or_else(|| panic!(\"cannot find canvas {}\", canvas_id));\n    let area = backend.into_drawing_area();\n    area.fill(&colors.fill).unwrap();\n    area.margin(margin.top, margin.bottom, margin.left, margin.right)\n}\n\nfn make_chart_config(\n    title: &str, params: &PlotParameters, filename: &str, ds: &Datastore,\n    ty: &ChartOutputType,\n) -> ChartConfig {\n    let chart_config = ChartConfig {\n        title: title.into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: ty.clone(),\n    };\n\n    chart_config.init_chart_dir();\n\n    chart_config\n}\n\npub fn chart_title_style(color: &RGBColor) -> TextStyle<'_> {\n    TextStyle::from((\"sans-serif\", 40).into_font()).color(color)\n}\n\npub fn chart_subtitle_style(color: &RGBColor) -> TextStyle<'_> {\n    TextStyle::from((\"sans-serif\", 20).into_font()).color(color)\n}\n\npub fn chart_label_style(color: &RGBColor) -> TextStyle<'_> {\n    TextStyle::from((\"sans-serif\", 15).into_font()).color(color)\n}\n\nfn draw_mesh<XT, YT, X, Y, DB: DrawingBackend>(\n    colors: &PlotColors, x_desc: &str, y_desc: &str, draw_minor_lines: bool,\n    chart: &mut ChartContext<DB, Cartesian2d<X, Y>>,\n) where\n    X: Ranged<ValueType = XT> + ValueFormatter<XT>,\n    Y: Ranged<ValueType = YT> + ValueFormatter<YT>,\n{\n    let mut mesh = chart.configure_mesh();\n\n    mesh.axis_style(colors.axis)\n        .x_desc(x_desc)\n        .y_desc(y_desc)\n        .bold_line_style(colors.bold_line.mix(0.5));\n\n    // TODO: bizarre! From the docs, state the correct way to do this is to set\n    // number of light lines to zero. However, that causes the drawing to spin\n    // indefinatly. Perhaps some bug in plotters...\n    if !draw_minor_lines {\n        mesh.light_line_style(colors.bold_line.mix(0.0));\n        // mesh.x_max_light_lines(0);\n        // mesh.y_max_light_lines(0);\n    } else {\n        mesh.light_line_style(colors.light_line.mix(0.2));\n    }\n\n    mesh.label_style(chart_label_style(&colors.caption))\n        .draw()\n        .unwrap();\n}\n\n#[derive(Clone, Default, Debug)]\npub struct ClampParams {\n    pub start: Option<f64>,\n    pub end: Option<f64>,\n    pub stream_y_max: Option<u64>,\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct ChartSize {\n    pub width: u32,\n    pub height: u32,\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct ChartMargin {\n    pub top: u32,\n    pub bottom: u32,\n    pub left: u32,\n    pub right: u32,\n}\n\n#[derive(Clone, Copy, Debug, Default)]\npub struct AreaMargin {\n    pub x: u32,\n    pub y: u32,\n}\n\npub struct PlotParameters {\n    pub clamp: ClampParams,\n    pub cwnd_y_max: Option<u64>,\n    pub chart_size: ChartSize,\n    pub colors: PlotColors,\n    pub chart_margin: ChartMargin,\n    pub area_margin: AreaMargin,\n    pub display_chart_title: bool,\n    pub display_legend: bool,\n    pub display_minor_lines: bool,\n}\n\nfn draw_line<DB: DrawingBackend>(\n    data: &[(f64, u64)], label: Option<&str>, colour: RGBColor,\n    chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n) {\n    let c = chart\n        .draw_series(LineSeries::new(data.to_vec(), colour))\n        .unwrap();\n\n    if let Some(l) = label {\n        c.label(l).legend(move |(x, y)| {\n            PathElement::new(vec![(x, y), (x + 20, y)], colour)\n        });\n    }\n}\n\npub mod colors;\npub mod congestion_control;\npub mod conn_flow_control;\npub mod conn_overview;\npub mod minmax;\npub mod packet_received;\npub mod packet_sent;\npub mod pending;\npub mod rtt;\npub mod stream_multiplex;\npub mod stream_sparks;\n"
  },
  {
    "path": "qlog-dancer/src/plots/packet_received.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse packet_sent::draw_packet_sent_received_plot;\nuse plotters::prelude::*;\n\nuse crate::plots::*;\n\nuse crate::datastore::Datastore;\nuse crate::seriesstore::SeriesStore;\n\npub fn plot_packet_received(\n    params: &PlotParameters, filename: &str, ss: &SeriesStore, ds: &Datastore,\n    ty: &ChartOutputType,\n) {\n    let chart_config = ChartConfig {\n        title: \"packet-received\".into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: ty.clone(),\n    };\n\n    chart_config.init_chart_dir();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let chart_path = chart_config.chart_filepath();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let root = make_chart_bitmap_area(\n        &chart_path,\n        params.chart_size,\n        params.colors,\n        params.chart_margin,\n    );\n\n    #[cfg(target_arch = \"wasm32\")]\n    let canvas_id: String = chart_config.canvas_id().unwrap_or_default();\n\n    #[cfg(target_arch = \"wasm32\")]\n    let root =\n        make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);\n\n    let (raw_timings, _remainder) = root.split_vertically((60).percent());\n\n    draw_packet_sent_received_plot(false, filename, params, ss, &raw_timings);\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/packet_sent.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse minmax::XMinMax;\n\nuse plotters::coord::types::RangedCoordf64;\nuse plotters::coord::types::RangedCoordu64;\nuse plotters::coord::Shift;\nuse plotters::prelude::*;\n\nuse crate::plots::colors::*;\nuse crate::plots::*;\n\nuse crate::datastore::Datastore;\nuse crate::seriesstore::SeriesStore;\n\nstruct XYMinMax {\n    pub x: XMinMax,\n    pub y_min: u64,\n    pub y_max: u64,\n}\n\nimpl XYMinMax {\n    fn init(params: &PlotParameters, ss: &SeriesStore, y_max: u64) -> Self {\n        let x = XMinMax::new(\n            ss.sent_x_min,\n            ss.sent_x_max,\n            params.clamp.start,\n            params.clamp.end,\n        );\n\n        Self { x, y_min: 0, y_max }\n    }\n\n    fn y_range(&self) -> std::ops::Range<u64> {\n        self.y_min..self.y_max\n    }\n}\n\nconst Y_WIGGLE: f64 = 1.1;\n\npub fn draw_packet_sent_received_plot<'a, DB: DrawingBackend + 'a>(\n    is_sent: bool, filename: &str, params: &PlotParameters, ss: &SeriesStore,\n    plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {\n    let (caption, y_max) = if is_sent {\n        let y_max = (ss.y_max_onertt_pkt_sent_plot as f64 * Y_WIGGLE) as u64;\n\n        (format!(\"{} Packet Sent timeline\", filename), y_max)\n    } else {\n        let y_max = (ss.y_max_onertt_pkt_received_plot as f64 * Y_WIGGLE) as u64;\n\n        (format!(\"{} Packet Received timeline\", filename), y_max)\n    };\n\n    let axis = XYMinMax::init(params, ss, y_max);\n\n    let mut builder = ChartBuilder::on(plot);\n\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        builder.caption(caption, chart_title_style(&params.colors.caption));\n    }\n\n    let mut chart = builder\n        .build_cartesian_2d(axis.x.range(), axis.y_range())\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Packet Number\",\n        params.display_minor_lines,\n        &mut chart,\n    );\n\n    if is_sent {\n        chart\n            .draw_series(LineSeries::new(ss.onertt_packet_created.clone(), GREEN))\n            .unwrap()\n            .label(\"packet created\")\n            .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], GREEN));\n\n        chart\n            .draw_series(LineSeries::new(ss.onertt_packet_sent.clone(), ORANGE))\n            .unwrap()\n            .label(\"packet sent (packet number)\")\n            .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], ORANGE));\n\n        let packet_losses = ss\n            .onertt_packet_lost_hacky\n            .iter()\n            .map(|point| Cross::new(*point, 2, PURPLE));\n\n        chart\n            .draw_series(packet_losses)\n            .unwrap()\n            .label(\"packet lost (packet number)\")\n            .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], PURPLE));\n    } else {\n        let missing_packets_line = ss\n            .netlog_missing_packets\n            .iter()\n            .map(|t| PathElement::new([(*t, 0), (*t, y_max)], BLUE));\n\n        chart\n            .draw_series(missing_packets_line)\n            .unwrap()\n            .label(\"missing packets\")\n            .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE));\n\n        chart\n            .draw_series(LineSeries::new(\n                ss.onertt_packet_received.clone(),\n                ORANGE,\n            ))\n            .unwrap()\n            .label(\"packet received\")\n            .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], ORANGE));\n    }\n\n    if params.display_legend {\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::UpperLeft)\n            .draw()\n            .unwrap();\n    }\n\n    chart\n}\n\npub fn draw_packet_sent_lost_delivered_count_plot<'a, DB: DrawingBackend + 'a>(\n    params: &PlotParameters, ss: &SeriesStore,\n    plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {\n    let caption = \"Packet sent/lost/delivered counts\";\n\n    let y_max = (ss.y_max_onertt_pkt_sent_plot as f64 * Y_WIGGLE) as u64;\n\n    let axis = XYMinMax::init(params, ss, y_max);\n\n    let mut builder = ChartBuilder::on(plot);\n\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        builder.caption(caption, chart_subtitle_style(&params.colors.caption));\n    }\n\n    let mut chart = builder\n        .build_cartesian_2d(axis.x.range(), axis.y_range())\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Packet Count\",\n        params.display_minor_lines,\n        &mut chart,\n    );\n\n    chart\n        .draw_series(LineSeries::new(\n            ss.onertt_packet_sent_aggregate_count.clone(),\n            TAUPE,\n        ))\n        .unwrap()\n        .label(\"packet sent count\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], TAUPE));\n\n    chart\n        .draw_series(LineSeries::new(\n            ss.onertt_packet_delivered_aggregate_count.clone(),\n            BLUE,\n        ))\n        .unwrap()\n        .label(\"packet delivered count\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE));\n\n    chart\n        .draw_series(LineSeries::new(\n            ss.onertt_packet_lost_aggregate_count.clone(),\n            SOFT_PINK,\n        ))\n        .unwrap()\n        .label(\"packet lost count\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], SOFT_PINK));\n\n    if params.display_legend {\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::UpperLeft)\n            .draw()\n            .unwrap();\n    }\n\n    chart\n}\n\nfn draw_delta_plot<'a, DB: DrawingBackend + 'a>(\n    params: &PlotParameters, ss: &SeriesStore,\n    plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordu64, RangedCoordf64>> {\n    let y_range = ss.y_min_onertt_packet_created_sent_delta..\n        (ss.y_max_onertt_packet_created_sent_delta * Y_WIGGLE);\n\n    let mut builder = ChartBuilder::on(plot);\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        builder.caption(\n            \"Packet created/sent Delta timing\",\n            chart_subtitle_style(&params.colors.caption),\n        );\n    }\n\n    let mut chart = builder\n        .build_cartesian_2d(0..ss.y_max_onertt_pkt_sent_plot, y_range)\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Packet Number\",\n        \"Delta time (ms)\",\n        params.display_minor_lines,\n        &mut chart,\n    );\n\n    // Draw the series\n\n    // not sure best way to render these, lines or points?\n    let lines =\n        LineSeries::new(ss.onertt_packet_created_sent_delta.clone(), BLUE);\n\n    let _crosses = ss\n        .onertt_packet_created_sent_delta\n        .iter()\n        .map(|point| Cross::new(*point, 2, BLUE));\n\n    chart\n        .draw_series(lines)\n        .unwrap()\n        .label(\"packet created/sent delta\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLACK));\n\n    if params.display_legend {\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::MiddleMiddle)\n            .draw()\n            .unwrap();\n    }\n\n    chart\n}\n\nfn draw_pacing_rate_plot<'a, DB: DrawingBackend + 'a>(\n    params: &PlotParameters, ss: &SeriesStore, _ds: &Datastore,\n    plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordu64>> {\n    let y_max = ss\n        .max_pacing_rate\n        .max(ss.max_delivery_rate)\n        .max(ss.max_send_rate)\n        .max(ss.max_ack_rate);\n    let y_max = (y_max as f64 * Y_WIGGLE) as u64;\n    let axis = XYMinMax::init(params, ss, y_max);\n    let mut builder = ChartBuilder::on(plot);\n\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        builder.caption(\n            \"Pacing Rate vs Delivery Rate\",\n            chart_subtitle_style(&params.colors.caption),\n        );\n    }\n\n    let mut chart = builder\n        .build_cartesian_2d(axis.x.range(), axis.y_range())\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"Rate (bytes/sec)\",\n        params.display_minor_lines,\n        &mut chart,\n    );\n\n    // Draw all 4 rate series\n    chart\n        .draw_series(LineSeries::new(ss.local_pacing_rate.clone(), PURPLE))\n        .unwrap()\n        .label(\"pacing rate\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], PURPLE));\n\n    chart\n        .draw_series(LineSeries::new(ss.local_delivery_rate.clone(), TEAL))\n        .unwrap()\n        .label(\"delivery rate\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], TEAL));\n\n    chart\n        .draw_series(LineSeries::new(ss.local_send_rate.clone(), ORANGE))\n        .unwrap()\n        .label(\"send rate\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], ORANGE));\n\n    chart\n        .draw_series(LineSeries::new(ss.local_ack_rate.clone(), BLUE))\n        .unwrap()\n        .label(\"ack rate\")\n        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE));\n\n    if params.display_legend {\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::UpperLeft)\n            .draw()\n            .unwrap();\n    }\n\n    chart\n}\n\npub fn plot_packet_sent(\n    params: &PlotParameters, filename: &str, ss: &SeriesStore, ds: &Datastore,\n    ty: &ChartOutputType,\n) {\n    let chart_config = make_chart_config(\"packet-sent\", params, filename, ds, ty);\n\n    chart_config.init_chart_dir();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let chart_path = chart_config.chart_filepath();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let root = make_chart_bitmap_area(\n        &chart_path,\n        params.chart_size,\n        params.colors,\n        params.chart_margin,\n    );\n\n    #[cfg(target_arch = \"wasm32\")]\n    let canvas_id: String = chart_config.canvas_id().unwrap_or_default();\n\n    #[cfg(target_arch = \"wasm32\")]\n    let root =\n        make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);\n    let (raw_timings, remainder) = root.split_vertically((33).percent());\n    let (counts, remainder) = remainder.split_vertically((33).percent());\n\n    let (delta_timings, pacing_rate) = remainder.split_vertically((50).percent());\n\n    draw_packet_sent_received_plot(true, filename, params, ss, &raw_timings);\n    draw_packet_sent_lost_delivered_count_plot(params, ss, &counts);\n    draw_delta_plot(params, ss, &delta_timings);\n    draw_pacing_rate_plot(params, ss, ds, &pacing_rate);\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn plot_packet_sent_plot_canvas<'a>(\n    params: &PlotParameters, filename: &str, ss: &SeriesStore, canvas_id: &str,\n) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>\n{\n    let root =\n        make_chart_canvas_area(canvas_id, params.colors, params.chart_margin);\n\n    draw_packet_sent_received_plot(true, filename, params, ss, &root)\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn plot_packet_sent_lost_delivered_count_plot<'a>(\n    params: &PlotParameters, ss: &SeriesStore, canvas_id: &str,\n) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>\n{\n    let root =\n        make_chart_canvas_area(canvas_id, params.colors, params.chart_margin);\n\n    draw_packet_sent_lost_delivered_count_plot(params, ss, &root)\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn plot_packet_sent_delta_plot_canvas<'a>(\n    params: &PlotParameters, ss: &SeriesStore, canvas_id: &str,\n) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordu64, RangedCoordf64>>\n{\n    let root =\n        make_chart_canvas_area(canvas_id, params.colors, params.chart_margin);\n\n    draw_delta_plot(params, ss, &root)\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn plot_packet_sent_pacing_rate_plot_canvas<'a>(\n    params: &PlotParameters, ss: &SeriesStore, ds: &Datastore, canvas_id: &str,\n) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordu64>>\n{\n    let root =\n        make_chart_canvas_area(canvas_id, params.colors, params.chart_margin);\n\n    draw_pacing_rate_plot(params, ss, ds, &root)\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/pending.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Pending charts\n//! A single image file containing a view of pending vs. in-flight requests\n\nuse plotters::prelude::*;\n\nuse crate::datastore::{\n    self,\n};\n\nuse crate::plots::*;\n\nuse crate::datastore::Datastore;\nuse crate::seriesstore::SeriesStore;\n\n#[derive(Debug, Default)]\npub struct PendingPlotParams {\n    pub clamp: ClampParams,\n    pub chart_size: ChartSize,\n    pub colors: PlotColors,\n    pub display_chart_title: bool,\n}\n\nstruct PendingStack {\n    pub time: f64,\n    pub pending: i32,\n    pub in_flight: i32,\n}\n\npub fn plot_pending(\n    params: &PendingPlotParams, filename: &str, ss: &SeriesStore, ds: &Datastore,\n    ty: &ChartOutputType,\n) {\n    // TODO: put some stuff in series store\n    let x_max = match ds.vantage_point {\n        datastore::VantagePoint::Client => ss.received_x_max,\n        datastore::VantagePoint::Server => ss.sent_x_max,\n    };\n\n    let mut y_max = 0;\n\n    let chart_config = ChartConfig {\n        title: \"pending\".into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: ty.clone(),\n    };\n\n    chart_config.init_chart_dir();\n\n    let mut x = 0f64;\n    let step_size = 1f64;\n\n    let mut series = vec![];\n\n    while x < x_max {\n        let mut pending = 0;\n        let mut in_flight = 0;\n\n        for req in ds.http_requests.values() {\n            match (\n                req.time_discovery,\n                req.time_first_headers_tx,\n                req.time_fin_rx,\n            ) {\n                (Some(dt), Some(hdrs_tx), Some(fin)) => {\n                    if x >= dt && x < hdrs_tx && x < fin {\n                        pending += 1;\n                    } else if x >= hdrs_tx && x < fin {\n                        in_flight += 1;\n                    }\n                },\n\n                (Some(dt), None, None) =>\n                    if x >= dt {\n                        pending += 1;\n                    },\n\n                (Some(dt), Some(hdrs_tx), None) =>\n                    if x >= dt {\n                        pending += 1;\n                    } else if x >= hdrs_tx {\n                        in_flight += 1;\n                    },\n\n                _ => (),\n            }\n        }\n        series.push(PendingStack {\n            time: x,\n            pending,\n            in_flight,\n        });\n        x += step_size;\n\n        y_max = y_max.max(pending + in_flight);\n    }\n\n    // let size = ChartSize{ width: 1600, height: 600};\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let chart_path = chart_config.chart_filepath();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let root = make_chart_bitmap_area(\n        &chart_path,\n        params.chart_size,\n        params.colors,\n        ChartMargin::default(),\n    );\n\n    #[cfg(target_arch = \"wasm32\")]\n    let canvas_id: String = chart_config.canvas_id().unwrap_or_default();\n\n    #[cfg(target_arch = \"wasm32\")]\n    let root =\n        make_chart_canvas_area(&canvas_id, params.colors, ChartMargin::default());\n\n    let mut builder = ChartBuilder::on(&root);\n    builder\n        .set_label_area_size(LabelAreaPosition::Left, 40)\n        .set_label_area_size(LabelAreaPosition::Bottom, 40);\n\n    if params.display_chart_title {\n        let caption =\n            format!(\"{} Pending vs. In-flight requests (stacked)\", filename);\n        builder.caption(caption, chart_title_style(&params.colors.caption));\n    }\n\n    let mut ctx = builder.build_cartesian_2d(0f64..x_max, 0..y_max).unwrap();\n\n    ctx.configure_mesh()\n        .axis_style(params.colors.axis)\n        .bold_line_style(params.colors.bold_line.mix(0.5))\n        .light_line_style(params.colors.light_line.mix(0.2))\n        .label_style(chart_label_style(&params.colors.caption))\n        .draw()\n        .unwrap();\n\n    ctx.draw_series(series.iter().map(|stack| {\n        // make sure bars are narrow enough that they don't bleed over each\n        // step. If we don't we'll miss periods where there's no requests in\n        // flight.\n        let shrink = step_size / 4f64;\n        let x0 = stack.time + shrink;\n        let x1 = x0 + (step_size - shrink);\n\n        // Clippy wants to remove the variable assignment that helps understand\n        // WTF this stuff is doing, screw clippy.\n        #[allow(clippy::let_and_return)]\n        let in_flight_bar = Rectangle::new(\n            [(x0, 0), (x1, stack.in_flight)],\n            colors::FOREST_GREEN.filled(),\n        );\n        in_flight_bar\n    }))\n    .unwrap()\n    .label(\"In-flight requests\")\n    .legend(|(x, y)| {\n        PathElement::new(vec![(x, y), (x + 20, y)], colors::FOREST_GREEN)\n    });\n\n    ctx.draw_series(series.iter().map(|stack| {\n        // make sure bars are narrow enough that they don't bleed over each\n        // step. If we don't we'll miss periods where there's no requests in\n        // flight.\n        let shrink = step_size / 3f64;\n        let x0 = stack.time + shrink;\n        let x1 = x0 + (step_size - shrink);\n\n        // pending is stack on top of in-flight\n        // Clippy wants to remove the variable assignment that helps understand\n        // WTF this stuff is doing, screw clippy.\n        #[allow(clippy::let_and_return)]\n        let pending_bar = Rectangle::new(\n            [(x0, stack.in_flight), (x1, stack.in_flight + stack.pending)],\n            colors::ORANGE.filled(),\n        );\n        pending_bar\n    }))\n    .unwrap()\n    .label(\"Pending requests\")\n    .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], colors::ORANGE));\n\n    ctx.configure_series_labels()\n        .label_font(chart_label_style(&params.colors.caption))\n        .background_style(params.colors.fill.mix(0.8))\n        .border_style(params.colors.axis)\n        .position(SeriesLabelPosition::UpperRight)\n        .draw()\n        .unwrap();\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/rtt.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse minmax::XYMinMax;\nuse plotters::coord::types::RangedCoordf32;\nuse plotters::coord::types::RangedCoordf64;\n\nuse plotters::coord::Shift;\nuse plotters::prelude::*;\n\nuse crate::plots::colors::*;\nuse crate::plots::*;\n\nuse crate::seriesstore::SeriesStore;\n\nfn draw_rtt_series<DB: DrawingBackend>(\n    data: &[(f64, f32)], label: &str, colour: RGBColor,\n    rtt_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordf32>>,\n) {\n    rtt_chart\n        .draw_series(LineSeries::new(data.to_vec(), colour))\n        .unwrap()\n        .label(label)\n        .legend(move |(x, y)| {\n            PathElement::new(vec![(x, y), (x + 20, y)], colour)\n        });\n}\n\nfn draw_min_rtt<DB: DrawingBackend>(\n    data: &[(f64, f32)],\n    rtt_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordf32>>,\n) {\n    draw_rtt_series(data, \"Min RTT\", SOFT_PINK, rtt_chart);\n}\n\nfn draw_latest_rtt<DB: DrawingBackend>(\n    data: &[(f64, f32)],\n    rtt_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordf32>>,\n) {\n    draw_rtt_series(data, \"Latest RTT\", ORANGE, rtt_chart);\n}\n\nfn draw_smoothed_rtt<DB: DrawingBackend>(\n    data: &[(f64, f32)],\n    rtt_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordf32>>,\n) {\n    draw_rtt_series(data, \"Smoothed RTT\", BROWN, rtt_chart);\n}\n\npub fn draw_rtt_plot<'a, DB: DrawingBackend + 'a>(\n    params: &PlotParameters, axis: &XYMinMax<u64>, ss: &SeriesStore,\n    plot: &plotters::drawing::DrawingArea<DB, Shift>,\n) -> ChartContext<'a, DB, Cartesian2d<RangedCoordf64, RangedCoordf32>> {\n    let mut builder = ChartBuilder::on(plot);\n    builder\n        .x_label_area_size(params.area_margin.x)\n        .y_label_area_size(params.area_margin.y);\n\n    if params.display_chart_title {\n        builder.caption(\"RTT\", chart_subtitle_style(&params.colors.caption));\n    }\n    let mut chart = builder\n        .build_cartesian_2d(\n            axis.x.range(),\n            0.0f32..(ss.y_max_rtt_plot + ss.y_max_rtt_plot / 10.0),\n        )\n        .unwrap();\n\n    draw_mesh(\n        &params.colors,\n        \"Relative time (ms)\",\n        \"RTT (ms)\",\n        false,\n        &mut chart,\n    );\n\n    draw_min_rtt(&ss.local_min_rtt, &mut chart);\n    draw_latest_rtt(&ss.local_latest_rtt, &mut chart);\n    draw_smoothed_rtt(&ss.local_smoothed_rtt, &mut chart);\n\n    if params.display_legend {\n        chart\n            .configure_series_labels()\n            .label_font(chart_label_style(&params.colors.caption))\n            .background_style(params.colors.fill.mix(0.8))\n            .border_style(params.colors.axis)\n            .position(SeriesLabelPosition::UpperLeft)\n            .draw()\n            .unwrap();\n    }\n\n    chart\n}\n\n#[cfg(target_arch = \"wasm32\")]\npub fn plot_rtt_plot<'a>(\n    params: &PlotParameters, ss: &SeriesStore, canvas_id: &str,\n) -> ChartContext<'a, CanvasBackend, Cartesian2d<RangedCoordf64, RangedCoordf32>>\n{\n    let root =\n        make_chart_canvas_area(&canvas_id, params.colors, params.chart_margin);\n\n    let cwnd_y_max = if let Some(y_max) = params.cwnd_y_max {\n        y_max\n    } else {\n        // add a bit of margin\n        ss.y_max_congestion_plot + ss.y_max_congestion_plot / 10\n    };\n\n    // TODO set minimum\n    let axis = super::minmax::XYMinMax::init(\n        ss.sent_x_min..ss.sent_x_max,\n        params.clamp.start,\n        params.clamp.end,\n        0..cwnd_y_max,\n    );\n\n    draw_rtt_plot(params, &axis, ss, &root)\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/stream_multiplex.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Multiplexing charts\n//! A single image file containing various renderings of stream and H3 DATA\n\nuse log::warn;\nuse plotters::prelude::*;\n\nuse crate::datastore::VantagePoint;\nuse crate::plots::colors::*;\nuse crate::plots::*;\n\nuse crate::datastore::Datastore;\nuse crate::seriesstore::SeriesStore;\n\npub struct MultiplexPlotsParams {\n    pub clamp: ClampParams,\n    pub bidi_only: bool,\n    pub width: u32,\n    pub y_spacer: u32,\n    pub right_shrink_margin: u32,\n    pub combined_plot_height: u32,\n    pub frame_swimlane_height: u32,\n    pub bubbles_swimlane_height: u32,\n    pub colors: PlotColors,\n    pub area_margin: AreaMargin,\n}\n\nimpl Default for MultiplexPlotsParams {\n    fn default() -> Self {\n        Self {\n            bidi_only: true,\n            width: 4000,\n            y_spacer: 20,\n            right_shrink_margin: 50,\n            combined_plot_height: 300,\n            frame_swimlane_height: 60,\n            bubbles_swimlane_height: 100,\n            clamp: Default::default(),\n            colors: Default::default(),\n            area_margin: AreaMargin { x: 40, y: 0 },\n        }\n    }\n}\n\npub fn plot_stream_multiplexing(\n    params: &MultiplexPlotsParams, filename: &str, ss: &SeriesStore,\n    ds: &Datastore, ty: &ChartOutputType,\n) {\n    let (mut x_min, mut x_max) = match ds.vantage_point {\n        VantagePoint::Client => (ss.received_x_min, ss.received_x_max),\n        VantagePoint::Server => (ss.sent_x_min, ss.sent_x_max),\n    };\n\n    // Clamp plot x-axis if the user told us.\n    if let Some(s) = params.clamp.start {\n        x_min = s;\n    }\n\n    if let Some(e) = params.clamp.end {\n        x_max = x_max.min(e);\n    }\n\n    let http_requests = match ds.vantage_point {\n        VantagePoint::Client => &ds.http_requests,\n        VantagePoint::Server => &ds.http_requests,\n    };\n\n    let y_spacer = 50;\n\n    let data_frames_all_combined_x = params.width - params.right_shrink_margin;\n    let data_frames_all_combined_y: i32 = params.combined_plot_height as i32;\n\n    let data_frames_mini_offset_y = data_frames_all_combined_y + y_spacer;\n    let data_frames_mini_x = params.width - params.right_shrink_margin;\n    let data_frames_mini_y = params.frame_swimlane_height;\n\n    let bubbles_x = params.width - params.right_shrink_margin;\n    let bubbles_y = params.bubbles_swimlane_height;\n    let bubbles_offset_y =\n        data_frames_mini_offset_y + (http_requests.len() * 25) as i32;\n\n    let stream_frames_all_combined_x = params.width - params.right_shrink_margin;\n    let stream_frames_all_combined_y = params.combined_plot_height;\n    let stream_frames_all_combined_offset_y =\n        bubbles_offset_y + (http_requests.len() * 70) as i32;\n\n    let stream_frames_mini_offset_y = stream_frames_all_combined_offset_y +\n        stream_frames_all_combined_y as i32 +\n        y_spacer;\n    let stream_frames_mini_x = params.width - params.right_shrink_margin;\n    let stream_frames_mini_y = params.frame_swimlane_height;\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let full_y =\n        stream_frames_mini_offset_y as u32 + (http_requests.len() * 25) as u32;\n\n    let chart_config = ChartConfig {\n        title: \"stream-multiplexing\".into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: ty.clone(),\n    };\n\n    chart_config.init_chart_dir();\n\n    let margin = ChartMargin {\n        top: 20,\n        bottom: 20,\n        left: 20,\n        right: 20,\n    };\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let size = ChartSize {\n        width: params.width,\n        height: full_y,\n    };\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let chart_path = chart_config.chart_filepath();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let root = make_chart_bitmap_area(&chart_path, size, params.colors, margin);\n\n    #[cfg(target_arch = \"wasm32\")]\n    let canvas_id: String = chart_config.canvas_id().unwrap_or_default();\n\n    #[cfg(target_arch = \"wasm32\")]\n    let root = make_chart_canvas_area(&canvas_id, params.colors, margin);\n\n    let stream_frame_series = match ds.vantage_point {\n        VantagePoint::Client => &ss.received_stream_frames_series,\n        VantagePoint::Server => &ss.sent_stream_frames_series,\n    };\n\n    //// Plot the data frames first\n    let mut color_cyle = ColorCycle::default();\n\n    color_cyle.reset();\n\n    let data_frames_all_combined_temp_root = root.clone().shrink(\n        (0, 0),\n        (data_frames_all_combined_x, data_frames_all_combined_y),\n    );\n\n    let mut data_frames_all_combined_chart =\n        ChartBuilder::on(&data_frames_all_combined_temp_root)\n            .caption(\n                \"DATA frame events\",\n                chart_title_style(&params.colors.caption),\n            )\n            .x_label_area_size(params.area_margin.x)\n            .y_label_area_size(params.area_margin.y)\n            .build_cartesian_2d(x_min..x_max, 0..2)\n            .unwrap();\n\n    data_frames_all_combined_chart\n        .configure_mesh()\n        .disable_mesh()\n        .axis_style(params.colors.axis)\n        .label_style(chart_label_style(&params.colors.caption))\n        .draw()\n        .unwrap();\n\n    for (id, stub) in http_requests {\n        if params.bidi_only && (id & 0x2) != 0 {\n            continue;\n        }\n\n        let data_frames = match ds.vantage_point {\n            VantagePoint::Client => &stub.time_data_rx_set,\n            VantagePoint::Server => &stub.time_data_tx_set,\n        };\n\n        let style = color_cyle.next_color();\n\n        let lines = data_frames\n            .iter()\n            .map(|point| PathElement::new([(point.0, 2), (point.0, 0)], style));\n\n        data_frames_all_combined_chart\n            .draw_series(lines.clone())\n            .unwrap();\n    }\n\n    //// Now plot the stream frames combined\n    color_cyle.reset();\n\n    let data_frames_all_combined_temp_root = root.clone().shrink(\n        (0, stream_frames_all_combined_offset_y),\n        (stream_frames_all_combined_x, stream_frames_all_combined_y),\n    );\n\n    let mut stream_frames_all_combined_chart =\n        ChartBuilder::on(&data_frames_all_combined_temp_root)\n            .caption(\n                \"STREAM frame events\",\n                chart_title_style(&params.colors.caption),\n            )\n            .x_label_area_size(params.area_margin.x)\n            .y_label_area_size(params.area_margin.y)\n            .build_cartesian_2d(x_min..x_max, 0..1)\n            .unwrap();\n\n    stream_frames_all_combined_chart\n        .configure_mesh()\n        .disable_mesh()\n        .axis_style(params.colors.axis)\n        .label_style(chart_label_style(&params.colors.caption))\n        .draw()\n        .unwrap();\n\n    for (id, stream_frames) in stream_frame_series {\n        if params.bidi_only && (id & 0x2) != 0 {\n            continue;\n        }\n\n        let style = color_cyle.next_color();\n\n        let lines = stream_frames\n            .iter()\n            .map(|point| PathElement::new([(point.0, 2), (point.0, 0)], style));\n\n        stream_frames_all_combined_chart.draw_series(lines).unwrap();\n    }\n\n    //// now the mini charts\n    color_cyle.reset();\n\n    let mut upper_y = 0;\n    let mut bubbles_upper_y = 0;\n    let mini_chart_axis_y_range = 0..4;\n    let marker_y = 2;\n    let marker_size = 5;\n\n    let max_data_len = match ds.vantage_point {\n        VantagePoint::Client => ds.largest_data_frame_rx_length_global,\n        VantagePoint::Server => ds.largest_data_frame_tx_length_global,\n    };\n\n    if max_data_len == 0 {\n        warn!(\"max_data_len = 0, skipping remaining charts\");\n        return;\n    }\n\n    for (id, stub) in http_requests {\n        if params.bidi_only && (id & 0x2) != 0 {\n            continue;\n        }\n\n        let data_frames = match ds.vantage_point {\n            VantagePoint::Client => &stub.time_data_rx_set,\n            VantagePoint::Server => &stub.time_data_tx_set,\n        };\n\n        let headers_init = match ds.vantage_point {\n            VantagePoint::Client => stub.time_first_headers_tx,\n            VantagePoint::Server => stub.time_first_headers_rx,\n        };\n\n        let headers_actioned = match ds.vantage_point {\n            VantagePoint::Client => stub.time_first_headers_rx,\n            VantagePoint::Server => stub.time_first_headers_tx,\n        };\n\n        let data_frames_temp_root = root.clone().shrink(\n            (0, upper_y + data_frames_mini_offset_y),\n            (data_frames_mini_x, data_frames_mini_y),\n        );\n\n        let stream_frames_temp_root = root.clone().shrink(\n            (0, upper_y + stream_frames_mini_offset_y),\n            (stream_frames_mini_x, stream_frames_mini_y),\n        );\n\n        upper_y += 20;\n\n        let mut data_frames_mini_independent_chart =\n            ChartBuilder::on(&data_frames_temp_root)\n                .x_label_area_size(params.area_margin.x)\n                .y_label_area_size(params.area_margin.y)\n                .build_cartesian_2d(x_min..x_max, mini_chart_axis_y_range.clone())\n                .unwrap();\n\n        data_frames_mini_independent_chart\n            .configure_mesh()\n            .disable_y_mesh()\n            .disable_y_axis()\n            .axis_style(params.colors.axis)\n            .draw()\n            .unwrap();\n\n        // let's jump ahead here and plot the mini chart header events, to avoid\n        // having to loop again later\n        let mut stream_frames_mini_independent_chart =\n            ChartBuilder::on(&stream_frames_temp_root)\n                .x_label_area_size(params.area_margin.x)\n                .y_label_area_size(params.area_margin.y)\n                .build_cartesian_2d(x_min..x_max, mini_chart_axis_y_range.clone())\n                .unwrap();\n\n        stream_frames_mini_independent_chart\n            .configure_mesh()\n            .disable_y_mesh()\n            .disable_y_axis()\n            .axis_style(params.colors.axis)\n            .draw()\n            .unwrap();\n\n        let style = color_cyle.next_color();\n\n        if let Some(h) = headers_init {\n            data_frames_mini_independent_chart\n                .draw_series([TriangleMarker::new(\n                    (h, marker_y),\n                    marker_size,\n                    style,\n                )])\n                .unwrap();\n\n            stream_frames_mini_independent_chart\n                .draw_series([TriangleMarker::new(\n                    (h, marker_y),\n                    marker_size,\n                    style,\n                )])\n                .unwrap();\n        }\n\n        if let Some(h) = headers_actioned {\n            data_frames_mini_independent_chart\n                .draw_series([TriangleMarker::new(\n                    (h, marker_y),\n                    marker_size,\n                    style,\n                )])\n                .unwrap();\n\n            stream_frames_mini_independent_chart\n                .draw_series([TriangleMarker::new(\n                    (h, marker_y),\n                    marker_size,\n                    style,\n                )])\n                .unwrap();\n        }\n\n        let lines = data_frames\n            .iter()\n            .map(|point| PathElement::new([(point.0, 2), (point.0, 0)], style));\n\n        data_frames_mini_independent_chart\n            .draw_series(lines.clone())\n            .unwrap();\n\n        if let Some(stream_frames) = stream_frame_series.get(id) {\n            let lines = stream_frames.iter().map(|point| {\n                PathElement::new([(point.0, 2), (point.0, 0)], style)\n            });\n\n            stream_frames_mini_independent_chart\n                .draw_series(lines.clone())\n                .unwrap();\n        }\n\n        let bubbles_temp_root = root.clone().shrink(\n            (0, bubbles_upper_y + bubbles_offset_y),\n            (bubbles_x, bubbles_y),\n        );\n\n        bubbles_upper_y += 70;\n\n        let mut bubbles_mini_chart = ChartBuilder::on(&bubbles_temp_root)\n            .x_label_area_size(params.area_margin.x)\n            .y_label_area_size(params.area_margin.y)\n            .build_cartesian_2d(\n                x_min..x_max,\n                0..max_data_len, /*0..30*/ /*mini_chart_axis_y_range.clone()*/\n            )\n            .unwrap();\n\n        bubbles_mini_chart\n            .configure_mesh()\n            .disable_y_mesh()\n            .disable_y_axis()\n            .axis_style(params.colors.axis)\n            .draw()\n            .unwrap();\n\n        // Integer conversion will floor, so when hardly any DATA is transferred\n        // avoid 0, which would cause a divide-by-0 later.\n        let bucket_size = if max_data_len >= 5 {\n            max_data_len / 5\n        } else {\n            1\n        };\n\n        let mut bubbles: Vec<Circle<(f64, i32), i32>> = vec![];\n        let mut lines: Vec<PathElement<(f64, i32)>> = vec![];\n        let mut lines2: Vec<PathElement<(f64, u64)>> = vec![];\n\n        let bubble_sizes = [1, 6, 11, 16, 21, 26, 31];\n\n        for (time, length) in data_frames {\n            // TODO: replace with div_ceil once stablized- https://github.com/rust-lang/rust/issues/88581\n            let i: usize = (length / bucket_size) as usize +\n                usize::from(length % bucket_size != 0);\n            let i = i.saturating_sub(1);\n\n            // TODO: there seems to be a bug if the bubble is greater th x_max it\n            // would still apear. So let's just avoid that.\n            if time > &x_max {\n                continue;\n            }\n\n            lines.push(PathElement::new(\n                [(*time, 2), (*time, 2 + bubble_sizes[i])],\n                style,\n            ));\n\n            lines2.push(PathElement::new([(*time, 0), (*time, *length)], style));\n\n            bubbles.push(Circle::new((*time, 2), bubble_sizes[i], style));\n        }\n\n        // bubbles_mini_chart.draw_series(bubbles).unwrap();\n        bubbles_mini_chart.draw_series(lines2).unwrap();\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/plots/stream_sparks.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Spark charts\n//! These are a single image file containing a grid of mini plots. Each plot\n//! represents a single stream, so it is easier to see how they all compare\n//! at a glance.\n\n// TODO: this seems to be required to overcome a transient error in nightly; see\n// https://github.com/rust-lang/rust/issues/147648#issuecomment-3482917926\n#![allow(unused_assignments)]\n\nuse full_palette::PURPLE_500;\nuse plotters::coord::types::RangedCoordf64;\nuse plotters::coord::types::RangedCoordu64;\nuse plotters::prelude::*;\n\nuse tabled::Tabled;\n\nuse crate::datastore::ApplicationProto;\nuse crate::datastore::VantagePoint;\nuse crate::plots::colors::*;\nuse crate::plots::*;\nuse crate::request_stub::HttpRequestStub;\n\nuse crate::datastore::Datastore;\nuse crate::seriesstore::SeriesStore;\n\n#[allow(unused_assignments)]\npub enum TransmissionType {\n    Upload,\n    Download,\n}\n\n#[derive(Debug)]\npub struct SparkPlotsParams {\n    pub clamp: ClampParams,\n    pub bidi_only: bool,\n    pub label_area_width: u32,\n    pub label_area_height: u32,\n    pub caption_area_height: u32,\n    pub caption_area_width: u32,\n    pub spark_offset_x: u32,\n    pub spark_offset_y: u32,\n    pub spark_dimension_x: u32,\n    pub spark_dimension_y: u32,\n    pub sparks_per_row: u32,\n    pub captions_on_top: bool,\n    pub colors: PlotColors,\n}\n\nimpl Default for SparkPlotsParams {\n    fn default() -> Self {\n        Self {\n            clamp: Default::default(),\n            bidi_only: true,\n            label_area_width: 20,\n            label_area_height: 20,\n            caption_area_height: 100,\n            caption_area_width: 0,\n            spark_offset_x: 50,\n            spark_offset_y: 30,\n            spark_dimension_x: 150,\n            spark_dimension_y: 150,\n            sparks_per_row: 10,\n            captions_on_top: true,\n            colors: Default::default(),\n        }\n    }\n}\n\n#[derive(Tabled)]\nenum SparkCaption {\n    UniStream {\n        summary: String,\n    },\n\n    RequestAtServer {\n        summary: String,\n        method: String,\n        path: String,\n        client_content_length: String,\n        server_content_length: String,\n        client_pri_hdr: String,\n        server_pri_hdr: String,\n        duration_rx_hdr_tx_hdr: String,\n        duration_rx_hdr_tx_first_data: String,\n        duration_rx_hdr_tx_last_data: String,\n        duration_tx_first_data_tx_last_data: String,\n    },\n\n    RequestAtClient {\n        summary: String,\n        method: String,\n        path: String,\n        client_content_length: String,\n        server_content_length: String,\n        client_pri_hdr: String,\n        client_pri_update: String,\n        server_pri_hdr: String,\n        duration_tx_hdr_rx_hdr: String,\n        duration_tx_hdr_rx_first_data: String,\n        duration_tx_hdr_rx_last_data: String,\n        duration_tx_first_data_tx_last_data: String,\n    },\n}\n\nimpl SparkCaption {\n    fn request_at_client_from_stub(\n        application_proto: ApplicationProto, req: &HttpRequestStub,\n    ) -> Self {\n        let summary = format!(\n            \"ID= {}, status= {}, proto= {:?}\",\n            req.stream_id, req.status, application_proto\n        );\n        let empty = &\"\".to_string();\n\n        let duration_tx_hdr_rx_hdr =\n            match req.at_client_deltas.as_ref().unwrap().tx_hdr_rx_hdr.inner {\n                Some(t) => format!(\"d_tx_hdr_rx_hdr:               {:.2}ms\", t),\n\n                None => empty.to_owned(),\n            };\n\n        let duration_tx_hdr_rx_first_data = match req\n            .at_client_deltas\n            .as_ref()\n            .unwrap()\n            .tx_hdr_rx_first_data\n            .inner\n        {\n            Some(t) => format!(\"d_tx_hdr_rx_data_first:        {:.2}ms\", t),\n\n            None => empty.to_owned(),\n        };\n\n        let duration_tx_hdr_rx_last_data = match req\n            .at_client_deltas\n            .as_ref()\n            .unwrap()\n            .tx_hdr_rx_last_data\n            .inner\n        {\n            Some(t) => format!(\"d_tx_hdr_rx_data_last:         {:.2}ms\", t),\n\n            None => empty.to_owned(),\n        };\n\n        let duration_tx_first_data_tx_last_data = match req\n            .at_client_deltas\n            .as_ref()\n            .unwrap()\n            .tx_first_data_tx_last_data\n            .inner\n        {\n            Some(t) => format!(\"d_tx_data_first_tx_data_last:  {:.2}ms\", t),\n\n            None => empty.to_owned(),\n        };\n\n        SparkCaption::RequestAtClient {\n            summary,\n            method: format!(\"method= {}\", req.method),\n            path: format!(\"path= {}\", req.path),\n\n            client_content_length: format!(\n                \"client content length: {}\",\n                req.client_content_length\n            ),\n            server_content_length: format!(\n                \"server content length: {}\",\n                req.server_content_length\n            ),\n\n            client_pri_hdr: format!(\n                \"client pri hdr:         {}\",\n                req.client_pri_hdr\n            ),\n\n            client_pri_update: format!(\n                \"client pri updates:     {:?}\",\n                req.priority_updates\n            ),\n\n            server_pri_hdr: format!(\n                \"server pri hdr:         {}\",\n                req.server_pri_hdr\n            ),\n\n            duration_tx_hdr_rx_hdr,\n\n            duration_tx_hdr_rx_first_data,\n\n            duration_tx_hdr_rx_last_data,\n\n            duration_tx_first_data_tx_last_data,\n        }\n    }\n\n    fn request_at_server_from_stub(\n        stream_id: u64, req: &HttpRequestStub,\n    ) -> Self {\n        let summary = format!(\"ID= {}, status= {}\", stream_id, req.status);\n        let empty = &\"\".to_string();\n\n        let duration_rx_hdr_tx_hdr =\n            match req.at_server_deltas.as_ref().unwrap().rx_hdr_tx_hdr.inner {\n                Some(t) => format!(\"d_rx_hdr_tx_hdr:               {:.2}ms\", t),\n\n                None => empty.to_owned(),\n            };\n\n        let duration_rx_hdr_tx_first_data = match req\n            .at_server_deltas\n            .as_ref()\n            .unwrap()\n            .rx_hdr_tx_first_data\n            .inner\n        {\n            Some(t) => format!(\"d_rx_hdr_tx_data_first:        {:.2}ms\", t),\n\n            None => empty.to_owned(),\n        };\n\n        let duration_rx_hdr_tx_last_data = match req\n            .at_server_deltas\n            .as_ref()\n            .unwrap()\n            .rx_hdr_tx_last_data\n            .inner\n        {\n            Some(t) => format!(\"d_rx_hdr_tx_data_last:         {:.2}ms\", t),\n\n            None => empty.to_owned(),\n        };\n\n        let duration_tx_first_data_tx_last_data = match req\n            .at_server_deltas\n            .as_ref()\n            .unwrap()\n            .tx_first_data_tx_last_data\n            .inner\n        {\n            Some(t) => format!(\"d_tx_data_first_tx_data_last:  {:.2}ms\", t),\n\n            None => empty.to_owned(),\n        };\n\n        SparkCaption::RequestAtServer {\n            summary,\n            method: format!(\"method= {}\", req.method),\n            path: format!(\"path= {}\", req.path),\n\n            client_content_length: format!(\n                \"client content length: {}\",\n                req.client_content_length\n            ),\n            server_content_length: format!(\n                \"server content length: {}\",\n                req.server_content_length\n            ),\n\n            client_pri_hdr: format!(\n                \"client pri hdr:         {}\",\n                req.client_pri_hdr\n            ),\n\n            server_pri_hdr: format!(\n                \"server pri hdr:         {}\",\n                req.server_pri_hdr\n            ),\n\n            duration_rx_hdr_tx_hdr,\n\n            duration_rx_hdr_tx_first_data,\n\n            duration_rx_hdr_tx_last_data,\n\n            duration_tx_first_data_tx_last_data,\n        }\n    }\n\n    fn from_data_store(ds: &Datastore, stream_id: u64) -> Option<Self> {\n        let is_request = match ds.application_proto {\n            ApplicationProto::Http3 => stream_id.is_multiple_of(4),\n            ApplicationProto::Http2 => true,\n        };\n\n        if is_request {\n            if let Some(req) = ds.http_requests.get(&stream_id) {\n                match ds.vantage_point {\n                    VantagePoint::Client =>\n                        Some(SparkCaption::request_at_client_from_stub(\n                            ds.application_proto,\n                            req,\n                        )),\n                    VantagePoint::Server => Some(\n                        SparkCaption::request_at_server_from_stub(stream_id, req),\n                    ),\n                }\n            } else {\n                None\n            }\n        } else {\n            let summary = format!(\"ID= {}\", stream_id);\n            Some(SparkCaption::UniStream { summary })\n        }\n    }\n\n    fn draw<DB: DrawingBackend>(\n        &self, color: &RGBColor, root: &DrawingArea<DB, plotters::coord::Shift>,\n        x: i32, y: i32,\n    ) {\n        let style = &(\"monospace\", 12).into_text_style(root).color(color);\n\n        let newline_offset = 10;\n        let x = x + 20;\n\n        match self {\n            SparkCaption::UniStream { summary } => {\n                root.draw_text(summary, style, (x, y)).unwrap();\n            },\n\n            SparkCaption::RequestAtServer {\n                summary,\n                method,\n                path,\n                client_content_length,\n                server_content_length,\n                client_pri_hdr,\n                server_pri_hdr,\n                duration_rx_hdr_tx_hdr,\n                duration_rx_hdr_tx_first_data,\n                duration_rx_hdr_tx_last_data,\n                duration_tx_first_data_tx_last_data,\n                ..\n            } => {\n                root.draw_text(summary, style, (x, y)).unwrap();\n\n                root.draw_text(method, style, (x, y + newline_offset))\n                    .unwrap();\n\n                root.draw_text(path, style, (x, y + newline_offset * 2))\n                    .unwrap();\n\n                root.draw_text(\n                    client_content_length,\n                    style,\n                    (x, y + newline_offset * 3),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    server_content_length,\n                    style,\n                    (x, y + newline_offset * 4),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    client_pri_hdr,\n                    style,\n                    (x, y + newline_offset * 6),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    server_pri_hdr,\n                    style,\n                    (x, y + newline_offset * 7),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    duration_rx_hdr_tx_hdr,\n                    style,\n                    (x, y + newline_offset * 8),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    duration_rx_hdr_tx_first_data,\n                    style,\n                    (x, y + newline_offset * 9),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    duration_rx_hdr_tx_last_data,\n                    style,\n                    (x, y + newline_offset * 10),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    duration_tx_first_data_tx_last_data,\n                    style,\n                    (x, y + newline_offset * 11),\n                )\n                .unwrap();\n            },\n\n            SparkCaption::RequestAtClient {\n                summary,\n                method,\n                path,\n                client_content_length,\n                server_content_length,\n                client_pri_hdr,\n                client_pri_update,\n                server_pri_hdr,\n                duration_tx_hdr_rx_hdr,\n                duration_tx_hdr_rx_first_data,\n                duration_tx_hdr_rx_last_data,\n                duration_tx_first_data_tx_last_data,\n                ..\n            } => {\n                root.draw_text(summary, style, (x, y)).unwrap();\n\n                root.draw_text(method, style, (x, y + newline_offset))\n                    .unwrap();\n                root.draw_text(path, style, (x, y + newline_offset * 2))\n                    .unwrap();\n\n                root.draw_text(\n                    client_content_length,\n                    style,\n                    (x, y + newline_offset * 3),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    server_content_length,\n                    style,\n                    (x, y + newline_offset * 4),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    client_pri_hdr,\n                    style,\n                    (x, y + newline_offset * 6),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    client_pri_update,\n                    style,\n                    (x, y + newline_offset * 7),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    server_pri_hdr,\n                    style,\n                    (x, y + newline_offset * 8),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    duration_tx_hdr_rx_hdr,\n                    style,\n                    (x, y + newline_offset * 9),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    duration_tx_hdr_rx_first_data,\n                    style,\n                    (x, y + newline_offset * 10),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    duration_tx_hdr_rx_last_data,\n                    style,\n                    (x, y + newline_offset * 11),\n                )\n                .unwrap();\n\n                root.draw_text(\n                    duration_tx_first_data_tx_last_data,\n                    style,\n                    (x, y + newline_offset * 12),\n                )\n                .unwrap();\n            },\n        }\n    }\n}\n\nfn plot_legend<DB: DrawingBackend>(\n    color: &RGBColor, root: &DrawingArea<DB, plotters::coord::Shift>, x: i32,\n    y: i32, params: &SparkPlotsParams, transmission_type: TransmissionType,\n) {\n    // TODO: only considers vert layout, needs to consider grid layout too\n    let style = &(\"monospace\", 12).into_text_style(root).color(color);\n    let x: i32 = x + params.spark_dimension_x as i32 + 300;\n    let y: i32 = y;\n    let line_len: i32 = 50;\n    let nl_off: i32 = 20;\n\n    root.draw_text(\"Legend\", style, (x, y)).unwrap();\n\n    // stream data\n    let label = match transmission_type {\n        TransmissionType::Download => \"Stream data read\",\n        TransmissionType::Upload => \"Stream data sent\",\n    };\n    root.draw(&PathElement::new(\n        vec![(x, y + nl_off), (x + line_len, y + nl_off)],\n        PURPLE_500,\n    ))\n    .unwrap();\n    root.draw_text(label, style, (x + line_len + 10, y + (nl_off) - 5))\n        .unwrap();\n\n    // stream data buffered\n    root.draw(&PathElement::new(\n        vec![(x, y + nl_off * 2), (x + line_len, y + nl_off * 2)],\n        MAGENTA,\n    ))\n    .unwrap();\n    root.draw_text(\n        \"Stream data buffered\",\n        style,\n        (x + line_len + 10, y + (nl_off * 2) - 5),\n    )\n    .unwrap();\n\n    // max stream data\n    let label = match transmission_type {\n        TransmissionType::Download => \"Max stream data sent\",\n        TransmissionType::Upload => \"Max stream data received\",\n    };\n\n    root.draw(&PathElement::new(\n        vec![(x, y + nl_off * 3), (x + line_len, y + nl_off * 3)],\n        MUSTARD,\n    ))\n    .unwrap();\n    root.draw_text(label, style, (x + line_len + 10, y + (nl_off * 3) - 5))\n        .unwrap();\n\n    // headers transmitted\n    root.draw(&PathElement::new(\n        vec![(x, y + nl_off * 4), (x + line_len, y + nl_off * 4)],\n        CYAN,\n    ))\n    .unwrap();\n    root.draw_text(\n        \"Request headers sent\",\n        style,\n        (x + line_len + 10, y + (nl_off * 4) - 5),\n    )\n    .unwrap();\n\n    // first data frame\n    root.draw(&PathElement::new(\n        vec![(x, y + nl_off * 5), (x + line_len, y + nl_off * 5)],\n        ORANGE,\n    ))\n    .unwrap();\n    root.draw_text(\n        \"First data frame read\",\n        style,\n        (x + line_len + 10, y + (nl_off * 5) - 5),\n    )\n    .unwrap();\n\n    // last data frame\n    root.draw(&PathElement::new(\n        vec![(x, y + nl_off * 6), (x + line_len, y + nl_off * 6)],\n        BROWN,\n    ))\n    .unwrap();\n    root.draw_text(\n        \"Last data frame read\",\n        style,\n        (x + line_len + 10, y + (nl_off * 6) - 5),\n    )\n    .unwrap();\n}\n\n#[allow(clippy::too_many_arguments)]\npub fn plot_sparks(\n    params: &SparkPlotsParams, filename: &str, ss: &SeriesStore, ds: &Datastore,\n    abs_dl_ty: &ChartOutputType, rel_dl_ty: &ChartOutputType,\n    abs_ul_ty: &ChartOutputType, rel_ul_ty: &ChartOutputType,\n) {\n    let (stream_frame_dl_series_to_plot, stream_frame_ul_series_to_plot) =\n        match (ds.vantage_point, ds.application_proto) {\n            (VantagePoint::Client, ApplicationProto::Http3) => (\n                &ss.received_stream_frames_series,\n                &ss.sent_stream_frames_series,\n            ),\n            (VantagePoint::Server, ApplicationProto::Http3) => (\n                &ss.sent_stream_frames_series,\n                &ss.received_stream_frames_series,\n            ),\n            (VantagePoint::Client, ApplicationProto::Http2) =>\n                (&ss.received_data_frames_series, &ss.sent_data_frames_series),\n            _ => unimplemented!(),\n        };\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let (total_dl_sparks, total_ul_sparks) =\n        if params.bidi_only && ds.application_proto == ApplicationProto::Http3 {\n            let total_dl = stream_frame_dl_series_to_plot\n                .iter()\n                .filter(|(k, _)| (*k & 0x2) == 0)\n                .count() as u32;\n\n            let total_ul = stream_frame_ul_series_to_plot\n                .iter()\n                .filter(|(k, _)| (*k & 0x2) == 0)\n                .count() as u32;\n\n            (total_dl, total_ul)\n        } else {\n            (\n                stream_frame_dl_series_to_plot.len() as u32,\n                stream_frame_ul_series_to_plot.len() as u32,\n            )\n        };\n\n    // TODO: replace with div_ceil once stablized- https://github.com/rust-lang/rust/issues/88581\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let total_dl_rows: u32 = total_dl_sparks / params.sparks_per_row +\n        u32::from(total_dl_sparks % params.sparks_per_row != 0);\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let total_ul_rows: u32 = total_ul_sparks / params.sparks_per_row +\n        u32::from(total_ul_sparks % params.sparks_per_row != 0);\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let caption_area_width = if params.captions_on_top {\n        0\n    } else {\n        params.caption_area_width\n    };\n\n    let caption_area_height = if params.captions_on_top {\n        params.caption_area_height\n    } else {\n        0\n    };\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let spark_and_captions_width = params.spark_dimension_x +\n        (params.spark_offset_x as f32 * 1.2) as u32 +\n        caption_area_width;\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let spark_and_captions_height = params.spark_dimension_y +\n        params.spark_offset_y +\n        (params.label_area_height) +\n        caption_area_height;\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let root_width: u32 = params.sparks_per_row * spark_and_captions_width;\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let dl_root_height: u32 = total_dl_rows * spark_and_captions_height;\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let ul_root_height: u32 = total_ul_rows * spark_and_captions_height;\n\n    let mut x_min = match ds.vantage_point {\n        VantagePoint::Client => ss.received_x_min,\n        VantagePoint::Server => ss.sent_x_min,\n    };\n\n    let mut x_max = match ds.vantage_point {\n        VantagePoint::Client => ss.received_x_max,\n        VantagePoint::Server => ss.sent_x_max,\n    };\n\n    // Clamp plot x-axis if the user told us.\n    if let Some(s) = params.clamp.start {\n        x_min = s;\n    }\n\n    if let Some(e) = params.clamp.end {\n        x_max = x_max.min(e);\n    }\n\n    let abs_dl_chart_config = ChartConfig {\n        title: \"stream-spark-dl-absolute\".into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: abs_dl_ty.clone(),\n    };\n    abs_dl_chart_config.init_chart_dir();\n\n    let rel_dl_chart_config = ChartConfig {\n        title: \"stream-spark-dl-relative\".into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: rel_dl_ty.clone(),\n    };\n    rel_dl_chart_config.init_chart_dir();\n\n    let abs_ul_chart_config = ChartConfig {\n        title: \"stream-spark-ul-absolute\".into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: abs_ul_ty.clone(),\n    };\n    abs_ul_chart_config.init_chart_dir();\n\n    let rel_ul_chart_config = ChartConfig {\n        title: \"stream-spark-ul-relative\".into(),\n        input_filename: filename.into(),\n        clamp: params.clamp.clone(),\n        app_proto: ds.application_proto,\n        host: ds.host.clone(),\n        session_id: ds.session_id,\n        ty: rel_ul_ty.clone(),\n    };\n    rel_ul_chart_config.init_chart_dir();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let dl_size = ChartSize {\n        width: root_width,\n        height: dl_root_height,\n    };\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let ul_size = ChartSize {\n        width: root_width,\n        height: ul_root_height,\n    };\n    let margin = ChartMargin::default();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let abs_dl_chart_path = abs_dl_chart_config.chart_filepath();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let rel_dl_chart_path = rel_dl_chart_config.chart_filepath();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let abs_ul_chart_path = abs_ul_chart_config.chart_filepath();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let rel_ul_chart_path = rel_ul_chart_config.chart_filepath();\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let abs_dl_root = make_chart_bitmap_area(\n        &abs_dl_chart_path,\n        dl_size,\n        params.colors,\n        margin,\n    );\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let rel_dl_root = make_chart_bitmap_area(\n        &rel_dl_chart_path,\n        dl_size,\n        params.colors,\n        margin,\n    );\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let abs_ul_root = make_chart_bitmap_area(\n        &abs_ul_chart_path,\n        ul_size,\n        params.colors,\n        margin,\n    );\n\n    #[cfg(not(target_arch = \"wasm32\"))]\n    let rel_ul_root = make_chart_bitmap_area(\n        &rel_ul_chart_path,\n        ul_size,\n        params.colors,\n        margin,\n    );\n\n    #[cfg(target_arch = \"wasm32\")]\n    let abs_dl_canvas_id = abs_dl_chart_config.canvas_id().unwrap_or_default();\n\n    #[cfg(target_arch = \"wasm32\")]\n    let rel_dl_canvas_id = rel_dl_chart_config.canvas_id().unwrap_or_default();\n\n    #[cfg(target_arch = \"wasm32\")]\n    let abs_ul_canvas_id = abs_ul_chart_config.canvas_id().unwrap_or_default();\n\n    #[cfg(target_arch = \"wasm32\")]\n    let rel_ul_canvas_id = rel_ul_chart_config.canvas_id().unwrap_or_default();\n\n    #[cfg(target_arch = \"wasm32\")]\n    let abs_dl_root =\n        make_chart_canvas_area(&abs_dl_canvas_id, params.colors, margin);\n    #[cfg(target_arch = \"wasm32\")]\n    let rel_dl_root =\n        make_chart_canvas_area(&rel_dl_canvas_id, params.colors, margin);\n    #[cfg(target_arch = \"wasm32\")]\n    let abs_ul_root =\n        make_chart_canvas_area(&abs_ul_canvas_id, params.colors, margin);\n    #[cfg(target_arch = \"wasm32\")]\n    let rel_ul_root =\n        make_chart_canvas_area(&rel_ul_canvas_id, params.colors, margin);\n\n    let mut upper_x = params.spark_offset_x;\n    let mut dl_upper_y = params.spark_offset_y;\n    let mut ul_upper_y = params.spark_offset_y;\n\n    plot_legend(\n        &params.colors.caption,\n        &abs_dl_root,\n        upper_x as i32,\n        dl_upper_y as i32,\n        params,\n        TransmissionType::Download,\n    );\n    plot_legend(\n        &params.colors.caption,\n        &rel_dl_root,\n        upper_x as i32,\n        dl_upper_y as i32,\n        params,\n        TransmissionType::Download,\n    );\n    plot_legend(\n        &params.colors.caption,\n        &abs_ul_root,\n        upper_x as i32,\n        ul_upper_y as i32,\n        params,\n        TransmissionType::Upload,\n    );\n    plot_legend(\n        &params.colors.caption,\n        &rel_ul_root,\n        upper_x as i32,\n        ul_upper_y as i32,\n        params,\n        TransmissionType::Upload,\n    );\n\n    let mut dl_layout_count = 0;\n    let mut ul_layout_count = 0;\n\n    for (stream_id, dl_stream_frames) in stream_frame_dl_series_to_plot {\n        if ds.application_proto == ApplicationProto::Http3 &&\n            params.bidi_only &&\n            (stream_id & 0x2) != 0\n        {\n            continue;\n        }\n\n        let abs_dl_small_area = abs_dl_root.clone().shrink(\n            (upper_x, dl_upper_y + caption_area_height),\n            (params.spark_dimension_x, params.spark_dimension_y),\n        );\n\n        let rel_dl_small_area = rel_dl_root.clone().shrink(\n            (upper_x, dl_upper_y + caption_area_height),\n            (params.spark_dimension_x, params.spark_dimension_y),\n        );\n\n        let abs_ul_small_area = abs_ul_root.clone().shrink(\n            (upper_x, ul_upper_y + caption_area_height),\n            (params.spark_dimension_x, params.spark_dimension_y),\n        );\n\n        let rel_ul_small_area = rel_ul_root.clone().shrink(\n            (upper_x, ul_upper_y + caption_area_height),\n            (params.spark_dimension_x, params.spark_dimension_y),\n        );\n\n        let dl_zoom_x_min = dl_stream_frames.first().unwrap().0;\n        let dl_zoom_x_max = dl_stream_frames.last().unwrap().0;\n\n        let dl_zoom_y_max = if let Some(y_max) = params.clamp.stream_y_max {\n            y_max\n        } else {\n            dl_stream_frames.last().unwrap().1\n        };\n\n        let mut abs_dl_chart = ChartBuilder::on(&abs_dl_small_area)\n            .set_label_area_size(LabelAreaPosition::Left, params.label_area_width)\n            .set_label_area_size(\n                LabelAreaPosition::Bottom,\n                params.label_area_height,\n            )\n            .build_cartesian_2d(x_min..x_max, 0..dl_zoom_y_max)\n            .unwrap();\n        draw_mesh(&params.colors, &mut abs_dl_chart);\n\n        let mut rel_dl_chart = ChartBuilder::on(&rel_dl_small_area)\n            .set_label_area_size(LabelAreaPosition::Left, params.label_area_width)\n            .set_label_area_size(\n                LabelAreaPosition::Bottom,\n                params.label_area_height,\n            )\n            .build_cartesian_2d(dl_zoom_x_min..dl_zoom_x_max, 0..dl_zoom_y_max)\n            .unwrap();\n        draw_mesh(&params.colors, &mut rel_dl_chart);\n\n        dl_layout_count += 1;\n\n        let ul_stream_frames = stream_frame_ul_series_to_plot.get(stream_id);\n\n        // TODO upload window\n        let ul_h2_send_window =\n            ss.h2_send_window_series_balanced.get(&(*stream_id as u32));\n        let ul_h2_send_window_max =\n            ss.h2_send_window_balanced_max.get(&(*stream_id as u32));\n\n        let (abs_ul_chart, rel_ul_chart) = match ul_stream_frames {\n            Some(frames) => {\n                let ul_zoom_x_min = frames.first().unwrap().0;\n                let ul_zoom_x_max = frames.last().unwrap().0;\n\n                // let ul_zoom_x_min_send_window =\n                //     ul_send_window.unwrap().first().unwrap().0;\n                // let ul_zoom_x_max_send_window =\n                //     ul_send_window.unwrap().last().unwrap().0;\n\n                let ul_zoom_y_max = if let Some(y_max) = params.clamp.stream_y_max\n                {\n                    y_max\n                } else {\n                    frames.last().unwrap().1\n                };\n\n                // TODO: this is a hack because the underlying chart types make\n                // it hard to support optional secondary axis. And that axis\n                // only makes sense for HTTP/2.\n                let ul_h2_send_window_y_max = ul_h2_send_window_max.unwrap_or(&0);\n\n                let mut abs_ul_chart = ChartBuilder::on(&abs_ul_small_area)\n                    .set_label_area_size(\n                        LabelAreaPosition::Left,\n                        params.label_area_width,\n                    )\n                    .set_label_area_size(\n                        LabelAreaPosition::Bottom,\n                        params.label_area_height,\n                    )\n                    .build_cartesian_2d(x_min..x_max, 0..ul_zoom_y_max)\n                    .unwrap()\n                    .set_secondary_coord(\n                        x_min..x_max,\n                        0i32..*ul_h2_send_window_y_max,\n                    );\n\n                draw_mesh(&params.colors, &mut abs_ul_chart);\n\n                if ul_h2_send_window_max.is_some() {\n                    abs_ul_chart.configure_secondary_axes().draw().unwrap();\n                }\n\n                let mut rel_ul_chart = ChartBuilder::on(&rel_ul_small_area)\n                    .set_label_area_size(\n                        LabelAreaPosition::Left,\n                        params.label_area_width,\n                    )\n                    .set_label_area_size(\n                        LabelAreaPosition::Bottom,\n                        params.label_area_height,\n                    )\n                    .right_y_label_area_size(params.label_area_width)\n                    .build_cartesian_2d(\n                        ul_zoom_x_min..ul_zoom_x_max,\n                        0..ul_zoom_y_max,\n                    )\n                    .unwrap()\n                    .set_secondary_coord(\n                        ul_zoom_x_min..ul_zoom_x_max,\n                        0i32..*ul_h2_send_window_y_max,\n                    );\n                draw_mesh(&params.colors, &mut rel_ul_chart);\n\n                if ul_h2_send_window_max.is_some() {\n                    rel_ul_chart\n                        .configure_secondary_axes()\n                        .y_labels(5)\n                        .draw()\n                        .unwrap();\n                }\n\n                ul_layout_count += 1;\n\n                (Some(abs_ul_chart), Some(rel_ul_chart))\n            },\n\n            None => (None, None),\n        };\n\n        draw_captions(\n            &abs_dl_root,\n            &rel_dl_root,\n            &abs_ul_root,\n            &rel_ul_root,\n            params,\n            ds,\n            *stream_id,\n            ul_stream_frames.is_some(),\n            upper_x,\n            dl_upper_y,\n            ul_upper_y,\n        );\n\n        upper_x += params.spark_dimension_x + params.spark_offset_x;\n\n        // new row needed?\n        if dl_layout_count == params.sparks_per_row {\n            upper_x = params.spark_offset_x;\n\n            let y_offset = if params.captions_on_top {\n                params.caption_area_height\n            } else {\n                0\n            };\n\n            dl_upper_y +=\n                params.spark_dimension_y + params.spark_offset_y + y_offset;\n\n            dl_layout_count = 0;\n        }\n\n        if ul_layout_count == params.sparks_per_row {\n            upper_x = params.spark_offset_x;\n\n            let y_offset = if params.captions_on_top {\n                params.caption_area_height\n            } else {\n                0\n            };\n\n            ul_upper_y +=\n                params.spark_dimension_y + params.spark_offset_y + y_offset;\n\n            ul_layout_count = 0;\n        }\n\n        draw_stream_frames_line(\n            dl_stream_frames,\n            &mut abs_dl_chart,\n            &mut rel_dl_chart,\n        );\n\n        if let (Some(mut abs), Some(mut rel), Some(frames)) =\n            (abs_ul_chart, rel_ul_chart, ul_stream_frames)\n        {\n            if let Some(v) = ul_h2_send_window {\n                abs.draw_secondary_series(LineSeries::new(v.to_vec(), ORANGE))\n                    .unwrap();\n\n                rel.draw_secondary_series(LineSeries::new(v.to_vec(), ORANGE))\n                    .unwrap();\n            } else {\n                draw_stream_max_line(\n                    *stream_id,\n                    ss,\n                    &ds.vantage_point,\n                    TransmissionType::Upload,\n                    &mut abs,\n                    &mut rel,\n                );\n            }\n\n            draw_stream_frames_line(frames, &mut abs, &mut rel);\n        }\n\n        draw_buffered_data_line(\n            *stream_id,\n            ss,\n            &ds.vantage_point,\n            &mut abs_dl_chart,\n            &mut rel_dl_chart,\n        );\n\n        draw_stream_max_line(\n            *stream_id,\n            ss,\n            &ds.vantage_point,\n            TransmissionType::Download,\n            &mut abs_dl_chart,\n            &mut rel_dl_chart,\n        );\n\n        let y_max = match ds.vantage_point {\n            VantagePoint::Client => ss.y_max_stream_recv_plot,\n            VantagePoint::Server => ss.y_max_stream_send_plot,\n        };\n\n        draw_request_timing_lines(\n            ds,\n            *stream_id,\n            y_max,\n            &mut abs_dl_chart,\n            &mut rel_dl_chart,\n        );\n    }\n}\n\nfn draw_mesh<XT, YT, X, Y, DB: DrawingBackend>(\n    colors: &PlotColors, chart: &mut ChartContext<DB, Cartesian2d<X, Y>>,\n) where\n    X: Ranged<ValueType = XT> + ValueFormatter<XT>,\n    Y: Ranged<ValueType = YT> + ValueFormatter<YT>,\n{\n    chart\n        .configure_mesh()\n        .disable_mesh()\n        .x_labels(5)\n        .y_labels(5)\n        .axis_style(colors.axis)\n        .label_style(chart_label_style(&colors.caption))\n        .draw()\n        .unwrap();\n}\n\n#[allow(clippy::too_many_arguments)]\nfn draw_captions<DB: DrawingBackend>(\n    abs_dl_root: &DrawingArea<DB, plotters::coord::Shift>,\n    rel_dl_root: &DrawingArea<DB, plotters::coord::Shift>,\n    abs_ul_root: &DrawingArea<DB, plotters::coord::Shift>,\n    rel_ul_root: &DrawingArea<DB, plotters::coord::Shift>,\n    params: &SparkPlotsParams, ds: &Datastore, stream_id: u64, upload_data: bool,\n    upper_x: u32, dl_upper_y: u32, ul_upper_y: u32,\n) {\n    if let Some(captions) = SparkCaption::from_data_store(ds, stream_id) {\n        let x_offset = if params.captions_on_top {\n            0\n        } else if ds.application_proto == ApplicationProto::Http2 {\n            params.spark_dimension_x + 30\n        } else {\n            params.spark_dimension_x\n        };\n\n        captions.draw(\n            &params.colors.caption,\n            abs_dl_root,\n            (upper_x + x_offset) as i32,\n            dl_upper_y as i32,\n        );\n        captions.draw(\n            &params.colors.caption,\n            rel_dl_root,\n            (upper_x + x_offset) as i32,\n            dl_upper_y as i32,\n        );\n\n        if upload_data {\n            captions.draw(\n                &params.colors.caption,\n                abs_ul_root,\n                (upper_x + x_offset) as i32,\n                ul_upper_y as i32,\n            );\n            captions.draw(\n                &params.colors.caption,\n                rel_ul_root,\n                (upper_x + x_offset) as i32,\n                ul_upper_y as i32,\n            );\n        }\n    }\n}\n\nfn draw_stream_frames_line<DB: DrawingBackend>(\n    stream_frames: &[(f64, u64)],\n    abs_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n    rel_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n) {\n    abs_chart\n        .draw_series(LineSeries::new(stream_frames.to_vec(), PURPLE_500))\n        .unwrap();\n\n    rel_chart\n        .draw_series(LineSeries::new(stream_frames.to_vec(), PURPLE_500))\n        .unwrap();\n\n    let circles: Vec<Circle<(f64, u64), i32>> = stream_frames\n        .iter()\n        .map(|point| Circle::new(*point, 2, PURPLE_500))\n        .collect();\n    rel_chart.draw_series(circles).unwrap();\n}\n\nfn draw_buffered_data_line<DB: DrawingBackend>(\n    stream_id: u64, ss: &SeriesStore, vantage_point: &VantagePoint,\n    abs_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n    rel_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n) {\n    let buffered_data_to_plot = match vantage_point {\n        VantagePoint::Client => ss.stream_buffer_reads.get(&stream_id),\n        VantagePoint::Server => ss.stream_buffer_writes.get(&stream_id),\n    };\n\n    if let Some(buffered_data) = buffered_data_to_plot {\n        abs_chart\n            .draw_series(LineSeries::new(buffered_data.clone(), MAGENTA))\n            .unwrap();\n\n        rel_chart\n            .draw_series(LineSeries::new(buffered_data.clone(), MAGENTA))\n            .unwrap();\n\n        rel_chart\n            .draw_series(\n                buffered_data\n                    .iter()\n                    .map(|point| TriangleMarker::new(*point, 3, MAGENTA)),\n            )\n            .unwrap();\n    }\n}\n\nfn draw_stream_max_line<DB: DrawingBackend>(\n    stream_id: u64, ss: &SeriesStore, vantage_point: &VantagePoint,\n    transmission_type: TransmissionType,\n    abs_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n    rel_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n) {\n    let stream_max_data_to_plot = match (vantage_point, transmission_type) {\n        (VantagePoint::Client, TransmissionType::Download) =>\n            ss.sent_stream_max_data.get(&stream_id),\n        (VantagePoint::Client, TransmissionType::Upload) =>\n            ss.received_stream_max_data.get(&stream_id),\n        (VantagePoint::Server, TransmissionType::Download) =>\n            ss.received_stream_max_data.get(&stream_id),\n        (VantagePoint::Server, TransmissionType::Upload) =>\n            ss.sent_stream_max_data.get(&stream_id),\n    };\n\n    if let Some(stream_max_data) = stream_max_data_to_plot {\n        abs_chart\n            .draw_series(LineSeries::new(stream_max_data.clone(), MUSTARD))\n            .unwrap();\n\n        rel_chart\n            .draw_series(LineSeries::new(stream_max_data.clone(), MUSTARD))\n            .unwrap();\n    }\n}\n\nfn draw_request_timing_lines<DB: DrawingBackend>(\n    ds: &Datastore, stream_id: u64, y_max: u64,\n    abs_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n    rel_chart: &mut ChartContext<DB, Cartesian2d<RangedCoordf64, RangedCoordu64>>,\n) {\n    if let Some(req) = ds.http_requests.get(&stream_id) {\n        if let Some(h) = req.time_first_headers_rx {\n            let points = vec![(h, 0), (h, y_max)];\n            abs_chart\n                .draw_series(LineSeries::new(points, GREEN))\n                .unwrap();\n\n            // don't care about rx events on the relative spark chart\n        }\n\n        if let Some(h) = req.time_first_headers_tx {\n            let points = vec![(h, 0), (h, y_max)];\n            abs_chart\n                .draw_series(LineSeries::new(points.clone(), CYAN))\n                .unwrap();\n            rel_chart\n                .draw_series(LineSeries::new(points, CYAN))\n                .unwrap();\n        }\n\n        let (first_data_to_plot, last_data_to_plot) = match ds.vantage_point {\n            VantagePoint::Client =>\n                (req.time_first_data_rx, req.time_last_data_rx),\n            VantagePoint::Server =>\n                (req.time_first_data_tx, req.time_last_data_tx),\n        };\n\n        if let Some(t) = first_data_to_plot {\n            let points = vec![(t, 0), (t, y_max)];\n            abs_chart\n                .draw_series(LineSeries::new(points.clone(), ORANGE))\n                .unwrap();\n            rel_chart\n                .draw_series(LineSeries::new(points, ORANGE))\n                .unwrap();\n        }\n\n        if let Some(t) = last_data_to_plot {\n            let points = vec![(t, 0), (t, y_max)];\n            abs_chart\n                .draw_series(LineSeries::new(points.clone(), BROWN))\n                .unwrap();\n            rel_chart\n                .draw_series(LineSeries::new(points, BROWN))\n                .unwrap();\n        }\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/reports/events.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse qlog::events::http3::Http3Frame;\nuse qlog::events::quic::AckedRanges;\nuse qlog::events::quic::QuicFrame;\nuse qlog::events::EventData;\nuse tabled::Table;\nuse tabled::Tabled;\n\nuse crate::category_and_type_from_event;\nuse crate::category_and_type_from_name;\n\nmacro_rules! printy {\n    ($k:expr, $value:expr, $s:expr) => {{\n        $s += &format!(\"{}={}, \", $k, $value);\n    }};\n}\n\nmacro_rules! printyo {\n    ($k:expr, $value:expr, $s:expr) => {{\n        if let Some(v) = $value {\n            $s += &format!(\"{}={}, \", $k, v);\n        }\n    }};\n}\n\nmacro_rules! printy_json {\n    ($k:expr, $value:expr, $s:expr) => {{\n        $s += &format!(\n            \"{}={}, \",\n            $k,\n            &serde_json::to_string(&$value).unwrap().replace(\"\\\"\", \"\")\n        );\n    }};\n}\n\nmacro_rules! printyo_json {\n    ($k:expr, $value:expr, $s:expr) => {{\n        if let Some(v) = $value {\n            $s += &format!(\n                \"{}={}, \",\n                $k,\n                &serde_json::to_string(&v).unwrap().replace(\"\\\"\", \"\")\n            );\n        }\n    }};\n}\n\n#[derive(Debug, Default, Tabled)]\nstruct PrintableEvent {\n    pub time: f64,\n    pub category: String,\n    #[tabled(rename = \"Type\")]\n    pub ty: String,\n    pub details: String,\n}\n\npub fn frames_to_string(frames: &[QuicFrame]) -> String {\n    let mut s = String::new();\n    for f in frames {\n        match f {\n            QuicFrame::Padding { raw, .. } => {\n                s += &format!(\" PADDING {{raw={raw:?}}}\");\n            },\n            QuicFrame::Ping { .. } => {\n                s += \" PING\";\n            },\n            QuicFrame::Ack { acked_ranges, .. } => {\n                s += \" ACK {\";\n                if let Some(ar) = acked_ranges {\n                    match ar {\n                        AckedRanges::Single(items) => {\n                            for a in items {\n                                for b in a {\n                                    s += &format!{\"{b}, \"};\n                                }\n                            }\n                        },\n                        AckedRanges::Double(items) => {\n                            for a in items {\n                                s += &format!{\"{}-{}, \", a.0, a.1};\n                            }\n                        },\n                    }\n                }\n                s += \"}\";\n            },\n            QuicFrame::ResetStream { stream_id, error, error_code, final_size, .. } => {\n                s += &format!(\" RESET_STREAM {{id={stream_id}, error={error:?}, error_code={error_code:?}, final_size={final_size}}}\");\n            },\n            QuicFrame::StopSending { stream_id, error, error_code, ..} => {\n                s += &format!(\" STOP_SENDING {{id={stream_id}, error={error:?}, error_code={error_code:?}}}\");\n            },\n            QuicFrame::Crypto { offset, raw } => {\n                s += &format!(\" CRYPTO {{off={offset}, raw={raw:?}}}\");\n            },\n            QuicFrame::NewToken { token , ..} => {\n               s += \" NEW_TOKEN \";\n               if let Some(ty) = &token.ty {\n                    s += &format!(\"{{ty={ty:?}}}\");\n               }\n            },\n            QuicFrame::Stream { stream_id, offset, fin, raw } => {\n                s += &format!(\" STREAM {{id={stream_id}, off={offset:?}, raw={raw:?}\");\n                if let Some(f) = fin {\n                    s += &format!(\", fin={f}\")\n                }\n                s += \"}\";\n            },\n            QuicFrame::MaxData { maximum, .. } => {\n                s += &format!(\" MAX_DATA {{max={maximum}}}\");\n            },\n            QuicFrame::MaxStreamData { stream_id, maximum, .. } => {\n                s += &format!(\" MAX_STREAM_DATA {{id={stream_id}, max={maximum}}}\");\n            },\n            QuicFrame::MaxStreams { stream_type, maximum, .. } => {\n                s += &format!(\" MAX_STREAMS {{ty={stream_type:?}, max={maximum}}}\");\n            },\n            QuicFrame::DataBlocked { limit, .. } => {\n                s += &format!(\" DATA_BLOCKED {{limit={limit}}}\");\n            },\n            QuicFrame::StreamDataBlocked { stream_id, limit, .. } => {\n                s += &format!(\" STREAM_DATA_BLOCKED {{id={stream_id}, limit={limit}}}\");\n            },\n            QuicFrame::StreamsBlocked { stream_type, limit , ..} => {\n                s += &format!(\" STREAMS_BLOCKED {{ty={stream_type:?}, limit={limit}}}\");\n            },\n            QuicFrame::NewConnectionId { /*sequence_number, retire_prior_to, connection_id_length, connection_id, stateless_reset_token*/ .. } => {\n                s += \" NEW_CONNECTION_ID {{todo='todo'}}\";\n            },\n            QuicFrame::RetireConnectionId { sequence_number , ..} => {\n                s += &format!(\" RETIRE_CONNECION_ID {{sn={sequence_number}}}\");\n            },\n            QuicFrame::PathChallenge { /*data*/ .. } => {\n                s += \" PATH_CHALLENGE {{todo='todo'}}\";\n            },\n            QuicFrame::PathResponse { /*data*/ .. } => {\n                s += \" PATH_RESPONSE {{todo='todo'}}\";\n            },\n            QuicFrame::ConnectionClose { error_space, error_code, reason, .. } => {\n               s += \" CONNECTION_CLOSE {\";\n               if let Some(es) = error_space {\n                    s += &format!(\" ty={es:?},\");\n               }\n               printyo!(\" code\", error_code, s);\n               printyo!(\" reason\", reason, s);\n               s += \"}\";\n            },\n            QuicFrame::HandshakeDone { .. } => {\n                s += \" HANDSHAKE_DONE\";\n            },\n            QuicFrame::Datagram { raw, .. } => {\n               s += &format!(\" DATAGRAM {{raw={raw:?}}}\");\n            },\n            QuicFrame::Unknown { frame_type_bytes, .. } => {\n               s += &format!(\" UNKNOWN {{frame_type_bytes={frame_type_bytes:?}}}\");\n            },\n        }\n    }\n\n    s\n}\n\nfn http_frame_to_string(frame: &Http3Frame) -> String {\n    let mut s = String::new();\n\n    match frame {\n        Http3Frame::Data { raw } =>{\n            s += \" DATA\";\n            if let Some(r) = raw {\n                printyo!(\"len\", r.length, s);\n            }\n        },\n        Http3Frame::Headers { headers } => {\n            s += \" HEADERS {\";\n\n            for header in headers {\n                let name = header.name.as_deref().unwrap_or(\"<binary>\");\n                let value = header.value.as_deref().unwrap_or(\"<binary>\");\n                s += &format!(\"{}: {}, \", name, value);\n            }\n\n            s += \"}\";\n        },\n        Http3Frame::CancelPush { push_id } => {\n            s += &format!(\" CANCEL_PUSH {{id={push_id}}}\");\n        },\n        Http3Frame::Settings { /* settings */ ..} => {\n            s += \" SETTINGS {{todo}}\";\n        }\n        Http3Frame::PushPromise { /*push_id, headers */ ..} => {\n            s += \" PUSH_PROMISE {{todo}}\";\n        }\n        Http3Frame::Goaway { id } => {\n            s += &format!(\" GOAWAY {{id={id}}}\");\n        }\n        Http3Frame::MaxPushId { push_id } => {\n            s += &format!(\" MAX_PUSH_ID {{id={push_id}}}\");\n        }\n        Http3Frame::PriorityUpdate { /*target_stream_type, prioritized_element_id, priority_field_value*/ .. } => {\n            s += \" PRIORITY_UPDATE {{todo}}\";\n        },\n        Http3Frame::Reserved { /*length*/ .. } => {\n            s += \" GREASE {{todo}}\";\n        },\n        Http3Frame::Unknown { frame_type_value, .. } => {\n            s += &format!(\" UNKNOWN {{ty={frame_type_value}}}\");\n        }\n    }\n\n    s\n}\n\npub fn sqlog_event_list(\n    events: &[qlog::reader::Event],\n) -> tabled::builder::Builder {\n    let mut pp = vec![];\n\n    for event in events {\n        match event {\n            qlog::reader::Event::Qlog(ev) => {\n                let (cat, ty) = category_and_type_from_event(ev);\n\n                let mut p = PrintableEvent {\n                    time: ev.time,\n                    ty,\n                    category: cat,\n                    ..Default::default()\n                };\n\n                match &ev.data {\n                    EventData::QuicConnectionStarted(v) => {\n                        printyo!(\"local_ip_v4\", &v.local.ip_v4, p.details);\n                        printyo!(\"local_port_v4\", &v.local.port_v4, p.details);\n                        printyo!(\"local_ip_v6\", &v.local.ip_v6, p.details);\n                        printyo!(\"local_port_v6\", &v.local.port_v6, p.details);\n                        printy!(\n                            \"local_cids\",\n                            format!(\"{:?}\", &v.local.connection_ids),\n                            p.details\n                        );\n                        printyo!(\"remote_ip_v4\", &v.remote.ip_v4, p.details);\n                        printyo!(\"remote_port_v4\", &v.remote.port_v4, p.details);\n                        printyo!(\"remote_ip_v6\", &v.remote.ip_v6, p.details);\n                        printyo!(\"remote_port_v6\", &v.remote.port_v6, p.details);\n                        printy!(\n                            \"remote_cid\",\n                            format!(\"{:?}\", &v.local.connection_ids),\n                            p.details\n                        );\n                    },\n                    EventData::QuicConnectionClosed(v) => {\n                        printyo_json!(\"initiator\", &v.initiator, p.details);\n                        printyo_json!(\n                            \"connection_code\",\n                            &v.connection_error,\n                            p.details\n                        );\n                        printyo_json!(\n                            \"application_error\",\n                            &v.application_error,\n                            p.details\n                        );\n                        printyo!(\"internal_code\", &v.internal_code, p.details);\n                        printyo!(\"reason\", &v.reason, p.details);\n                        printyo_json!(\"trigger\", &v.trigger, p.details);\n                    },\n                    EventData::QuicParametersSet(v) => {\n                        printyo_json!(\"initiator\", &v.initiator, p.details);\n                        printyo!(\n                            \"resumption_allowed\",\n                            &v.resumption_allowed,\n                            p.details\n                        );\n                        printyo!(\n                            \"early_data_enabled\",\n                            &v.early_data_enabled,\n                            p.details\n                        );\n                        printyo!(\"tls_cipher\", &v.tls_cipher, p.details);\n                        printyo!(\n                            \"odcid\",\n                            &v.original_destination_connection_id,\n                            p.details\n                        );\n                        printyo!(\n                            \"initial_scid\",\n                            &v.initial_source_connection_id,\n                            p.details\n                        );\n                        printyo!(\n                            \"retry_scid\",\n                            &v.retry_source_connection_id,\n                            p.details\n                        );\n                        printyo!(\n                            \"stateless_reset_token\",\n                            &v.stateless_reset_token,\n                            p.details\n                        );\n                        printyo!(\n                            \"disable_active_migration\",\n                            &v.disable_active_migration,\n                            p.details\n                        );\n                        printyo!(\n                            \"max_idle_timeout\",\n                            &v.max_idle_timeout,\n                            p.details\n                        );\n                        printyo!(\n                            \"max_udp_payload_size\",\n                            &v.max_udp_payload_size,\n                            p.details\n                        );\n                        printyo!(\"max_ack_delay\", &v.max_ack_delay, p.details);\n                        printyo!(\n                            \"active_connection_id_limit\",\n                            &v.active_connection_id_limit,\n                            p.details\n                        );\n                        printyo!(\n                            \"initial_max_data\",\n                            &v.initial_max_data,\n                            p.details\n                        );\n                        printyo!(\n                            \"initial_max_stream_data_bidi_local\",\n                            &v.initial_max_stream_data_bidi_local,\n                            p.details\n                        );\n                        printyo!(\n                            \"initial_max_stream_data_bidi_remote\",\n                            &v.initial_max_stream_data_bidi_remote,\n                            p.details\n                        );\n                        printyo!(\n                            \"initial_max_stream_data_uni\",\n                            &v.initial_max_stream_data_uni,\n                            p.details\n                        );\n                        printyo!(\n                            \"initial_max_streams_bidi\",\n                            &v.initial_max_streams_bidi,\n                            p.details\n                        );\n                        printyo!(\n                            \"initial_max_streams_uni\",\n                            &v.initial_max_streams_uni,\n                            p.details\n                        );\n                        printyo_json!(\n                            \"preferred_address\",\n                            &v.preferred_address,\n                            p.details\n                        );\n                        // TODO: v.unknown_parameters\n                    },\n                    EventData::QuicPacketSent(v) => {\n                        p.details +=\n                            &serde_json::to_string(&v.header.packet_type)\n                                .unwrap()\n                                .replace(\"\\\"\", \"\");\n                        printyo!(\" pn\", v.header.packet_number, p.details);\n\n                        if let Some(frames) = &v.frames {\n                            p.details += &frames_to_string(frames);\n                        }\n                    },\n                    EventData::QuicPacketReceived(v) => {\n                        p.details +=\n                            &serde_json::to_string(&v.header.packet_type)\n                                .unwrap()\n                                .replace(\"\\\"\", \"\");\n                        printyo!(\" pn\", v.header.packet_number, p.details);\n\n                        if let Some(frames) = &v.frames {\n                            p.details += &frames_to_string(frames);\n                        }\n                    },\n                    EventData::QuicStreamDataMoved(v) => {\n                        printyo!(\"id\", v.stream_id, p.details);\n                        printyo!(\"off\", v.offset, p.details);\n                        if let Some(raw) = &v.raw {\n                            printyo!(\"len\", raw.length, p.details);\n                        }\n                        printyo_json!(\"from\", &v.from, p.details);\n                        printyo_json!(\"to\", &v.to, p.details);\n                    },\n                    EventData::QuicMetricsUpdated(v) => {\n                        printyo!(\"min_rtt\", v.min_rtt, p.details);\n                        printyo!(\"smoothed_rtt\", v.smoothed_rtt, p.details);\n                        printyo!(\"latest_rtt\", v.latest_rtt, p.details);\n                        printyo!(\"rtt_variance\", v.rtt_variance, p.details);\n                        printyo!(\"pto_count\", v.pto_count, p.details);\n                        printyo!(\"cwnd\", v.congestion_window, p.details);\n                        printyo!(\"bytes_in_flight\", v.bytes_in_flight, p.details);\n                        printyo!(\"ssthresh\", v.ssthresh, p.details);\n                        printyo!(\n                            \"packets_in_flight\",\n                            v.packets_in_flight,\n                            p.details\n                        );\n                        printyo!(\"pacing_rate\", v.pacing_rate, p.details);\n                    },\n                    EventData::Http3StreamTypeSet(ev) => {\n                        printy!(\"id\", &ev.stream_id, p.details);\n                        printyo_json!(\"initiator\", &ev.initiator, p.details);\n                        printy_json!(\"ty\", &ev.stream_type, p.details);\n                    },\n                    EventData::Http3FrameCreated(v) => {\n                        printy!(\"id\", v.stream_id, p.details);\n                        printyo!(\"len\", v.length, p.details);\n                        p.details += &http_frame_to_string(&v.frame);\n                    },\n                    EventData::Http3FrameParsed(v) => {\n                        printy!(\"id\", v.stream_id, p.details);\n                        printyo!(\"len\", v.length, p.details);\n                        p.details += &http_frame_to_string(&v.frame);\n                    },\n\n                    _ => {\n                        p.details += &serde_json::to_string(&ev.data).unwrap();\n                    },\n                }\n\n                pp.push(p);\n            },\n\n            qlog::reader::Event::Json(ev) => {\n                let (cat, ty) = category_and_type_from_name(&ev.name);\n\n                let p = PrintableEvent {\n                    time: ev.time,\n                    ty,\n                    category: cat,\n                    details: serde_json::to_string(&ev.data).unwrap(),\n                };\n\n                pp.push(p);\n            },\n        }\n    }\n\n    Table::builder(pp)\n}\n"
  },
  {
    "path": "qlog-dancer/src/reports/html.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Reporting (tables etc.)\n\nuse table_to_html::html::Attribute;\nuse table_to_html::html::HtmlElement;\nuse table_to_html::html::HtmlValue;\nuse table_to_html::html::HtmlVisitorMut;\nuse table_to_html::HtmlTable;\nuse tabled::Table;\n\nuse crate::create_file_recursive;\nuse crate::reports::events::sqlog_event_list;\nuse crate::reports::text::request_timing_table;\nuse crate::AppConfig;\nuse crate::LogFileParseResult;\nuse std::io::Write;\n\nconst HTML_INCLUDES: &str = r#\"\n<script src=\"https://code.jquery.com/jquery-3.7.0.js\"></script>\n<script src=\"https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js\"></script>\n<script src=\"https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js\"></script>\n<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css\">\n<link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.datatables.net/1.13.7/css/jquery.dataTables.min.css\">\n\"#;\n\nconst TABLE_INIT_SCRIPT: &str = r#\"\n<script type=\"text/javascript\">\n    window.addEventListener(\"load\", (event) => {\n\n        let prefers = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n        let html = document.querySelector('html');\n\n        html.classList.add(prefers);\n        html.setAttribute('data-bs-theme', prefers);\n\n        new DataTable('table.log-dancer-table',\n        {\n            paging: false,\n            dom: '<\"center\" flpti  >'\n        });\n\n        let loading = document.getElementById(\"loading\");\n        loading.style.visibility = 'hidden';\n\n        let tables = document.getElementById(\"tables\");\n        tables.style.visibility = 'visible';\n    });\n</script>\n\"#;\n\nconst REQUEST_TABLE_INIT_SCRIPT: &str = r#\"\n<script type=\"text/javascript\">\n    window.addEventListener(\"load\", (event) => {\n\n        let prefers = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n        let html = document.querySelector('html');\n\n        html.classList.add(prefers);\n        html.setAttribute('data-bs-theme', prefers);\n\n        new DataTable('table.log-dancer-table',\n        {\n            paging: false,\n            dom: '<\"center\" flpti  >',\n            columnDefs: [\n                {targets: [0,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19], type: 'html-num'}\n            ]\n        });\n\n        let loading = document.getElementById(\"loading\");\n        loading.style.visibility = 'hidden';\n\n        let tables = document.getElementById(\"tables\");\n        tables.style.visibility = 'visible';\n    });\n</script>\n\"#;\n\nconst SESSIONS_STYLES: &str = r#\"\n<style>\n    .center {\n  margin: auto;\n  width: 90%;\n  padding: 10px;\n}\n\n.yellow {\n    color: yellow\n}\n\n.red {\n    color: red\n}\n\n.green {\n    color: limegreen\n}\n</style>\n\"#;\n\nfn inject_table_id_class(\n    input: &HtmlTable, id: Option<String>, class: Option<String>,\n) -> String {\n    let id = if let Some(i) = id {\n        format!(\"id='{i}'\")\n    } else {\n        \"\".to_string()\n    };\n    let class = if let Some(c) = class {\n        format!(\"class='{c}'\")\n    } else {\n        \"\".to_string()\n    };\n    let replaced = format!(\"<table {id} {class}>\");\n\n    input.to_string().replace(\"<table>\", &replaced)\n}\n\npub fn overview(log_file: &LogFileParseResult, config: &AppConfig) {\n    let mut h2 = vec![];\n    let mut quic = vec![];\n\n    for data in &log_file.data {\n        if let Some(h2_close) = &data.datastore.h2_session_close {\n            h2.push(h2_close);\n        }\n\n        if let Some(quic_close) = &data.datastore.quic_session_close {\n            quic.push(quic_close);\n        }\n    }\n\n    let filename = format!(\"{}-reports/overview.html\", config.filename);\n    let mut file = create_file_recursive(&filename).unwrap();\n\n    file.write_all(HTML_INCLUDES.as_bytes()).unwrap();\n    file.write_all(TABLE_INIT_SCRIPT.as_bytes()).unwrap();\n    file.write_all(SESSIONS_STYLES.as_bytes()).unwrap();\n    file.write_all(r#\"<html>\n    <head><head>\n    <body>\n        <div>\n            <h1 class=\"center\">Session Overview</h1>\n            <p class=\"center\">This page lists all the sessions (aka connections)\n            that were present in a log file. It is possible to filter only specific\n            SNIs for analysis using the qlog-dancer `--netlog-filter` option.</p>\n            <p class =\"center\">Analysed session detailed information is presented in\n            <a href=\"closures.html\">session terminations</a>\n            and <a href=\"requests.html\">requests breakdown</a>.</p>\n            \"#.as_bytes()).unwrap();\n\n    let all_table = HtmlTable::with_header(Vec::<Vec<String>>::from(\n        Table::builder(log_file.details.sessions.values()),\n    ));\n    file.write_all(\n        inject_table_id_class(\n            &all_table,\n            Some(\"all_sessions\".to_string()),\n            Some(\n                \"log-dancer-table cell-border hover compact order-column\"\n                    .to_string(),\n            ),\n        )\n        .as_bytes(),\n    )\n    .unwrap();\n\n    file.write_all(\n        r#\"\n        </div>\n    </body>\n    <html>\"#\n            .as_bytes(),\n    )\n    .unwrap();\n}\n\npub fn closures(log_file: &LogFileParseResult, config: &AppConfig) {\n    let mut h2 = vec![];\n    let mut quic = vec![];\n\n    for data in &log_file.data {\n        if let Some(h2_close) = &data.datastore.h2_session_close {\n            h2.push(h2_close);\n        }\n\n        if let Some(quic_close) = &data.datastore.quic_session_close {\n            quic.push(quic_close);\n        }\n    }\n\n    let filename = format!(\"{}-reports/closures.html\", config.filename);\n    let mut file = create_file_recursive(&filename).unwrap();\n\n    file.write_all(HTML_INCLUDES.as_bytes()).unwrap();\n    file.write_all(TABLE_INIT_SCRIPT.as_bytes()).unwrap();\n    file.write_all(SESSIONS_STYLES.as_bytes()).unwrap();\n    file.write_all(r#\"<html>\n    <head><head>\n    <body>\n        <div>\n            <h1 class=\"center\">Session Overview</h1>\n            <p class=\"center\">This page lists all the sessions (aka connections)\n            that were present in a log file and filtered into analysis using the\n            `--netlog-filter` option. Connections are split by HTTP version. A single SNI might\n            have multiple sessions, and it might use multiple HTTP versions. The reason that\n            each session is closed is also captured in the Error column (and subsequent columns).\n            A log that is closed before a session is terminated will not show any value in the\n            columns.</p>\n            <h2 class=\"center\">HTTP/2 Connections</h2>\"#.as_bytes()).unwrap();\n\n    let mut h2_html_table =\n        HtmlTable::with_header(Vec::<Vec<String>>::from(Table::builder(h2)));\n    h2_html_table.visit_mut(H2ClosureTableDecorator { i: 0 });\n\n    file.write_all(\n        inject_table_id_class(\n            &h2_html_table,\n            Some(\"h2_close\".to_string()),\n            Some(\n                \"log-dancer-table cell-border hover compact order-column\"\n                    .to_string(),\n            ),\n        )\n        .as_bytes(),\n    )\n    .unwrap();\n\n    file.write_all(\n        r#\"\n            <h2 class=\"center\">HTTP/3 & QUIC Connections</h2>\"#\n            .as_bytes(),\n    )\n    .unwrap();\n\n    let mut quic_html_table =\n        HtmlTable::with_header(Vec::<Vec<String>>::from(Table::builder(quic)));\n    quic_html_table.visit_mut(QUICClosureTableDecorator { i: 0 });\n    file.write_all(\n        inject_table_id_class(\n            &quic_html_table,\n            Some(\"quic_close\".to_string()),\n            Some(\n                \"log-dancer-table cell-border hover compact order-column\"\n                    .to_string(),\n            ),\n        )\n        .as_bytes(),\n    )\n    .unwrap();\n\n    file.write_all(\n        r#\"\n        </div>\n    </body>\n    <html>\"#\n            .as_bytes(),\n    )\n    .unwrap();\n}\n\npub fn requests(log_file: &LogFileParseResult, config: &AppConfig) {\n    let filename = format!(\"{}-reports/requests.html\", config.filename);\n\n    let mut file = create_file_recursive(&filename).unwrap();\n\n    file.write_all(HTML_INCLUDES.as_bytes()).unwrap();\n    file.write_all(REQUEST_TABLE_INIT_SCRIPT.as_bytes())\n        .unwrap();\n    file.write_all(SESSIONS_STYLES.as_bytes()).unwrap();\n    file.write_all(\n        r#\"<html>\n    <head><head>\n    <body>\n        <h1 class=\"center\">Summary of All HTTP Requests</h1>\n        <div class=\"center\" id=\"loading\">\n            <strong>Loading data...</strong>\n            <div class=\"spinner-border\" role=\"status\">\n            </div>\n        </div>\n        <div class=\"center\">\n            <p>This page provides information about the requests & responses in a connection.</p>\n            <p> Each individual table represents an HTTP session with a unique ID. Each session is bound\n            to an SNI and has a version. There can be multiple connections to the same SNI depending on the\n            client's behaviour.</p>\n            <p>In each table, a single row represents a request & response. The columns expressing different properties:</p>\n            <details>\n                <Summary>Click to expand</Summary>\n                <p>\n                <ul>\n                    <li><strong>ID</strong> - the stream ID of the request & response</li>\n                    <li><strong>Method</strong> - the request Method</li>\n                    <li><strong>Host</strong> - the request Host (or authority). Due to connection coalescing, this value can be dfifferent from the session SNI</li>\n                    <li><strong>Path</strong> - the request Path</li>\n                    <li><strong>Status</strong> - the response Status</li>\n                    <li><strong>Response Content-Length</strong> - for downloads; the value of the response Content-Length, if any. A response can omit this header. </li>\n                    <li><strong>Response Transferred</strong> - for downloads; the actual number of bytes of response that were received. This can be less than Response Content-Length, indicating that the request or connection was terminated early.</li>\n                    <li><strong>Download Duration (d2d) (ms)</strong> - the time duration between receiving the first and last DATA frame. This can be 0 for various reasons.</li>\n                    <li><strong>Download Rate (d2d) (Mbps)</strong> - the download rate, in megabits/s, between first and last DATA frames. This number has caveats - can be very high if data size or durations are small.</li>\n                    <li><strong>Download Duration (h2d) (ms)</strong> - the time duration between receiving the first HEADERS and last DATA frame. This can be 0 for various reasons.</li>\n                    <li><strong>Dowload Rate (h2d) (Mbps)</strong> - the download rate, in megabits/s, between first HEADERS and last DATA frames. This number has caveats - can be very high if data size or durations are small.</li>\n                    <li><strong>Client Tx Hdr, Rx First Data</strong> - the duration between the client sending a HEADERS frame, and the first DATA frame being received. This is analagous to TTFB.</li>\n                    <li><strong>Client Tx Hdr, Rx Last Data</strong> - the duration between the client sending a HEADERS frame, and the last DATA frame being received. This is analagous to TTLB.</li>\n                    <li><strong>Request Content-Length</strong> - for uploads; the value of the request Content-Length, if any. A request can omit this header. </li>\n                    <li><strong>Request Transferred</strong> - for uploads; the actual number of bytes of request that were sent. This can be less than Request Content-Length, indicating that the request or connection was terminated early.</li>\n                    <li><strong>Upload Duration (ms)</strong> - the time duration between sending the first and last DATA frame. This can be 0 for various reasons.</li>\n                    <li><strong>Upload Rate (Mbps)</strong> - the upload rate, in megabits/s, between first and last DATA frames. This number has caveats - can be very high if data size or durations are small.</li>\n                    <li><strong>Client Priority Header</strong> - the value of the RFC 9218 request Priority header, if any.</li>\n                    <li><strong>Server Priority Header</strong> - the value of the RFC 9218 response Priority header, if any.</li>\n                    <li><strong>Reset Stream Sent</strong> - the value of the error code in a Reset Stream, if sent.</li>\n                    <li><strong>Reset Stream Received</strong> - the value of the error code in a Reset Stream, if received.</li>\n                    <li><strong>Stop Sending Sent</strong> - the value of the error code in a Stop Sending, if sent.</li>\n                </ul>\n                </p>\n            </details>\n        </div>\n        <div id=\"tables\" style=\"visibility: hidden;\">\n            \"#\n            .as_bytes(),\n    )\n    .unwrap();\n\n    for data in &log_file.data {\n        file.write_all(\n            format!(\n                \"<h2 class=\\\"center\\\">Session ID: {:?}, {:?}, {:?}</h2>\",\n                data.datastore.session_id.unwrap_or(-1),\n                data.datastore\n                    .host\n                    .clone()\n                    .unwrap_or(\"ERROR UNKNOWN\".to_string()),\n                data.datastore.application_proto\n            )\n            .as_bytes(),\n        )\n        .unwrap();\n\n        // This is a bit weird, we need to get our actual Table and then convert\n        // it back to a builder to pass to HtmlTable.\n        let table: tabled::builder::Builder =\n            request_timing_table(data, config).unwrap().into();\n        let mut reqs = HtmlTable::with_header(Vec::<Vec<String>>::from(table));\n\n        // colorize the table\n        reqs.visit_mut(RequestTableDecorator { i: 0 });\n\n        file.write_all(\n            inject_table_id_class(\n                &reqs,\n                None,\n                Some(\n                    \"log-dancer-table cell-border hover compact order-column\"\n                        .to_string(),\n                ),\n            )\n            .as_bytes(),\n        )\n        .unwrap();\n    }\n\n    file.write_all(\n        r#\"\n        </div>\n    </body>\n    <html>\"#\n            .as_bytes(),\n    )\n    .unwrap();\n}\n\npub fn event_list_html_from_sqlog(events: &[qlog::reader::Event]) -> String {\n    let table = sqlog_event_list(events);\n    let table = HtmlTable::with_header(Vec::<Vec<String>>::from(table));\n    inject_table_id_class(\n        &table,\n        None,\n        Some(\n            \"log-dancer-table cell-border hover compact order-column\".to_string(),\n        ),\n    )\n}\n\npub fn event_list(log_file: &LogFileParseResult, config: &AppConfig) {\n    let filename = format!(\"{}-reports/event-list.html\", config.filename);\n    let mut file = create_file_recursive(&filename).unwrap();\n\n    file.write_all(HTML_INCLUDES.as_bytes()).unwrap();\n    file.write_all(TABLE_INIT_SCRIPT.as_bytes()).unwrap();\n    file.write_all(SESSIONS_STYLES.as_bytes()).unwrap();\n    file.write_all(\n        r#\"<html>\n    <head><head>\n    <body>\n        <div>\n            <h1 class=\"center\">List of events</h1>\n            <p class=\"center\">This page lists all the events\n            that were present in a log file.</p>\n            \"#\n        .as_bytes(),\n    )\n    .unwrap();\n\n    for data in &log_file.data {\n        match &data.raw {\n            crate::RawLogEvents::QlogJson { events: _ } =>\n                println!(\"Support for event list of contained qlog is TODO\"),\n            crate::RawLogEvents::QlogJsonSeq { events } => {\n                let table = event_list_html_from_sqlog(events);\n\n                file.write_all(table.as_bytes()).unwrap();\n            },\n            crate::RawLogEvents::Netlog =>\n                println!(\"Support for event list of netlog is TODO\"),\n        }\n    }\n\n    file.write_all(\n        r#\"\n        </div>\n    </body>\n    <html>\"#\n            .as_bytes(),\n    )\n    .unwrap();\n}\n\nfn table_cell_value(cell: &HtmlElement) -> Option<String> {\n    if cell.tag() == \"td\" {\n        if let Some(HtmlValue::Elements(elems)) = cell.value() {\n            if let Some(val) = elems.first() {\n                if let Some(HtmlValue::Elements(p)) = val.value() {\n                    if let Some(p_val) = p.first() {\n                        if let Some(HtmlValue::Content(inner)) = p_val.value() {\n                            return Some(inner.clone());\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    None\n}\n\nstruct H2ClosureTableDecorator {\n    i: usize,\n}\n\nimpl HtmlVisitorMut for H2ClosureTableDecorator {\n    fn visit_element_mut(&mut self, e: &mut HtmlElement) -> bool {\n        if e.tag() == \"tr\" {\n            if self.i == 0 {\n                self.i += 1;\n                return true;\n            }\n\n            let mut mark_red = false;\n            let mut mark_yellow = false;\n            let mut mark_green = false;\n\n            if let Some(HtmlValue::Elements(kids)) = e.value() {\n                if let Some(error) = kids.get(2) {\n                    if let Some(err) = table_cell_value(error) {\n                        if let Ok(val) = err.parse::<i32>() {\n                            // Aborted\n                            if val == -3 {\n                                mark_yellow = true;\n                            } else if val < 0 {\n                                mark_red = true;\n                            } else if val == 0 {\n                                mark_green = true;\n                            }\n                        }\n                    }\n                }\n            }\n\n            if mark_yellow {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"yellow\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            } else if mark_red {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"red\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            } else if mark_green {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"green\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            }\n        }\n\n        true\n    }\n}\n\nstruct QUICClosureTableDecorator {\n    i: usize,\n}\n\nimpl HtmlVisitorMut for QUICClosureTableDecorator {\n    fn visit_element_mut(&mut self, e: &mut HtmlElement) -> bool {\n        if e.tag() == \"tr\" {\n            if self.i == 0 {\n                self.i += 1;\n                return true;\n            }\n\n            let mut mark_red = false;\n            let mut mark_yellow = false;\n            let mut mark_green = false;\n\n            if let Some(HtmlValue::Elements(kids)) = e.value() {\n                if let Some(error) = kids.get(2) {\n                    if let Some(err) = table_cell_value(error) {\n                        if let Ok(val) = err.parse::<i32>() {\n                            // Aborted\n                            if val == 25 {\n                                mark_green = true;\n                            } else if val == 70 {\n                                mark_yellow = true;\n                            } else if val == 199 {\n                                mark_red = true;\n                            }\n                        }\n                    }\n                }\n            }\n\n            if mark_yellow {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"yellow\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            } else if mark_red {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"red\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            } else if mark_green {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"green\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            }\n        }\n\n        true\n    }\n}\n\nstruct RequestTableDecorator {\n    i: usize,\n}\n\nimpl HtmlVisitorMut for RequestTableDecorator {\n    fn visit_element_mut(&mut self, e: &mut HtmlElement) -> bool {\n        if e.tag() == \"tr\" {\n            if self.i == 0 {\n                self.i += 1;\n                return true;\n            }\n\n            let mut mark_red = false;\n            let mut mark_yellow = false;\n            let mut mark_green = false;\n\n            if let Some(HtmlValue::Elements(kids)) = e.value() {\n                if let Some(reset_sent) = kids.get(24) {\n                    if table_cell_value(reset_sent) != Some(\"n/a\".to_string()) {\n                        mark_yellow = true;\n                    }\n                }\n\n                if let Some(reset_received) = kids.get(25) {\n                    if table_cell_value(reset_received) != Some(\"n/a\".to_string())\n                    {\n                        mark_red = true;\n                    }\n                }\n\n                if let Some(status_code) = kids.get(4) {\n                    if let Some(val) = table_cell_value(status_code) {\n                        // if the status code is unknown, we probably got no\n                        // response\n\n                        if let Ok(val) = val.parse::<u16>() {\n                            if val >= 400 {\n                                mark_red = true;\n                            }\n                        } else {\n                            // if the status code is unknown or mangled, we\n                            // probably got no response\n                            mark_red = true;\n                        }\n                    }\n                }\n                let response_content_length = kids.get(5);\n                let response_content_transferred = kids.get(6);\n\n                if let (Some(cl), Some(tx)) =\n                    (response_content_length, response_content_transferred)\n                {\n                    let cl = table_cell_value(cl);\n                    let tx = table_cell_value(tx);\n\n                    if let (Some(length), Some(actual)) = (cl, tx) {\n                        // If there is no content-length, we can't verify\n                        // it was receivd ok.\n                        if length == \"n/a\" || length == actual {\n                            mark_green = true;\n                        } else {\n                            mark_red = true;\n                        }\n                    }\n                }\n            }\n\n            if mark_yellow {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"yellow\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            } else if mark_red {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"red\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            } else if mark_green {\n                let mut attrs = e.attrs().to_vec();\n                attrs.push(Attribute::new(\"class\", \"green\".to_string()));\n                *e = HtmlElement::new(\"tr\", attrs, e.value().cloned());\n            }\n        }\n\n        true\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/reports/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Reporting (tables etc.)\n\nuse events::sqlog_event_list;\nuse tabled::settings::Style;\n\nuse crate::config::AppConfig;\nuse crate::LogFileParseResult;\n\npub fn report(log_file: &LogFileParseResult, config: &AppConfig) {\n    if config.report_text {\n        for data in &log_file.data {\n            if let Some(table) = text::request_timing_table(data, config) {\n                println!(\n                    \"Request timing table for session ID: {:?}, app proto: {:?}, host: {:?}\",\n                    &data.datastore.session_id.unwrap_or(-1),\n                    &data.datastore.application_proto,\n                    &data.datastore\n                        .host\n                        .clone()\n                        .unwrap_or(\"ERROR UNKNOWN\".to_string())\n                );\n                println!(\"{}\", table);\n                println!();\n            }\n\n            text::print_stats(&data.datastore, &config.stats_config);\n            println!();\n\n            match &data.raw {\n                crate::RawLogEvents::QlogJson { events: _ } => todo!(),\n                crate::RawLogEvents::QlogJsonSeq { events } => {\n                    let mut table = sqlog_event_list(events).build();\n                    table.with(Style::sharp());\n                    println!(\"Qlog events\");\n                    println!(\"{}\", table);\n                },\n                crate::RawLogEvents::Netlog => todo!(),\n            }\n        }\n\n        // TODO: make this more configurable\n        text::print_packet_loss(&log_file.data);\n        text::print_flow_control(&log_file.data);\n    }\n\n    if config.report_html {\n        html::overview(log_file, config);\n        html::closures(log_file, config);\n        html::requests(log_file, config);\n        html::event_list(log_file, config);\n    }\n}\n\nmod events;\npub mod html;\nmod text;\n"
  },
  {
    "path": "qlog-dancer/src/reports/text.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Reporting (tables etc.)\n\nuse qlog::events::quic::QuicFrame;\nuse tabled::settings::location::ByColumnName;\nuse tabled::settings::object::Segment;\nuse tabled::settings::Alignment;\nuse tabled::settings::Disable;\nuse tabled::settings::Modify;\nuse tabled::settings::Style;\nuse tabled::Table;\n\nuse crate::config::AppConfig;\nuse crate::datastore::Datastore;\nuse crate::datastore::PrintStatsConfig;\nuse crate::request_stub::*;\nuse crate::stringify_last;\nuse crate::LogFileData;\n\npub fn request_timing_table(\n    lf: &LogFileData, config: &AppConfig,\n) -> Option<Table> {\n    let mut table = Table::new(lf.datastore.http_requests.values());\n    table.with(Modify::new(Segment::all()).with(Alignment::right()));\n\n    if config.report_omit_upload {\n        table\n            .with(Disable::column(ByColumnName::new(CLIENT_CONTENT_LENGTH)))\n            .with(Disable::column(ByColumnName::new(CLIENT_TRANSFERRED)))\n            .with(Disable::column(ByColumnName::new(UPLOAD_TIME)))\n            .with(Disable::column(ByColumnName::new(UPLOAD_RATE)));\n    }\n\n    match lf.datastore.vantage_point {\n        crate::datastore::VantagePoint::Client => {\n            table\n                .with(Disable::column(ByColumnName::new(SERVER_RX_HDR_TX_HDR)))\n                .with(Disable::column(ByColumnName::new(\n                    SERVER_TX_HDR_TX_FIRST_HDR,\n                )))\n                .with(Disable::column(ByColumnName::new(\n                    SERVER_TX_HDR_TX_LAST_HDR,\n                )))\n                .with(Disable::column(ByColumnName::new(\n                    SERVER_TX_FIRST_DATA_TX_LAST_DATA,\n                )));\n        },\n\n        crate::datastore::VantagePoint::Server => {\n            // TODO:\n        },\n    }\n\n    if config.report_omit_priorities {\n        table\n            .with(Disable::column(ByColumnName::new(CLIENT_PRI)))\n            .with(Disable::column(ByColumnName::new(SERVER_PRI)));\n    }\n\n    if config.report_text_csv {\n        let style = Style::empty().vertical(',');\n\n        table.with(style);\n    }\n\n    Some(table)\n}\n\npub fn print_stats(data_store: &Datastore, config: &PrintStatsConfig) {\n    if config.rx_flow_control {\n        print_rx_max_data_frames(data_store);\n        print_rx_max_stream_data_frames(data_store);\n    }\n\n    if config.tx_flow_control {\n        print_tx_max_data_frames(data_store);\n        print_tx_max_stream_data_frames(data_store);\n    }\n\n    if config.reset_streams {\n        print_tx_reset_stream_frames(data_store);\n        print_rx_reset_stream_frames(data_store);\n    }\n\n    if config.tx_stream_frames {\n        print_tx_stream_frames(data_store);\n    }\n\n    if config.stream_buffering {\n        print_local_stream_buffer_reads(data_store);\n        print_local_stream_buffer_writes(data_store);\n        print_local_stream_buffer_dropped(data_store);\n    }\n\n    if config.packet_stats {\n        print_sent_packet_stats(data_store);\n    }\n}\n\nfn print_sent_packet_stats(data_store: &Datastore) {\n    println!(\"### sent packets ###\");\n    for (pkt_space, pkts) in &data_store.packet_sent {\n        println!(\"\\t# packet space={:?}\", pkt_space);\n\n        for (pkt_num, pkt_info) in pkts {\n            let (length, payload_length) = match &pkt_info.raw {\n                Some(raw) => (raw.length, raw.payload_length),\n\n                None => (None, None),\n            };\n\n            println!(\n                \"\\tpkt_num={}, acked=TODO-unknown, length={:?}, payload_length={:?}, \",\n                pkt_num, length, payload_length\n            );\n        }\n        println!();\n    }\n}\n\nfn print_rx_max_data_frames(data_store: &Datastore) {\n    println!(\"### received MAX_DATA frames ###\");\n    if data_store\n        .received_stream_max_data_tracker\n        .per_stream\n        .is_empty()\n    {\n        println!(\"    None\")\n    } else {\n        println!(\n            \"    first={:?}, last={:?}\",\n            data_store.received_max_data.first().unwrap(),\n            data_store.received_max_data.last().unwrap()\n        );\n    }\n}\n\nfn print_tx_max_data_frames(data_store: &Datastore) {\n    println!(\"### sent MAX_DATA frames ###\");\n    println!(\n        \"   total_count={}, first={:?}, last={:?}\",\n        data_store.sent_max_data.len(),\n        data_store.sent_max_data.first().unwrap(),\n        data_store.sent_max_data.last().unwrap()\n    );\n}\n\nfn print_rx_max_stream_data_frames(data_store: &Datastore) {\n    println!(\"### received MAX_STREAM_DATA frames ###\");\n    if data_store\n        .received_stream_max_data_tracker\n        .per_stream\n        .is_empty()\n    {\n        println!(\"    None\")\n    } else {\n        for entry in &data_store.received_stream_max_data_tracker.per_stream {\n            println!(\n                \"    stream={}, total_count={}, first={:?}, last={}\",\n                entry.0,\n                entry.1.len(),\n                entry.1.first(),\n                stringify_last(entry.1)\n            );\n        }\n    }\n}\n\nfn print_tx_max_stream_data_frames(data_store: &Datastore) {\n    println!(\"### sent MAX_STREAM_DATA frames ###\");\n    if data_store\n        .sent_stream_max_data_tracker\n        .per_stream\n        .is_empty()\n    {\n        println!(\"    None\")\n    } else {\n        for entry in &data_store.sent_stream_max_data_tracker.per_stream {\n            println!(\n                \"    stream={}, total_count={}, first={:?}, last={:?}\",\n                entry.0,\n                entry.1.len(),\n                entry.1.first().unwrap(),\n                entry.1.last().unwrap()\n            );\n        }\n    }\n}\n\nfn print_tx_reset_stream_frames(data_store: &Datastore) {\n    println!(\"### sent RESET_STREAM frames ###\");\n    if data_store.sent_reset_stream.is_empty() {\n        println!(\"    None\")\n    } else {\n        for entry in &data_store.sent_reset_stream {\n            println!(\n                \"    stream={}, total_count={}, first={:?}, last={:?}\",\n                entry.0,\n                entry.1.len(),\n                entry.1.first(),\n                stringify_last(entry.1)\n            );\n        }\n    }\n}\n\nfn print_rx_reset_stream_frames(data_store: &Datastore) {\n    println!(\"### received RESET_STREAM frames ###\");\n    if data_store.received_reset_stream.is_empty() {\n        println!(\"    None\")\n    } else {\n        for entry in &data_store.received_reset_stream {\n            println!(\n                \"    stream={}, total_count={}, first={:?}, last={:?}\",\n                entry.0,\n                entry.1.len(),\n                entry.1.first(),\n                stringify_last(entry.1)\n            );\n        }\n    }\n}\n\nfn print_local_stream_buffer_reads(data_store: &Datastore) {\n    println!(\"### local stream buffer reads ###\");\n    if data_store.stream_buffer_reads_tracker.per_stream.is_empty() {\n        println!(\"    None\")\n    } else {\n        for entry in &data_store.stream_buffer_reads_tracker.per_stream {\n            println!(\n                \"    stream={}, total_count={}, first=(offset={}, length={}), last=(offset={}, length={}), total_length={}\",\n                entry.0,\n                entry.1.len(),\n                entry.1.first().unwrap().1.offset,\n                entry.1.first().unwrap().1.length,\n                entry.1.last().unwrap().1.offset,\n                entry.1.last().unwrap().1.length,\n                entry.1.last().unwrap().1.offset + entry.1.last().unwrap().1.length,\n            );\n        }\n    }\n}\n\nfn print_local_stream_buffer_writes(data_store: &Datastore) {\n    println!(\"### local stream buffer writes ###\");\n    if data_store\n        .stream_buffer_writes_tracker\n        .per_stream\n        .is_empty()\n    {\n        println!(\"    None\")\n    } else {\n        for entry in &data_store.stream_buffer_writes_tracker.per_stream {\n            println!(\n                \"    stream={}, total_count={}, first=(offset={}, length={}), last=(offset={}, length={}), total_length={}\",\n                entry.0,\n                entry.1.len(),\n                entry.1.first().unwrap().1.offset,\n                entry.1.first().unwrap().1.length,\n                entry.1.last().unwrap().1.offset,\n                entry.1.last().unwrap().1.length,\n                entry.1.last().unwrap().1.offset + entry.1.last().unwrap().1.length,\n            );\n        }\n    }\n}\n\nfn print_local_stream_buffer_dropped(data_store: &Datastore) {\n    println!(\"### local stream buffer dropped ###\");\n    if data_store\n        .stream_buffer_dropped_tracker\n        .per_stream\n        .is_empty()\n    {\n        println!(\"    None\")\n    } else {\n        for entry in &data_store.stream_buffer_dropped_tracker.per_stream {\n            println!(\n                \"    stream={}, total_count={}, first=(offset={}, length={}), last=(offset={}, length={}), total_length={}\",\n                entry.0,\n                entry.1.len(),\n                entry.1.first().unwrap().1.offset,\n                entry.1.first().unwrap().1.length,\n                entry.1.last().unwrap().1.offset,\n                entry.1.last().unwrap().1.length,\n                entry.1.last().unwrap().1.offset + entry.1.last().unwrap().1.length,\n            );\n        }\n    }\n}\n\nfn print_tx_stream_frames(data_store: &Datastore) {\n    println!(\"### sent STREAM frames ###\");\n    if data_store.sent_stream_frames.is_empty() {\n        println!(\"    None\")\n    } else {\n        for entry in &data_store.sent_stream_frames {\n            let total = match entry.1.last() {\n                Some((_, QuicFrame::Stream { offset, raw, .. })) => {\n                    let offset = offset.unwrap_or_default();\n                    let length = raw\n                        .clone()\n                        .unwrap_or_default()\n                        .payload_length\n                        .unwrap_or_default();\n                    format!(\"{}\", offset + length)\n                },\n\n                _ => \"n/a\".to_string(),\n            };\n\n            println!(\n                \"    stream={}, total_count={}, first={:?}, last={:?}, total_length={}\",\n                entry.0,\n                entry.1.len(),\n                entry.1.first(),\n                stringify_last(entry.1),\n                total\n            );\n        }\n    }\n}\n\npub fn print_flow_control(data: &[LogFileData]) {\n    // TODO make this a proper table\n    println!(\"================\");\n    println!(\"flow control stuff\");\n    println!(\"================\");\n    for lf in data {\n        println!(\n            \"Session={}, host={}\",\n            lf.datastore.session_id.unwrap_or(-1),\n            lf.datastore\n                .host\n                .clone()\n                .unwrap_or(\"ERROR UNKNOWN\".to_string())\n        );\n\n        println!(\"  Initial Client connection window, Initial Client Bidi Local Stream Window\");\n        println!(\n            \"  {},{}\",\n            lf.datastore.client_quic_tps.initial_max_data.unwrap_or(0),\n            lf.datastore\n                .client_quic_tps\n                .initial_max_stream_data_bidi_local\n                .unwrap_or(0)\n        );\n\n        for (stream_id, points) in\n            &lf.datastore.netlog_quic_client_side_window_updates\n        {\n            println!(\"  Stream {} flow control updates\", stream_id);\n            println!(\"    Time, Value\");\n\n            for (time, val) in points {\n                println!(\"    {},{}\", time, val);\n            }\n        }\n    }\n}\n\npub fn print_packet_loss(data: &[LogFileData]) {\n    // TODO make this a proper table\n    println!(\"================\");\n    println!(\"QUIC packet loss\");\n    println!(\"================\");\n    for lf in data {\n        let is_received_some = lf\n            .datastore\n            .netlog_ack_sent_missing_packet\n            .contains_key(&crate::PacketType::Handshake) ||\n            lf.datastore\n                .netlog_ack_sent_missing_packet\n                .contains_key(&crate::PacketType::Initial) ||\n            lf.datastore\n                .netlog_ack_sent_missing_packet\n                .contains_key(&crate::PacketType::OneRtt) ||\n            lf.datastore\n                .netlog_ack_sent_missing_packet\n                .contains_key(&crate::PacketType::ZeroRtt) ||\n            lf.datastore\n                .netlog_ack_sent_missing_packet\n                .contains_key(&crate::PacketType::Retry) ||\n            lf.datastore\n                .netlog_ack_sent_missing_packet\n                .contains_key(&crate::PacketType::VersionNegotiation);\n\n        if !lf.datastore.netlog_ack_received_missing_packet.is_empty() ||\n            is_received_some\n        {\n            println!(\n                \"Session={}, host={}\",\n                lf.datastore.session_id.unwrap_or(-1),\n                lf.datastore\n                    .host\n                    .clone()\n                    .unwrap_or(\"ERROR UNKNOWN\".to_string())\n            );\n        }\n\n        if !lf.datastore.netlog_ack_received_missing_packet.is_empty() {\n            println!(\"  Packets sent and lost\");\n            println!(\"    Packet Type, Packet tx count, Packet lost count, Packet loss %, Packets lost\");\n\n            for (pkt_type, pkt_nums) in\n                &lf.datastore.netlog_ack_received_missing_packet\n            {\n                if let Some(pkts) = lf.datastore.packet_received.get(pkt_type) {\n                    let total_pkts_tx = pkts.len();\n                    let pkt_loss = pkt_nums.len() as f64 / total_pkts_tx as f64;\n\n                    if !pkt_nums.is_empty() {\n                        println!(\n                            \"    {:?}, {}, {}, {:.2}, {:?}\",\n                            pkt_type,\n                            total_pkts_tx,\n                            pkt_nums.len(),\n                            pkt_loss * 100f64,\n                            pkt_nums\n                        );\n                    }\n                }\n            }\n\n            println!();\n        }\n\n        if is_received_some {\n            println!(\"  Packet lost and not received\");\n            println!(\"    Packet Type, Packet rx count, Packet lost count, Packet loss %, Packets lost\");\n\n            for (pkt_type, pkt_nums) in\n                &lf.datastore.netlog_ack_sent_missing_packet\n            {\n                if let Some(pkts) = lf.datastore.packet_received.get(pkt_type) {\n                    let total_pkts_tx = pkts.len();\n                    let pkt_loss = pkt_nums.len() as f64 / total_pkts_tx as f64;\n\n                    if !pkt_nums.is_empty() {\n                        println!(\n                            \"    {:?}, {}, {}, {:.2}, {:?}\",\n                            pkt_type,\n                            total_pkts_tx,\n                            pkt_nums.len(),\n                            pkt_loss * 100f64,\n                            pkt_nums\n                        );\n                    }\n                }\n            }\n\n            println!();\n        }\n    }\n\n    println!();\n}\n"
  },
  {
    "path": "qlog-dancer/src/request_stub.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse qlog::events::http3::HttpHeader;\nuse std::borrow::Cow;\nuse std::fmt::Display;\n\nuse crate::datastore::H2StreamReset;\nuse crate::datastore::QuicStreamReset;\nuse crate::datastore::QuicStreamStopSending;\nuse crate::datastore::RequestActor;\nuse crate::datastore::RequestAtClientDeltas;\nuse crate::datastore::RequestAtServerDeltas;\nuse netlog::http::headers_to_map;\n\npub const CLIENT_CONTENT_LENGTH: &str = \"Request Content-Length\";\npub const CLIENT_TRANSFERRED: &str = \"Request Transferred\";\npub const UPLOAD_TIME: &str = \"Upload duration (ms)\";\npub const UPLOAD_RATE: &str = \"Upload rate (Mbps)\";\npub const SERVER_RX_HDR_TX_HDR: &str = \"Server Rx Hdr, Tx Hdr\";\npub const SERVER_TX_HDR_TX_FIRST_HDR: &str = \"Server Tx Hdr, Tx First Hdr\";\npub const SERVER_TX_HDR_TX_LAST_HDR: &str = \"Server Tx Hdr, Tx Last Hdr\";\npub const SERVER_TX_FIRST_DATA_TX_LAST_DATA: &str =\n    \"Server Tx First Data, Tx Last Data (Download time)\";\n\npub const CLIENT_PRI: &str = \"Client Priority Header\";\npub const SERVER_PRI: &str = \"Server Priority Header\";\n\nconst MAX_PATH_LENGTH: usize = 80;\n\n#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]\npub struct NaOption<T> {\n    pub inner: Option<T>,\n}\n\nimpl<T> NaOption<T> {\n    pub fn new(value: Option<T>) -> Self {\n        Self { inner: value }\n    }\n}\n\nimpl<T: std::fmt::Display> Display for NaOption<T> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self.inner.as_ref() {\n            Some(v) => write!(f, \"{}\", v),\n            None => write!(f, \"n/a\"),\n        }\n    }\n}\n\n#[derive(Debug, Default)]\npub struct HttpRequestStub {\n    pub request_actor: RequestActor,\n    pub stream_id: u64,\n    pub host: NaOption<String>,\n    pub method: NaOption<String>,\n    pub path: NaOption<String>,\n    pub status: NaOption<String>,\n    pub client_content_length: NaOption<String>,\n    pub server_content_length: NaOption<String>,\n    pub client_pri_hdr: NaOption<String>,\n    pub server_pri_hdr: NaOption<String>,\n    pub priority_updates: Vec<String>,\n\n    pub time_discovery: Option<f64>,\n    pub time_first_headers_rx: Option<f64>,\n    pub time_first_headers_tx: Option<f64>,\n\n    pub time_first_data_rx: Option<f64>,\n    pub time_first_data_tx: Option<f64>,\n\n    pub time_last_data_rx: Option<f64>,\n    pub time_last_data_tx: Option<f64>,\n\n    pub time_fin_rx: Option<f64>,\n\n    // TODO, Option<u64>\n    pub time_data_rx_set: Vec<(f64, u64)>,\n    pub time_data_tx_set: Vec<(f64, u64)>,\n\n    pub client_transferred_bytes: NaOption<u64>,\n    pub server_transferred_bytes: NaOption<u64>,\n\n    pub avg_upload_rate: NaOption<f64>,\n    pub avg_download_rate_d2d: NaOption<f64>,\n    pub avg_download_rate_h2d: NaOption<f64>,\n\n    pub at_client_deltas: Option<RequestAtClientDeltas>,\n    pub at_server_deltas: Option<RequestAtServerDeltas>,\n\n    pub quic_stream_stop_sending_sent: Option<QuicStreamStopSending>,\n    pub quic_stream_stop_sending_received: Option<QuicStreamStopSending>,\n\n    pub quic_stream_reset_sent: Option<QuicStreamReset>,\n    pub quic_stream_reset_received: Option<QuicStreamReset>,\n\n    pub h2_stream_reset_sent: Option<H2StreamReset>,\n    pub h2_stream_reset_receive: Option<H2StreamReset>,\n}\n\nstruct ClientDeltaStrings {\n    pub discovery_tx_hdr: String,\n    pub tx_hdr_rx_hdr: String,\n    pub tx_hdr_rx_first_data: String,\n    pub tx_hdr_rx_last_data: String,\n    pub download_time_d2d: String,\n    pub download_time_h2d: String,\n    pub upload_time: String,\n}\n\nimpl Default for ClientDeltaStrings {\n    fn default() -> Self {\n        let na = NaOption::<u8>::new(None);\n\n        Self {\n            discovery_tx_hdr: na.to_string(),\n            tx_hdr_rx_hdr: na.to_string(),\n            tx_hdr_rx_first_data: na.to_string(),\n            tx_hdr_rx_last_data: na.to_string(),\n            download_time_d2d: na.to_string(),\n            download_time_h2d: na.to_string(),\n            upload_time: na.to_string(),\n        }\n    }\n}\n\nimpl HttpRequestStub {\n    fn client_deltas(&self) -> ClientDeltaStrings {\n        match self.at_client_deltas {\n            Some(d) => ClientDeltaStrings {\n                discovery_tx_hdr: d.discover_tx_hdr.to_string(),\n                tx_hdr_rx_hdr: d.tx_hdr_rx_hdr.to_string(),\n                tx_hdr_rx_first_data: d.tx_hdr_rx_first_data.to_string(),\n                tx_hdr_rx_last_data: d.tx_hdr_rx_last_data.to_string(),\n                download_time_d2d: d.rx_first_data_rx_last_data.to_string(),\n                download_time_h2d: d.tx_hdr_rx_last_data.to_string(),\n                upload_time: d.tx_first_data_tx_last_data.to_string(),\n            },\n\n            None => ClientDeltaStrings::default(),\n        }\n    }\n\n    fn server_deltas(&self) -> (String, String, String, String) {\n        match self.at_server_deltas {\n            Some(d) => (\n                d.rx_hdr_tx_hdr.to_string(),\n                d.rx_hdr_tx_first_data.to_string(),\n                d.rx_hdr_tx_last_data.to_string(),\n                d.tx_first_data_tx_last_data.to_string(),\n            ),\n\n            None => {\n                let na = NaOption::<u8>::new(None);\n                (\n                    na.to_string(),\n                    na.to_string(),\n                    na.to_string(),\n                    na.to_string(),\n                )\n            },\n        }\n    }\n}\n\nimpl tabled::Tabled for HttpRequestStub {\n    const LENGTH: usize = 28;\n\n    fn fields(&self) -> Vec<Cow<'_, str>> {\n        // truncate long paths for\n        let mut path = self.path.to_string();\n        if path.len() > MAX_PATH_LENGTH {\n            path.truncate(MAX_PATH_LENGTH);\n            path.push_str(\"<snip>\");\n        }\n\n        let req_start = match self.request_actor {\n            RequestActor::Client => self.time_first_headers_tx,\n            RequestActor::Server => self.time_first_headers_rx,\n        };\n        let req_start = NaOption::new(req_start);\n\n        // This one is tricky, we might never receive anything, so do a few\n        // checks.\n        let req_end = match self.request_actor {\n            RequestActor::Client =>\n                if self.time_last_data_rx.is_some() {\n                    self.time_last_data_rx\n                } else {\n                    self.time_first_headers_rx\n                },\n            RequestActor::Server =>\n                if self.time_last_data_tx.is_some() {\n                    self.time_first_data_tx\n                } else {\n                    self.time_first_headers_tx\n                },\n        };\n\n        let req_end = NaOption::new(req_end);\n\n        let client_deltas = self.client_deltas();\n\n        let (\n            server_rx_hdr_tx_hdr,\n            server_rx_hdr_tx_first_data,\n            server_rx_hdr_tx_last_data,\n            server_tx_first_data_tx_last_data,\n        ) = self.server_deltas();\n\n        let rst_stream_sent =\n            match (&self.quic_stream_reset_sent, &self.h2_stream_reset_sent) {\n                (Some(v), None) => v.to_string(),\n                (None, Some(v)) => v.to_string(),\n                _ => NaOption::<u8>::new(None).to_string(),\n            };\n\n        let rst_stream_received = match (\n            &self.quic_stream_reset_received,\n            &self.h2_stream_reset_receive,\n        ) {\n            (Some(v), None) => v.to_string(),\n            (None, Some(v)) => v.to_string(),\n            _ => NaOption::<u8>::new(None).to_string(),\n        };\n\n        let stop_sending_sent = match &self.quic_stream_stop_sending_sent {\n            Some(v) => v.to_string(),\n            None => NaOption::<u8>::new(None).to_string(),\n        };\n\n        vec![\n            self.stream_id.to_string().into(),\n            self.method.to_string().into(),\n            self.host.to_string().into(),\n            path.into(),\n            self.status.to_string().into(),\n            self.server_content_length.to_string().into(),\n            self.server_transferred_bytes.to_string().into(),\n            NaOption::new(self.time_discovery).to_string().into(),\n            req_start.to_string().into(),\n            req_end.to_string().into(),\n            client_deltas.discovery_tx_hdr.into(),\n            client_deltas.download_time_h2d.into(),\n            self.avg_download_rate_h2d.to_string().into(),\n            client_deltas.download_time_d2d.into(),\n            self.avg_download_rate_d2d.to_string().into(),\n            client_deltas.tx_hdr_rx_hdr.into(),\n            client_deltas.tx_hdr_rx_first_data.into(),\n            client_deltas.tx_hdr_rx_last_data.into(),\n            self.client_content_length.to_string().into(),\n            self.client_transferred_bytes.to_string().into(),\n            client_deltas.upload_time.into(),\n            self.avg_upload_rate.to_string().into(),\n            server_rx_hdr_tx_hdr.into(),\n            server_rx_hdr_tx_first_data.into(),\n            server_rx_hdr_tx_last_data.into(),\n            server_tx_first_data_tx_last_data.into(),\n            self.client_pri_hdr.to_string().into(),\n            self.server_pri_hdr.to_string().into(),\n            rst_stream_sent.into(),\n            rst_stream_received.into(),\n            stop_sending_sent.into(),\n        ]\n    }\n\n    fn headers() -> Vec<Cow<'static, str>> {\n        vec![\n            \"ID\".into(),\n            \"Method\".into(),\n            \"Host\".into(),\n            \"Path\".into(),\n            \"Status\".into(),\n            \"Response Content-Length (bytes)\".into(),\n            \"Response Transferred (bytes)\".into(),\n            \"Request Discovered Time\".into(),\n            \"Request Start Time\".into(),\n            \"Request End Time\".into(),\n            \"Stalled duration (ms)\".into(),\n            \"Download duration (h2d) (ms)\".into(),\n            \"Download rate (h2d) (Mbps)\".into(),\n            \"Download duration (d2d) (ms)\".into(),\n            \"Download rate (d2d) (Mbps)\".into(),\n            \"Client Tx Hdr, Rx Hdr\".into(),\n            \"Client Tx Hdr, Rx First Data\".into(),\n            \"Client Tx Hdr, Rx Last Data\".into(),\n            CLIENT_CONTENT_LENGTH.into(),\n            CLIENT_TRANSFERRED.into(),\n            UPLOAD_TIME.into(),\n            UPLOAD_RATE.into(),\n            SERVER_RX_HDR_TX_HDR.into(),\n            SERVER_TX_HDR_TX_FIRST_HDR.into(),\n            SERVER_TX_HDR_TX_LAST_HDR.into(),\n            SERVER_TX_FIRST_DATA_TX_LAST_DATA.into(),\n            CLIENT_PRI.into(),\n            SERVER_PRI.into(),\n            \"Reset Stream Sent\".into(),\n            \"Reset Stream Received\".into(),\n            \"Stop Sending Sent\".into(),\n        ]\n    }\n}\n\npub fn find_header_value(hdrs: &[HttpHeader], name: &str) -> Option<String> {\n    hdrs.iter()\n        .find(|&h| h.name.as_deref() == Some(name))\n        .and_then(|h| h.value.clone())\n}\n\nimpl HttpRequestStub {\n    pub fn set_request_info_from_netlog(&mut self, hdrs: &[String]) {\n        // TODO: case-sensitivity for HTTP/1.1\n        let headers = headers_to_map(hdrs);\n\n        self.method = NaOption::new(headers.get(\":method\").cloned());\n        self.host = NaOption::new(headers.get(\":authority\").cloned());\n        self.path = NaOption::new(headers.get(\":path\").cloned());\n        self.client_pri_hdr = NaOption::new(headers.get(\"priority\").cloned());\n        self.client_content_length =\n            NaOption::new(headers.get(\"content-length\").cloned());\n    }\n\n    pub fn set_response_info_from_netlog(&mut self, hdrs: &[String]) {\n        // TODO: case-sensitivity for HTTP/1.1\n        let headers = headers_to_map(hdrs);\n\n        self.status = NaOption::new(headers.get(\":status\").cloned());\n        self.server_pri_hdr = NaOption::new(headers.get(\"priority\").cloned());\n        self.server_content_length =\n            NaOption::new(headers.get(\"content-length\").cloned());\n    }\n\n    pub fn set_request_info_from_qlog(&mut self, hdrs: &[HttpHeader]) {\n        self.method = NaOption::new(find_header_value(hdrs, \":method\"));\n        self.host = NaOption::new(find_header_value(hdrs, \":authority\"));\n        self.path = NaOption::new(find_header_value(hdrs, \":path\"));\n        self.client_pri_hdr = NaOption::new(find_header_value(hdrs, \"priority\"));\n        self.client_content_length =\n            NaOption::new(find_header_value(hdrs, \"content-lengt\"));\n    }\n\n    pub fn set_response_info_from_qlog(&mut self, hdrs: &[HttpHeader]) {\n        self.status = NaOption::new(find_header_value(hdrs, \":status\"));\n        self.server_pri_hdr = NaOption::new(find_header_value(hdrs, \"priority\"));\n        self.server_content_length =\n            NaOption::new(find_header_value(hdrs, \"content-length\"));\n    }\n\n    // input times in milliseconds\n    fn maybe_megabits_per_second(\n        start: Option<f64>, end: Option<f64>, bytes: Option<u64>,\n    ) -> NaOption<f64> {\n        match (end, start, bytes) {\n            (Some(end), Some(start), Some(bytes)) => {\n                let mut total_time = end - start;\n                // there might be only one frame, or it was sent super quick,\n                // clamp to 1ms\n                if total_time == 0.0 {\n                    total_time = 1.0\n                };\n\n                // convert to seconds\n                total_time /= 1000.0;\n\n                let megabits = (bytes * 8) as f64 / 1000000.0;\n                let avg_rate = megabits / total_time;\n\n                NaOption::new(Some(avg_rate))\n            },\n\n            _ => NaOption::<f64>::new(None),\n        }\n    }\n\n    pub fn calculate_upload_download_rate(&mut self) {\n        match self.request_actor {\n            RequestActor::Client => {\n                self.avg_upload_rate = Self::maybe_megabits_per_second(\n                    self.time_first_data_tx,\n                    self.time_last_data_tx,\n                    self.client_transferred_bytes.inner,\n                );\n\n                self.avg_download_rate_d2d = Self::maybe_megabits_per_second(\n                    self.time_first_data_rx,\n                    self.time_last_data_rx,\n                    self.server_transferred_bytes.inner,\n                );\n\n                self.avg_download_rate_h2d = Self::maybe_megabits_per_second(\n                    self.time_first_headers_tx,\n                    self.time_last_data_rx,\n                    self.server_transferred_bytes.inner,\n                );\n            },\n\n            RequestActor::Server => {\n                self.avg_upload_rate = Self::maybe_megabits_per_second(\n                    self.time_first_data_rx,\n                    self.time_last_data_rx,\n                    self.client_transferred_bytes.inner,\n                );\n\n                self.avg_download_rate_d2d = Self::maybe_megabits_per_second(\n                    self.time_first_data_tx,\n                    self.time_last_data_tx,\n                    self.server_transferred_bytes.inner,\n                );\n            },\n        }\n    }\n\n    fn maybe_time_delta(start: Option<f64>, end: Option<f64>) -> NaOption<f64> {\n        match (start, end) {\n            (Some(start), Some(end)) => NaOption::new(Some(end - start)),\n\n            _ => NaOption::<f64>::new(None),\n        }\n    }\n\n    pub fn calculate_deltas(&mut self) {\n        match self.request_actor {\n            RequestActor::Client => {\n                let discover_tx_hdr = Self::maybe_time_delta(\n                    self.time_discovery,\n                    self.time_first_headers_tx,\n                );\n\n                let tx_hdr_rx_hdr = Self::maybe_time_delta(\n                    self.time_first_headers_tx,\n                    self.time_first_headers_rx,\n                );\n\n                let tx_hdr_rx_first_data = Self::maybe_time_delta(\n                    self.time_first_headers_tx,\n                    self.time_first_data_rx,\n                );\n\n                let tx_hdr_rx_last_data = Self::maybe_time_delta(\n                    self.time_first_headers_tx,\n                    self.time_last_data_rx,\n                );\n\n                let tx_first_data_tx_last_data = Self::maybe_time_delta(\n                    self.time_first_data_tx,\n                    self.time_last_data_tx,\n                );\n\n                let rx_first_data_rx_last_data = Self::maybe_time_delta(\n                    self.time_first_data_rx,\n                    self.time_last_data_rx,\n                );\n\n                let rx_hdr_rx_last_data = Self::maybe_time_delta(\n                    self.time_first_headers_rx,\n                    self.time_last_data_rx,\n                );\n\n                self.at_client_deltas = Some(RequestAtClientDeltas {\n                    discover_tx_hdr,\n                    tx_hdr_rx_hdr,\n                    tx_hdr_rx_first_data,\n                    tx_hdr_rx_last_data,\n                    tx_first_data_tx_last_data,\n                    rx_first_data_rx_last_data,\n                    rx_hdr_rx_last_data,\n                });\n            },\n\n            RequestActor::Server => {\n                let rx_hdr_tx_hdr = match (\n                    self.time_first_headers_tx,\n                    self.time_first_headers_rx,\n                ) {\n                    (Some(end), Some(start)) => NaOption::new(Some(end - start)),\n\n                    _ => NaOption::<f64>::new(None),\n                };\n\n                let rx_hdr_tx_first_data =\n                    match (self.time_first_data_tx, self.time_first_headers_rx) {\n                        (Some(end), Some(start)) =>\n                            NaOption::new(Some(end - start)),\n\n                        _ => NaOption::<f64>::new(None),\n                    };\n\n                let rx_hdr_tx_last_data =\n                    match (self.time_last_data_tx, self.time_first_headers_rx) {\n                        (Some(end), Some(start)) =>\n                            NaOption::new(Some(end - start)),\n\n                        _ => NaOption::<f64>::new(None),\n                    };\n\n                let tx_first_data_tx_last_data =\n                    match (self.time_last_data_tx, self.time_first_data_tx) {\n                        (Some(end), Some(start)) =>\n                            NaOption::new(Some(end - start)),\n\n                        _ => NaOption::<f64>::new(None),\n                    };\n\n                self.at_server_deltas = Some(RequestAtServerDeltas {\n                    rx_hdr_tx_hdr,\n                    rx_hdr_tx_first_data,\n                    rx_hdr_tx_last_data,\n                    tx_first_data_tx_last_data,\n                });\n            },\n        }\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/seriesstore.rs",
    "content": "// Copyright (C) 2022, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\n\nuse crate::datastore::Datastore;\nuse crate::push_interp;\nuse crate::QlogPointRtt;\nuse crate::QlogPointu64;\nuse netlog::h2::H2_DEFAULT_WINDOW_SIZE;\n\nuse qlog::events::quic::QuicFrame;\n\n#[derive(Default)]\npub struct SeriesStore {\n    pub local_cwnd: Vec<QlogPointu64>,\n    pub local_bytes_in_flight: Vec<QlogPointu64>,\n    pub local_ssthresh: Vec<QlogPointu64>,\n    pub local_pacing_rate: Vec<QlogPointu64>,\n    pub local_delivery_rate: Vec<QlogPointu64>,\n    pub local_send_rate: Vec<QlogPointu64>,\n    pub local_ack_rate: Vec<QlogPointu64>,\n\n    pub local_min_rtt: Vec<QlogPointRtt>,\n    pub local_latest_rtt: Vec<QlogPointRtt>,\n    pub local_smoothed_rtt: Vec<QlogPointRtt>,\n\n    pub onertt_packet_created: Vec<QlogPointu64>,\n    pub onertt_packet_sent: Vec<QlogPointu64>,\n    pub onertt_packet_sent_aggregate_count: Vec<QlogPointu64>,\n    pub onertt_packet_lost_hacky: Vec<QlogPointu64>,\n    pub onertt_packet_lost_aggregate_count: Vec<QlogPointu64>,\n    pub onertt_packet_delivered_aggregate_count: Vec<QlogPointu64>,\n\n    pub onertt_packet_received: Vec<QlogPointu64>,\n\n    pub netlog_missing_packets: Vec<f64>,\n\n    // this one is a little different, delta as a function of packet number\n    pub onertt_packet_created_sent_delta: Vec<(u64, f64)>,\n\n    pub sent_max_data: Vec<QlogPointu64>,\n    pub sent_stream_max_data: BTreeMap<u64, Vec<QlogPointu64>>,\n\n    pub received_max_data: Vec<QlogPointu64>,\n    pub received_stream_max_data: BTreeMap<u64, Vec<QlogPointu64>>,\n\n    pub stream_buffer_reads: BTreeMap<u64, Vec<QlogPointu64>>,\n    pub sum_stream_buffer_reads: Vec<QlogPointu64>,\n\n    pub stream_buffer_writes: BTreeMap<u64, Vec<QlogPointu64>>,\n    pub sum_stream_buffer_writes: Vec<QlogPointu64>,\n\n    pub stream_buffer_dropped: BTreeMap<u64, Vec<QlogPointu64>>,\n    pub sum_stream_buffer_dropped: Vec<QlogPointu64>,\n\n    pub sent_stream_frames_series: BTreeMap<u64, Vec<QlogPointu64>>,\n\n    pub received_stream_frames_series: BTreeMap<u64, Vec<QlogPointu64>>,\n\n    pub received_data_frames_series: BTreeMap<u64, Vec<QlogPointu64>>,\n    pub received_data_max: BTreeMap<u64, u64>,\n    pub sent_data_frames_series: BTreeMap<u64, Vec<QlogPointu64>>,\n    pub sent_data_max: BTreeMap<u64, u64>,\n\n    pub h2_send_window_series_balanced: BTreeMap<u32, Vec<(f64, i32)>>,\n    pub h2_send_window_balanced_max: BTreeMap<u32, i32>,\n    pub h2_send_window_series_absolute: BTreeMap<u32, Vec<(f64, u64)>>,\n    pub h2_send_window_absolute_max: BTreeMap<u32, u64>,\n\n    pub netlog_h2_stream_received_connection_cumulative: Vec<QlogPointu64>,\n    pub netlog_quic_stream_received_connection_cumulative: Vec<QlogPointu64>,\n\n    pub netlog_quic_client_side_window_updates: BTreeMap<i64, Vec<(f64, u64)>>,\n\n    pub sum_received_stream_max_data: Vec<QlogPointu64>,\n    pub sum_sent_stream_max_data: Vec<QlogPointu64>,\n\n    pub sent_x_min: f64,\n    pub sent_x_max: f64,\n\n    pub received_x_min: f64,\n    pub received_x_max: f64,\n\n    pub y_max_stream_send_plot: u64,\n    pub y_max_stream_recv_plot: u64,\n    pub y_max_congestion_plot: u64,\n    pub y_max_rtt_plot: f32,\n\n    pub y_max_onertt_pkt_sent_plot: u64,\n    pub y_min_onertt_packet_created_sent_delta: f64,\n    pub y_max_onertt_packet_created_sent_delta: f64,\n\n    pub y_max_onertt_pkt_received_plot: u64,\n\n    pub max_pacing_rate: u64,\n    pub max_delivery_rate: u64,\n    pub max_send_rate: u64,\n    pub max_ack_rate: u64,\n}\n\nimpl SeriesStore {\n    pub fn from_datastore(data_store: &Datastore) -> Self {\n        let mut series_store = SeriesStore::default();\n\n        series_store.populate_series_values(data_store);\n\n        series_store\n    }\n\n    fn update_sent_x_axis_max(&mut self, x: f64) {\n        self.sent_x_max = self.sent_x_max.max(x);\n    }\n\n    fn update_received_x_axis_max(&mut self, x: f64) {\n        self.received_x_max = self.sent_x_max.max(x);\n    }\n\n    fn update_congestion_y_axis_max(&mut self, y: u64) {\n        self.y_max_congestion_plot = self.y_max_congestion_plot.max(y);\n    }\n\n    fn update_stream_send_y_axis_max(&mut self, y: u64) {\n        self.y_max_stream_send_plot = self.y_max_stream_send_plot.max(y);\n    }\n\n    fn update_stream_recv_y_axis_max(&mut self, y: u64) {\n        self.y_max_stream_recv_plot = self.y_max_stream_recv_plot.max(y);\n    }\n\n    fn update_rtt_y_axis_max(&mut self, y: f32) {\n        self.y_max_rtt_plot = self.y_max_rtt_plot.max(y);\n    }\n\n    fn cwnd(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_cwnd {\n            self.update_sent_x_axis_max(point.0);\n            self.update_congestion_y_axis_max(point.1);\n\n            push_interp(&mut self.local_cwnd, *point);\n        }\n    }\n\n    fn bif(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_bytes_in_flight {\n            self.update_sent_x_axis_max(point.0);\n            self.update_congestion_y_axis_max(point.1);\n\n            push_interp(&mut self.local_bytes_in_flight, *point);\n        }\n    }\n\n    fn min_rtt(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_min_rtt {\n            self.update_sent_x_axis_max(point.0);\n            self.update_rtt_y_axis_max(point.1);\n\n            push_interp(&mut self.local_min_rtt, *point);\n        }\n    }\n\n    fn latest_rtt(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_latest_rtt {\n            self.update_sent_x_axis_max(point.0);\n            self.update_rtt_y_axis_max(point.1);\n\n            push_interp(&mut self.local_latest_rtt, *point);\n        }\n    }\n\n    fn pacing_rate(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_pacing_rate {\n            self.update_sent_x_axis_max(point.0);\n            self.max_pacing_rate = self.max_pacing_rate.max(point.1);\n\n            push_interp(&mut self.local_pacing_rate, *point);\n        }\n    }\n\n    fn delivery_rate(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_delivery_rate {\n            self.update_sent_x_axis_max(point.0);\n            self.max_delivery_rate = self.max_delivery_rate.max(point.1);\n\n            push_interp(&mut self.local_delivery_rate, *point);\n        }\n    }\n\n    fn send_rate(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_send_rate {\n            self.update_sent_x_axis_max(point.0);\n            self.max_send_rate = self.max_send_rate.max(point.1);\n\n            push_interp(&mut self.local_send_rate, *point);\n        }\n    }\n\n    fn ack_rate(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_ack_rate {\n            self.update_sent_x_axis_max(point.0);\n            self.max_ack_rate = self.max_ack_rate.max(point.1);\n\n            push_interp(&mut self.local_ack_rate, *point);\n        }\n    }\n\n    fn ssthresh(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_ssthresh {\n            self.update_sent_x_axis_max(point.0);\n            // ssthresh tends to start off large and swamps the y-axis\n\n            push_interp(&mut self.local_ssthresh, *point);\n        }\n    }\n\n    fn smoothed_rtt(&mut self, data_store: &Datastore) {\n        for point in &data_store.local_smoothed_rtt {\n            self.update_sent_x_axis_max(point.0);\n            // smoothed_rtt tends to start off large and swamps the y-axis\n\n            push_interp(&mut self.local_smoothed_rtt, *point);\n        }\n    }\n\n    fn sent_max_data(&mut self, data_store: &Datastore) {\n        for point in &data_store.sent_max_data {\n            self.update_sent_x_axis_max(point.0);\n            self.update_stream_recv_y_axis_max(point.1);\n\n            push_interp(&mut self.sent_max_data, *point);\n        }\n    }\n\n    fn sum_sent_stream_max_data(&mut self, data_store: &Datastore) {\n        for point in &data_store.sent_stream_max_data_tracker.sum_series {\n            self.update_sent_x_axis_max(point.0);\n            self.update_stream_recv_y_axis_max(point.1);\n\n            push_interp(&mut self.sum_sent_stream_max_data, *point);\n        }\n    }\n\n    fn packet_sent(&mut self, data_store: &Datastore) {\n        if let Some(onertt_pkts) =\n            &data_store.packet_sent.get(&crate::PacketType::OneRtt)\n        {\n            // TODO: perhaps better to take these counts when processing recovery\n            // metrics\n            let mut sent_count = 0;\n            let mut delivered_count = 0;\n            let mut lost_count = 0;\n\n            for (pkt_num, pkt_info) in onertt_pkts.iter() {\n                sent_count += 1;\n                push_interp(\n                    &mut self.onertt_packet_created,\n                    (pkt_info.created_time, *pkt_num),\n                );\n                push_interp(\n                    &mut self.onertt_packet_sent_aggregate_count,\n                    (pkt_info.created_time, sent_count),\n                );\n\n                // Hacky way to detect lost packets. We don't have the actual\n                // time the loss happened, so just reuse the packet creation time\n                if pkt_info.acked.is_none() {\n                    self.onertt_packet_lost_hacky\n                        .push((pkt_info.created_time, *pkt_num));\n                    lost_count += 1;\n                    push_interp(\n                        &mut self.onertt_packet_lost_aggregate_count,\n                        (pkt_info.created_time, lost_count),\n                    );\n                } else {\n                    delivered_count += 1;\n                    push_interp(\n                        &mut self.onertt_packet_delivered_aggregate_count,\n                        (pkt_info.created_time, delivered_count),\n                    );\n                }\n\n                self.y_max_onertt_pkt_sent_plot =\n                    std::cmp::max(self.y_max_onertt_pkt_sent_plot, *pkt_num);\n\n                // send_at_time is optional\n                if let Some(packet_sent_at) = pkt_info.send_at_time {\n                    push_interp(\n                        &mut self.onertt_packet_sent,\n                        (packet_sent_at, *pkt_num),\n                    );\n\n                    let delta = packet_sent_at - pkt_info.created_time;\n                    push_interp(\n                        &mut self.onertt_packet_created_sent_delta,\n                        (*pkt_num, delta),\n                    );\n\n                    // std::cmp hates floats, so go old school\n                    if delta > self.y_max_onertt_packet_created_sent_delta {\n                        self.y_max_onertt_packet_created_sent_delta = delta;\n                    }\n\n                    if delta < self.y_min_onertt_packet_created_sent_delta {\n                        self.y_min_onertt_packet_created_sent_delta = delta;\n                    }\n                }\n            }\n        }\n    }\n\n    fn packet_recv(&mut self, data_store: &Datastore) {\n        if let Some(onertt_pkts) =\n            &data_store.packet_received.get(&crate::PacketType::OneRtt)\n        {\n            for (pkt_num, pkt_info) in onertt_pkts.iter() {\n                self.update_received_x_axis_max(pkt_info.created_time);\n                self.y_max_onertt_pkt_received_plot =\n                    self.y_max_onertt_pkt_received_plot.max(*pkt_num);\n\n                self.onertt_packet_received\n                    .push((pkt_info.created_time, *pkt_num));\n            }\n        }\n    }\n\n    fn missing_packets(&mut self, data_store: &Datastore) {\n        // Simple way to try and determine if new missing packets were logged.\n        // TODO: netlog produces an array of missing_packets by packet number.\n        // The array can grow or shrink as QUIC connection evolves. We should\n        // only insert an event when a new unique value is observed.\n        // TODO: rewrite to avoid copies\n        let mut last: Vec<u64> = vec![];\n\n        for (event_time, missing_pkts) in\n            &data_store.netlog_ack_sent_missing_packets_raw\n        {\n            if &last != missing_pkts {\n                last = missing_pkts.clone();\n\n                self.netlog_missing_packets.push(*event_time);\n            }\n        }\n    }\n\n    fn sent_stream_max_data(&mut self, data_store: &Datastore) {\n        for (stream, points) in\n            &data_store.sent_stream_max_data_tracker.per_stream\n        {\n            let mut series_points = vec![];\n\n            for point in points {\n                self.update_sent_x_axis_max(point.0);\n                self.update_stream_recv_y_axis_max(point.1);\n\n                push_interp(&mut series_points, *point);\n            }\n\n            self.sent_stream_max_data.insert(*stream, series_points);\n        }\n    }\n\n    fn received_stream_max_data(&mut self, data_store: &Datastore) {\n        for (stream, points) in\n            &data_store.received_stream_max_data_tracker.per_stream\n        {\n            let mut series_points = vec![];\n\n            for point in points {\n                self.update_sent_x_axis_max(point.0);\n                self.update_stream_send_y_axis_max(point.1);\n\n                push_interp(&mut series_points, *point);\n            }\n\n            self.received_stream_max_data.insert(*stream, series_points);\n        }\n    }\n\n    fn stream_buffer_reads(&mut self, data_store: &Datastore) {\n        for (stream, points) in &data_store.stream_buffer_reads_tracker.per_stream\n        {\n            let mut series_points = vec![];\n\n            for point in points {\n                let y = point.1.offset + point.1.length;\n\n                self.update_sent_x_axis_max(point.0);\n                self.update_stream_recv_y_axis_max(y);\n\n                push_interp(&mut series_points, (point.0, y));\n            }\n\n            self.stream_buffer_reads.insert(*stream, series_points);\n        }\n    }\n\n    fn sum_stream_buffer_reads(&mut self, data_store: &Datastore) {\n        for point in &data_store.stream_buffer_reads_tracker.sum_series {\n            push_interp(&mut self.sum_stream_buffer_reads, *point);\n        }\n    }\n\n    fn stream_buffer_writes(&mut self, data_store: &Datastore) {\n        for (stream, points) in\n            &data_store.stream_buffer_writes_tracker.per_stream\n        {\n            let mut series_points = vec![];\n\n            for point in points {\n                let y = point.1.offset + point.1.length;\n\n                self.update_sent_x_axis_max(point.0);\n                self.update_stream_send_y_axis_max(y);\n\n                push_interp(&mut series_points, (point.0, y));\n            }\n\n            self.stream_buffer_writes.insert(*stream, series_points);\n        }\n    }\n\n    fn sum_stream_buffer_writes(&mut self, data_store: &Datastore) {\n        for point in &data_store.stream_buffer_writes_tracker.sum_series {\n            push_interp(&mut self.sum_stream_buffer_writes, *point);\n        }\n\n        if let Some((_, y)) =\n            data_store.stream_buffer_writes_tracker.sum_series.last()\n        {\n            self.update_stream_send_y_axis_max(*y);\n        }\n    }\n\n    fn stream_buffer_dropped(&mut self, data_store: &Datastore) {\n        for (stream, points) in\n            &data_store.stream_buffer_dropped_tracker.per_stream\n        {\n            let mut series_points = vec![];\n\n            for point in points {\n                let y = point.1.offset + point.1.length;\n\n                self.update_sent_x_axis_max(point.0);\n                self.update_stream_send_y_axis_max(y);\n\n                push_interp(&mut series_points, (point.0, y));\n            }\n\n            self.stream_buffer_dropped.insert(*stream, series_points);\n        }\n    }\n\n    fn sum_stream_buffer_dropped(&mut self, data_store: &Datastore) {\n        for point in &data_store.stream_buffer_dropped_tracker.sum_series {\n            push_interp(&mut self.sum_stream_buffer_dropped, *point);\n        }\n\n        if let Some((_, y)) =\n            data_store.stream_buffer_dropped_tracker.sum_series.last()\n        {\n            self.update_stream_send_y_axis_max(*y);\n        }\n    }\n\n    fn sent_stream_frames(&mut self, data_store: &Datastore) {\n        for (stream, points) in &data_store.sent_stream_frames {\n            let mut series_points = vec![];\n\n            for point in points {\n                if let (_, QuicFrame::Stream { offset, raw, .. }) = point {\n                    let offset = offset.unwrap_or_default();\n                    let length = raw\n                        .clone()\n                        .unwrap_or_default()\n                        .payload_length\n                        .unwrap_or_default();\n                    let y = offset + length;\n\n                    self.update_sent_x_axis_max(point.0);\n                    self.update_stream_send_y_axis_max(y);\n\n                    series_points.push((point.0, y));\n                }\n            }\n\n            self.sent_stream_frames_series\n                .insert(*stream, series_points);\n        }\n    }\n\n    fn received_stream_frames(&mut self, data_store: &Datastore) {\n        for (stream, points) in &data_store.received_stream_frames {\n            let mut series_points = vec![];\n\n            for point in points {\n                let y = point.1.offset + point.1.length;\n\n                self.received_x_max = self.received_x_max.max(point.0);\n                self.update_stream_recv_y_axis_max(y);\n\n                series_points.push((point.0, y));\n            }\n\n            self.received_stream_frames_series\n                .insert(*stream, series_points);\n        }\n    }\n\n    fn received_data_frames(&mut self, data_store: &Datastore) {\n        for (stream, points) in &data_store.received_data_frames {\n            let mut series_points = vec![];\n\n            // insert a dummy 0'th point at the first incidence time\n            if let Some(first) = points.first() {\n                series_points.push((first.0, 0));\n            } else {\n                continue;\n            }\n\n            let mut last_y = series_points.first().unwrap().1;\n\n            for point in points {\n                let new_y = last_y + point.1;\n\n                self.received_x_max = self.received_x_max.max(point.0);\n                self.update_stream_recv_y_axis_max(new_y);\n\n                series_points.push((point.0, new_y));\n\n                last_y = new_y;\n            }\n\n            self.received_data_max\n                .insert(*stream, series_points.last().unwrap().1);\n\n            self.received_data_frames_series\n                .insert(*stream, series_points);\n        }\n    }\n\n    fn sent_data_frames(&mut self, data_store: &Datastore) {\n        for (stream, points) in &data_store.sent_data_frames {\n            let mut series_points = vec![];\n\n            // insert a dummy 0'th point at the first incidence time\n            if let Some(first) = points.first() {\n                series_points.push((first.0, 0));\n            } else {\n                continue;\n            }\n\n            let mut last_y = series_points.first().unwrap().1;\n\n            for point in points {\n                let new_y = last_y + point.1;\n\n                self.update_sent_x_axis_max(point.0);\n                series_points.push((point.0, new_y));\n\n                series_points.push((point.0, new_y));\n\n                last_y = new_y;\n            }\n\n            self.sent_data_max\n                .insert(*stream, series_points.last().unwrap().1);\n\n            self.sent_data_frames_series.insert(*stream, series_points);\n        }\n    }\n\n    // TODO: hack. Initial window size can theoretically vary through\n    // the connection, which makes calculations hard. Practically,\n    // nobody does that, so just assume the initial window was either\n    // indicated in settings or omitted in order to use defaults.\n    fn initial_h2_fc_value(stream_id: u32, data_store: &Datastore) -> u32 {\n        if stream_id == 0 {\n            // https://www.rfc-editor.org/rfc/rfc9113.html#section-6.9.2\n            // Connection-level flow control is 65,535 octets\n            H2_DEFAULT_WINDOW_SIZE\n        } else {\n            data_store\n                .h2_server_settings\n                .initial_window_size\n                .unwrap_or(H2_DEFAULT_WINDOW_SIZE)\n        }\n    }\n\n    // TODO: avoid copy-pasta with h2_fc_absolute\n    fn h2_fc_balanced(&mut self, data_store: &Datastore) {\n        for (stream, points) in &data_store.h2_send_window_updates_balanced {\n            let mut series_points = vec![];\n            let mut y_max = 0;\n\n            let initial_window =\n                Self::initial_h2_fc_value(*stream, data_store) as i32;\n\n            // insert a dummy 0'th point at the first incidence time\n            if let Some(first) = points.first() {\n                series_points.push((first.0, initial_window));\n            } else {\n                continue;\n            }\n\n            let mut last_y = series_points.first().unwrap().1;\n\n            for point in points {\n                let new_y = last_y + point.1;\n                self.update_sent_x_axis_max(point.0);\n                y_max = y_max.max(new_y);\n\n                push_interp(&mut series_points, (point.0, new_y));\n                last_y = new_y;\n            }\n\n            self.h2_send_window_series_balanced\n                .insert(*stream, series_points);\n            self.h2_send_window_balanced_max.insert(*stream, y_max);\n        }\n    }\n\n    fn h2_fc_absolute(&mut self, data_store: &Datastore) {\n        for (stream, points) in &data_store.h2_send_window_updates_absolute {\n            let mut series_points = vec![];\n            let mut y_max = 0;\n\n            let initial_window =\n                Self::initial_h2_fc_value(*stream, data_store) as u64;\n\n            // insert a dummy 0'th point at the first incidence time\n            if let Some(first) = points.first() {\n                series_points.push((first.0, initial_window));\n            } else {\n                continue;\n            }\n\n            let mut last_y = series_points.first().unwrap().1;\n\n            for point in points {\n                let new_y = last_y + point.1;\n                self.update_sent_x_axis_max(point.0);\n                y_max = y_max.max(new_y);\n\n                push_interp(&mut series_points, (point.0, new_y));\n\n                last_y = new_y;\n            }\n\n            self.h2_send_window_series_absolute\n                .insert(*stream, series_points);\n            self.h2_send_window_absolute_max.insert(*stream, y_max);\n        }\n    }\n\n    fn netlog_quic_client_side_window_updates(&mut self, data_store: &Datastore) {\n        for (stream, points) in &data_store.netlog_quic_client_side_window_updates\n        {\n            let s = self\n                .netlog_quic_client_side_window_updates\n                .entry(*stream)\n                .or_default();\n\n            for point in points {\n                push_interp(s, *point);\n            }\n        }\n    }\n\n    fn netlog_h2_stream_received_connection_cumulative(\n        &mut self, data_store: &Datastore,\n    ) {\n        for point in &data_store.netlog_h2_stream_received_connection_cumulative {\n            push_interp(\n                &mut self.netlog_h2_stream_received_connection_cumulative,\n                *point,\n            );\n        }\n    }\n\n    fn netlog_quic_stream_received_connection_cumulative(\n        &mut self, data_store: &Datastore,\n    ) {\n        for point in &data_store.netlog_quic_stream_received_connection_cumulative\n        {\n            push_interp(\n                &mut self.netlog_quic_stream_received_connection_cumulative,\n                *point,\n            );\n        }\n    }\n\n    fn received_max_data(&mut self, data_store: &Datastore) {\n        for point in &data_store.received_max_data {\n            push_interp(&mut self.received_max_data, *point);\n        }\n    }\n\n    fn sum_received_stream_max_data(&mut self, data_store: &Datastore) {\n        for point in &data_store.received_stream_max_data_tracker.sum_series {\n            push_interp(&mut self.sum_received_stream_max_data, *point);\n        }\n    }\n\n    fn populate_series_values(&mut self, data_store: &Datastore) {\n        self.cwnd(data_store);\n        self.bif(data_store);\n        self.min_rtt(data_store);\n        self.latest_rtt(data_store);\n        self.pacing_rate(data_store);\n        self.delivery_rate(data_store);\n        self.send_rate(data_store);\n        self.ack_rate(data_store);\n        self.ssthresh(data_store);\n        self.smoothed_rtt(data_store);\n\n        self.sent_max_data(data_store);\n        self.sum_sent_stream_max_data(data_store);\n\n        self.packet_sent(data_store);\n        self.packet_recv(data_store);\n        self.missing_packets(data_store);\n\n        self.sent_stream_max_data(data_store);\n        self.received_stream_max_data(data_store);\n\n        self.stream_buffer_reads(data_store);\n        self.sum_stream_buffer_reads(data_store);\n\n        self.stream_buffer_writes(data_store);\n        self.sum_stream_buffer_writes(data_store);\n\n        self.stream_buffer_dropped(data_store);\n        self.sum_stream_buffer_dropped(data_store);\n\n        self.sent_stream_frames(data_store);\n        self.received_stream_frames(data_store);\n\n        self.sent_data_frames(data_store);\n        self.received_data_frames(data_store);\n\n        self.h2_fc_balanced(data_store);\n        self.h2_fc_absolute(data_store);\n\n        self.netlog_h2_stream_received_connection_cumulative(data_store);\n        self.netlog_quic_stream_received_connection_cumulative(data_store);\n        self.netlog_quic_client_side_window_updates(data_store);\n\n        self.received_max_data(data_store);\n        self.sum_received_stream_max_data(data_store);\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/trackers/mod.rs",
    "content": "// Copyright (C) 2026, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\npub mod stream_buffer_tracker;\npub mod stream_max_tracker;\n\npub use stream_buffer_tracker::StreamBufferTracker;\npub use stream_max_tracker::StreamMaxTracker;\n"
  },
  {
    "path": "qlog-dancer/src/trackers/stream_buffer_tracker.rs",
    "content": "// Copyright (C) 2026, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\n\nuse crate::datastore::StreamAccess;\n\n/// Tracks monotonically increasing stream buffer positions with efficient sum\n/// calculation. Handles out-of-order events by taking max end position (offset\n/// + length) seen per stream.\n#[derive(Debug, Default)]\npub struct StreamBufferTracker {\n    /// Full time series per stream with StreamAccess details.\n    pub per_stream: BTreeMap<u64, Vec<(f64, StreamAccess)>>,\n\n    /// Current maximum end position (offset + length) per stream.\n    pub flat: BTreeMap<u64, u64>,\n\n    /// Cumulative sum time series.\n    pub sum_series: Vec<(f64, u64)>,\n\n    /// Running sum for O(1) updates.\n    running_sum: u64,\n}\n\nimpl StreamBufferTracker {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Updates stream buffer position, returns Some((old_max, new_max)) if\n    /// changed.\n    pub fn update(\n        &mut self, stream_id: u64, access: StreamAccess, ev_time: f64,\n    ) -> Option<(u64, u64)> {\n        self.per_stream\n            .entry(stream_id)\n            .or_default()\n            .push((ev_time, access.clone()));\n\n        let new_end = access.offset + access.length;\n        let old_max = self.flat.get(&stream_id).copied().unwrap_or(0);\n        let new_max = old_max.max(new_end);\n\n        let result = if new_max > old_max {\n            self.flat.insert(stream_id, new_max);\n            self.running_sum += new_max - old_max;\n            Some((old_max, new_max))\n        } else {\n            None\n        };\n\n        self.sum_series.push((ev_time, self.running_sum));\n\n        result\n    }\n\n    pub fn get_stream_max(&self, stream_id: u64) -> Option<u64> {\n        self.flat.get(&stream_id).copied()\n    }\n\n    pub fn current_sum(&self) -> u64 {\n        self.running_sum\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_tracker_empty() {\n        let tracker = StreamBufferTracker::new();\n        assert_eq!(tracker.current_sum(), 0);\n        assert_eq!(tracker.get_stream_max(0), None);\n        assert!(tracker.sum_series.is_empty());\n        assert!(tracker.flat.is_empty());\n    }\n\n    #[test]\n    fn test_tracker_single_stream_in_order() {\n        let mut tracker = StreamBufferTracker::new();\n\n        let result = tracker.update(\n            0,\n            StreamAccess {\n                offset: 0,\n                length: 1000,\n            },\n            10.0,\n        );\n        assert_eq!(result, Some((0, 1000)));\n        assert_eq!(tracker.get_stream_max(0), Some(1000));\n        assert_eq!(tracker.current_sum(), 1000);\n        assert_eq!(tracker.sum_series, vec![(10.0, 1000)]);\n\n        let result = tracker.update(\n            0,\n            StreamAccess {\n                offset: 1000,\n                length: 1000,\n            },\n            20.0,\n        );\n        assert_eq!(result, Some((1000, 2000)));\n        assert_eq!(tracker.get_stream_max(0), Some(2000));\n        assert_eq!(tracker.current_sum(), 2000);\n        assert_eq!(tracker.sum_series, vec![(10.0, 1000), (20.0, 2000)]);\n    }\n\n    #[test]\n    fn test_tracker_out_of_order_events() {\n        let mut tracker = StreamBufferTracker::new();\n\n        // Larger end position arrives first at t=20.\n        let result = tracker.update(\n            0,\n            StreamAccess {\n                offset: 1000,\n                length: 1000,\n            },\n            20.0,\n        );\n        assert_eq!(result, Some((0, 2000)));\n        assert_eq!(tracker.get_stream_max(0), Some(2000));\n        assert_eq!(tracker.current_sum(), 2000);\n\n        // Smaller end position arrives later at t=10 - should NOT update.\n        let result = tracker.update(\n            0,\n            StreamAccess {\n                offset: 0,\n                length: 1000,\n            },\n            10.0,\n        );\n        assert_eq!(result, None);\n        assert_eq!(tracker.get_stream_max(0), Some(2000));\n        assert_eq!(tracker.current_sum(), 2000);\n\n        assert_eq!(tracker.sum_series, vec![(20.0, 2000), (10.0, 2000)]);\n\n        // Later, larger position arrives.\n        let result = tracker.update(\n            0,\n            StreamAccess {\n                offset: 2000,\n                length: 1000,\n            },\n            30.0,\n        );\n        assert_eq!(result, Some((2000, 3000)));\n        assert_eq!(tracker.get_stream_max(0), Some(3000));\n        assert_eq!(tracker.current_sum(), 3000);\n    }\n\n    #[test]\n    fn test_tracker_multiple_streams() {\n        let mut tracker = StreamBufferTracker::new();\n\n        tracker.update(\n            0,\n            StreamAccess {\n                offset: 0,\n                length: 1000,\n            },\n            10.0,\n        );\n        assert_eq!(tracker.current_sum(), 1000);\n\n        tracker.update(\n            1,\n            StreamAccess {\n                offset: 0,\n                length: 500,\n            },\n            15.0,\n        );\n        assert_eq!(tracker.current_sum(), 1500);\n\n        tracker.update(\n            0,\n            StreamAccess {\n                offset: 1000,\n                length: 500,\n            },\n            20.0,\n        );\n        assert_eq!(tracker.current_sum(), 2000);\n\n        tracker.update(\n            2,\n            StreamAccess {\n                offset: 0,\n                length: 300,\n            },\n            25.0,\n        );\n        assert_eq!(tracker.current_sum(), 2300);\n\n        assert_eq!(tracker.get_stream_max(0), Some(1500));\n        assert_eq!(tracker.get_stream_max(1), Some(500));\n        assert_eq!(tracker.get_stream_max(2), Some(300));\n        assert_eq!(tracker.get_stream_max(999), None);\n    }\n\n    #[test]\n    fn test_tracker_duplicate_values() {\n        let mut tracker = StreamBufferTracker::new();\n\n        let result = tracker.update(\n            0,\n            StreamAccess {\n                offset: 0,\n                length: 1000,\n            },\n            10.0,\n        );\n        assert_eq!(result, Some((0, 1000)));\n\n        let result = tracker.update(\n            0,\n            StreamAccess {\n                offset: 0,\n                length: 1000,\n            },\n            20.0,\n        );\n        assert_eq!(result, None);\n\n        assert_eq!(tracker.get_stream_max(0), Some(1000));\n        assert_eq!(tracker.current_sum(), 1000);\n\n        assert_eq!(tracker.sum_series.len(), 2);\n        assert_eq!(tracker.sum_series, vec![(10.0, 1000), (20.0, 1000)]);\n    }\n\n    #[test]\n    fn test_tracker_running_sum_correctness() {\n        let mut tracker = StreamBufferTracker::new();\n\n        tracker.update(\n            0,\n            StreamAccess {\n                offset: 0,\n                length: 1000,\n            },\n            1.0,\n        );\n        tracker.update(\n            1,\n            StreamAccess {\n                offset: 0,\n                length: 2000,\n            },\n            2.0,\n        );\n        tracker.update(\n            2,\n            StreamAccess {\n                offset: 0,\n                length: 1500,\n            },\n            3.0,\n        );\n        tracker.update(\n            0,\n            StreamAccess {\n                offset: 1000,\n                length: 200,\n            },\n            4.0,\n        );\n\n        let manual_sum: u64 = tracker.flat.values().sum();\n\n        assert_eq!(tracker.current_sum(), manual_sum);\n        assert_eq!(tracker.current_sum(), 4700);\n    }\n\n    #[test]\n    fn test_tracker_preserves_stream_access() {\n        let mut tracker = StreamBufferTracker::new();\n\n        tracker.update(\n            0,\n            StreamAccess {\n                offset: 100,\n                length: 500,\n            },\n            10.0,\n        );\n        tracker.update(\n            0,\n            StreamAccess {\n                offset: 600,\n                length: 300,\n            },\n            20.0,\n        );\n\n        let stream_data = tracker.per_stream.get(&0).unwrap();\n        assert_eq!(stream_data.len(), 2);\n        assert_eq!(stream_data[0].1.offset, 100);\n        assert_eq!(stream_data[0].1.length, 500);\n        assert_eq!(stream_data[1].1.offset, 600);\n        assert_eq!(stream_data[1].1.length, 300);\n\n        assert_eq!(tracker.get_stream_max(0), Some(900));\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/trackers/stream_max_tracker.rs",
    "content": "// Copyright (C) 2026, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\n\n/// Tracks monotonically increasing per-stream values with efficient sum\n/// calculation. Handles out-of-order events by taking max value seen per\n/// stream.\n#[derive(Debug, Default)]\npub struct StreamMaxTracker {\n    /// Full time series per stream.\n    pub per_stream: BTreeMap<u64, Vec<(f64, u64)>>,\n\n    /// Current maximum per stream.\n    pub flat: BTreeMap<u64, u64>,\n\n    /// Cumulative sum time series.\n    pub sum_series: Vec<(f64, u64)>,\n\n    /// Running sum for O(1) updates.\n    running_sum: u64,\n}\n\nimpl StreamMaxTracker {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    /// Updates stream value, returns Some((old_max, new_max)) if changed.\n    pub fn update(\n        &mut self, stream_id: u64, new_value: u64, ev_time: f64, init_val: u64,\n    ) -> Option<(u64, u64)> {\n        let entry = self\n            .per_stream\n            .entry(stream_id)\n            .or_insert_with(|| vec![(0.0, init_val)]);\n        entry.push((ev_time, new_value));\n\n        let old_max = self.flat.get(&stream_id).copied().unwrap_or(0);\n        let new_max = old_max.max(new_value);\n\n        let result = if new_max > old_max {\n            self.flat.insert(stream_id, new_max);\n            self.running_sum += new_max - old_max;\n            Some((old_max, new_max))\n        } else {\n            None\n        };\n\n        self.sum_series.push((ev_time, self.running_sum));\n\n        result\n    }\n\n    pub fn get_stream_max(&self, stream_id: u64) -> Option<u64> {\n        self.flat.get(&stream_id).copied()\n    }\n\n    pub fn current_sum(&self) -> u64 {\n        self.running_sum\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_tracker_empty() {\n        let tracker = StreamMaxTracker::new();\n        assert_eq!(tracker.current_sum(), 0);\n        assert_eq!(tracker.get_stream_max(0), None);\n        assert!(tracker.sum_series.is_empty());\n        assert!(tracker.flat.is_empty());\n    }\n\n    #[test]\n    fn test_tracker_single_stream_in_order() {\n        let mut tracker = StreamMaxTracker::new();\n\n        let result = tracker.update(0, 1000, 10.0, 0);\n        assert_eq!(result, Some((0, 1000)));\n        assert_eq!(tracker.get_stream_max(0), Some(1000));\n        assert_eq!(tracker.current_sum(), 1000);\n        assert_eq!(tracker.sum_series, vec![(10.0, 1000)]);\n\n        let result = tracker.update(0, 2000, 20.0, 0);\n        assert_eq!(result, Some((1000, 2000)));\n        assert_eq!(tracker.get_stream_max(0), Some(2000));\n        assert_eq!(tracker.current_sum(), 2000);\n        assert_eq!(tracker.sum_series, vec![(10.0, 1000), (20.0, 2000)]);\n    }\n\n    #[test]\n    fn test_tracker_out_of_order_events() {\n        let mut tracker = StreamMaxTracker::new();\n\n        let result = tracker.update(0, 2000, 20.0, 0);\n        assert_eq!(result, Some((0, 2000)));\n        assert_eq!(tracker.get_stream_max(0), Some(2000));\n        assert_eq!(tracker.current_sum(), 2000);\n\n        let result = tracker.update(0, 1000, 10.0, 0);\n        assert_eq!(result, None);\n        assert_eq!(tracker.get_stream_max(0), Some(2000));\n        assert_eq!(tracker.current_sum(), 2000);\n\n        assert_eq!(tracker.sum_series, vec![(20.0, 2000), (10.0, 2000)]);\n\n        let result = tracker.update(0, 3000, 30.0, 0);\n        assert_eq!(result, Some((2000, 3000)));\n        assert_eq!(tracker.get_stream_max(0), Some(3000));\n        assert_eq!(tracker.current_sum(), 3000);\n    }\n\n    #[test]\n    fn test_tracker_multiple_streams() {\n        let mut tracker = StreamMaxTracker::new();\n\n        tracker.update(0, 1000, 10.0, 0);\n        assert_eq!(tracker.current_sum(), 1000);\n\n        tracker.update(1, 500, 15.0, 0);\n        assert_eq!(tracker.current_sum(), 1500);\n\n        tracker.update(0, 1500, 20.0, 0);\n        assert_eq!(tracker.current_sum(), 2000);\n\n        tracker.update(2, 300, 25.0, 0);\n        assert_eq!(tracker.current_sum(), 2300);\n\n        assert_eq!(tracker.get_stream_max(0), Some(1500));\n        assert_eq!(tracker.get_stream_max(1), Some(500));\n        assert_eq!(tracker.get_stream_max(2), Some(300));\n        assert_eq!(tracker.get_stream_max(999), None);\n    }\n\n    #[test]\n    fn test_tracker_duplicate_values() {\n        let mut tracker = StreamMaxTracker::new();\n\n        let result = tracker.update(0, 1000, 10.0, 0);\n        assert_eq!(result, Some((0, 1000)));\n\n        let result = tracker.update(0, 1000, 20.0, 0);\n        assert_eq!(result, None);\n\n        assert_eq!(tracker.get_stream_max(0), Some(1000));\n        assert_eq!(tracker.current_sum(), 1000);\n\n        assert_eq!(tracker.sum_series.len(), 2);\n        assert_eq!(tracker.sum_series, vec![(10.0, 1000), (20.0, 1000)]);\n    }\n\n    #[test]\n    fn test_tracker_running_sum_correctness() {\n        let mut tracker = StreamMaxTracker::new();\n\n        tracker.update(0, 1000, 1.0, 0);\n        tracker.update(1, 2000, 2.0, 0);\n        tracker.update(2, 1500, 3.0, 0);\n        tracker.update(0, 1200, 4.0, 0);\n\n        let manual_sum: u64 = tracker.flat.values().sum();\n\n        assert_eq!(tracker.current_sum(), manual_sum);\n        assert_eq!(tracker.current_sum(), 4700);\n    }\n\n    #[test]\n    fn test_tracker_complex_interleaving() {\n        let mut tracker = StreamMaxTracker::new();\n\n        let updates = vec![\n            (0, 1000, 10.0),\n            (1, 500, 12.0),\n            (0, 800, 8.0),\n            (2, 2000, 15.0),\n            (1, 1000, 18.0),\n            (0, 1500, 20.0),\n        ];\n\n        for (stream_id, value, time) in updates {\n            tracker.update(stream_id, value, time, 0);\n        }\n\n        assert_eq!(tracker.get_stream_max(0), Some(1500));\n        assert_eq!(tracker.get_stream_max(1), Some(1000));\n        assert_eq!(tracker.get_stream_max(2), Some(2000));\n        assert_eq!(tracker.current_sum(), 4500);\n\n        assert_eq!(tracker.sum_series.len(), 6);\n    }\n\n    #[test]\n    fn test_tracker_init_value() {\n        let mut tracker = StreamMaxTracker::new();\n\n        tracker.update(0, 1000, 10.0, 500);\n\n        let stream_data = tracker.per_stream.get(&0).unwrap();\n        assert_eq!(stream_data.len(), 2);\n        assert_eq!(stream_data[0], (0.0, 500));\n        assert_eq!(stream_data[1], (10.0, 1000));\n\n        tracker.update(0, 1500, 20.0, 500);\n        let stream_data = tracker.per_stream.get(&0).unwrap();\n        assert_eq!(stream_data.len(), 3);\n        assert_eq!(stream_data[0], (0.0, 500));\n        assert_eq!(stream_data[1], (10.0, 1000));\n        assert_eq!(stream_data[2], (20.0, 1500));\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/web.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::config::AppConfig;\nuse crate::plots::congestion_control::plot_cc_plot;\nuse crate::plots::conn_flow_control::plot_conn_flow_control_canvas;\nuse crate::plots::conn_overview::plot_main_plot;\nuse crate::plots::packet_received::plot_packet_received;\nuse crate::plots::packet_sent::plot_packet_sent_delta_plot_canvas;\nuse crate::plots::packet_sent::plot_packet_sent_lost_delivered_count_plot;\nuse crate::plots::packet_sent::plot_packet_sent_pacing_rate_plot_canvas;\nuse crate::plots::packet_sent::plot_packet_sent_plot_canvas;\nuse crate::plots::pending::plot_pending;\nuse crate::plots::pending::PendingPlotParams;\nuse crate::plots::rtt::plot_rtt_plot;\nuse crate::plots::stream_multiplex::plot_stream_multiplexing;\nuse crate::plots::stream_multiplex::MultiplexPlotsParams;\nuse crate::plots::stream_sparks::plot_sparks;\nuse crate::plots::stream_sparks::SparkPlotsParams;\nuse crate::plots::AreaMargin;\nuse crate::plots::ChartMargin;\nuse crate::plots::ChartOutputType;\nuse crate::plots::ChartSize;\nuse crate::plots::ClampParams;\nuse crate::plots::PlotParameters;\nuse crate::reports::html::event_list_html_from_sqlog;\n\nuse crate::datastore::Datastore;\nuse crate::datastore::VantagePoint;\n\nuse plotters::chart::ChartContext;\nuse plotters::coord::types::RangedCoordf32;\nuse plotters::coord::types::RangedCoordu64;\nuse plotters::prelude::Cartesian2d;\nuse plotters_canvas::CanvasBackend;\nuse wasm_bindgen::prelude::*;\n\nuse crate::seriesstore::SeriesStore;\n\nuse serde::Deserialize;\nuse serde::Serialize;\n\nuse web_sys::Document;\nuse web_sys::Element;\nuse web_sys::Window;\n\nconst AREA_MARGIN: AreaMargin = AreaMargin { x: 50, y: 100 };\nconst CHART_MARGIN: ChartMargin = ChartMargin {\n    top: 20,\n    bottom: 20,\n    left: 20,\n    right: 20,\n};\nconst CHART_MARGIN_TIGHT: ChartMargin = ChartMargin {\n    top: 10,\n    bottom: 2,\n    left: 5,\n    right: 5,\n};\n\n#[wasm_bindgen]\npub struct Pointu64 {\n    pub x: f32,\n    pub y: u64,\n}\n\n#[derive(Clone, Copy, Serialize, Deserialize)]\npub struct ChartBounds {\n    pub left: i32,\n    pub top: i32,\n    pub width: i32,\n    pub height: i32,\n}\n\n#[derive(Clone, Copy, Serialize, Deserialize)]\npub struct PlotRanges<T, U> {\n    pub x_min: T,\n    pub x_max: T,\n    pub y_min: U,\n    pub y_max: U,\n}\n\n#[derive(Clone, Copy, Serialize, Deserialize)]\npub struct ChartInfo<T, U> {\n    pub chart_bounds: ChartBounds,\n    pub plot_ranges: PlotRanges<T, U>,\n}\n\nimpl<T, U> ChartInfo<T, U> {\n    pub fn with_f32_u64(\n        chart: &ChartContext<\n            CanvasBackend,\n            Cartesian2d<RangedCoordf32, RangedCoordu64>,\n        >,\n    ) -> ChartInfo<f32, u64> {\n        let plotting_area = chart.plotting_area();\n        let pixel_range = plotting_area.get_pixel_range();\n\n        let chart_bounds = ChartBounds {\n            left: pixel_range.0.start,\n            top: pixel_range.1.start,\n            width: pixel_range.0.end - pixel_range.0.start,\n            height: pixel_range.1.end - pixel_range.1.start,\n        };\n\n        let plot_ranges = PlotRanges {\n            x_min: chart.x_range().start,\n            x_max: chart.x_range().end,\n            y_min: chart.y_range().start,\n            y_max: chart.y_range().end,\n        };\n\n        ChartInfo {\n            chart_bounds,\n            plot_ranges,\n        }\n    }\n\n    pub fn with_f32_f32(\n        chart: &ChartContext<\n            CanvasBackend,\n            Cartesian2d<RangedCoordf32, RangedCoordf32>,\n        >,\n    ) -> ChartInfo<f32, f32> {\n        let plotting_area = chart.plotting_area();\n        let pixel_range = plotting_area.get_pixel_range();\n\n        let chart_bounds = ChartBounds {\n            left: pixel_range.0.start,\n            top: pixel_range.1.start,\n            width: pixel_range.0.end - pixel_range.0.start,\n            height: pixel_range.1.end - pixel_range.1.start,\n        };\n\n        let plot_ranges = PlotRanges {\n            x_min: chart.x_range().start,\n            x_max: chart.x_range().end,\n            y_min: chart.y_range().start,\n            y_max: chart.y_range().end,\n        };\n\n        ChartInfo {\n            chart_bounds,\n            plot_ranges,\n        }\n    }\n\n    pub fn with_u64_f32(\n        chart: &ChartContext<\n            CanvasBackend,\n            Cartesian2d<RangedCoordu64, RangedCoordf32>,\n        >,\n    ) -> ChartInfo<u64, f32> {\n        let plotting_area = chart.plotting_area();\n        let pixel_range = plotting_area.get_pixel_range();\n\n        let chart_bounds = ChartBounds {\n            left: pixel_range.0.start,\n            top: pixel_range.1.start,\n            width: pixel_range.0.end - pixel_range.0.start,\n            height: pixel_range.1.end - pixel_range.1.start,\n        };\n\n        let plot_ranges = PlotRanges {\n            x_min: chart.x_range().start,\n            x_max: chart.x_range().end,\n            y_min: chart.y_range().start,\n            y_max: chart.y_range().end,\n        };\n\n        ChartInfo {\n            chart_bounds,\n            plot_ranges,\n        }\n    }\n}\n\nimpl<T, U> Into<JsValue> for ChartInfo<T, U>\nwhere\n    T: serde::Serialize,\n    U: serde::Serialize,\n{\n    fn into(self) -> JsValue {\n        serde_wasm_bindgen::to_value(&(self.chart_bounds, self.plot_ranges))\n            .unwrap()\n    }\n}\n\n#[wasm_bindgen]\n/// Creates a QlogDancerWeb object to interact with logs.\n///\n/// The `display_name` parameter is reflected into drawn plots.\npub fn new_qlog_dancer(display_name: &str) -> QlogDancerWeb {\n    let ds = Datastore {\n        total_sent_stream_frame_count: 0,\n        vantage_point: VantagePoint::Server,\n        ..Default::default()\n    };\n\n    QlogDancerWeb {\n        ds,\n        ss: None,\n        display_name: display_name.into(),\n        partial: vec![],\n        log_info: None,\n        qlog_events: vec![],\n\n        mp_chart_info: None,\n        cc_chart_info: None,\n        rtt_chart_info: None,\n        fc_chart_info: None,\n        pkt_rx_chart_info: None,\n        pkt_tx_chart_info: None,\n        pkt_tx_counts_chart_info: None,\n        pkt_tx_delta_chart_info: None,\n        pkt_tx_pacing_chart_info: None,\n    }\n}\n\n#[wasm_bindgen]\npub struct QlogDancerWeb {\n    display_name: String,\n    ds: Datastore,\n    ss: Option<SeriesStore>,\n    partial: Vec<u8>,\n    log_info: Option<Vec<u8>>,\n    qlog_events: Vec<qlog::reader::Event>,\n\n    mp_chart_info: Option<ChartInfo<f32, u64>>,\n    cc_chart_info: Option<ChartInfo<f32, u64>>,\n    rtt_chart_info: Option<ChartInfo<f32, f32>>,\n    fc_chart_info: Option<ChartInfo<f32, u64>>,\n    pkt_rx_chart_info: Option<ChartInfo<f32, u64>>,\n    pkt_tx_chart_info: Option<ChartInfo<f32, u64>>,\n    pkt_tx_counts_chart_info: Option<ChartInfo<f32, u64>>,\n    pkt_tx_delta_chart_info: Option<ChartInfo<u64, f32>>,\n    pkt_tx_pacing_chart_info: Option<ChartInfo<f32, u64>>,\n}\n\n#[wasm_bindgen]\nimpl QlogDancerWeb {\n    #[wasm_bindgen]\n    pub fn process_chunk(&mut self, chunk: &[u8]) {\n        let mut s = chunk.split(|f| *f == b'\u001e');\n\n        if self.log_info.is_none() {\n            // null value, ignore it\n            s.next();\n\n            if let Some(info) = s.next() {\n                self.log_info = Some(info.to_vec());\n            }\n        }\n\n        for obj in s {\n            if !self.partial.is_empty() {\n                self.partial.extend_from_slice(obj);\n\n                // ignore result as we'll give up either way.\n                // TODO: clone needed to appease borrow checker\n                let _ = self.try_read_event(&self.partial.clone());\n\n                self.partial.clear();\n            } else {\n                // Try to parse the bytes we have. This might fail because\n                // the event is unknown, or because the reader only gave us\n                // a partial string. Always try to recover from partial\n                // strings by storing it and retrying later when we have\n                // more data available.\n                if self.try_read_event(obj).is_err() && !obj.ends_with(b\"}}\") {\n                    self.partial = obj.to_vec();\n                };\n            }\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Consume a ReadableStream as if it were a sqlog file.\n    /// TODO: support other log formats\n    pub async fn read_stream(&mut self, readable: web_sys::ReadableStream) {\n        use futures_util::StreamExt;\n\n        let mut a =\n            wasm_streams::ReadableStream::from_raw(readable).into_stream();\n\n        while let Some(Ok(chunk)) = a.next().await {\n            let b = js_sys::Uint8Array::new(&chunk).to_vec();\n\n            self.process_chunk(&b);\n        }\n    }\n\n    pub fn try_read_event(&mut self, buf: &[u8]) -> Result<(), String> {\n        let r: serde_json::Result<qlog::events::Event> =\n            serde_json::from_slice(buf);\n\n        if let Ok(event) = r {\n            self.qlog_events.push(qlog::reader::Event::Qlog(event));\n            return Ok(());\n        }\n\n        let r: serde_json::Result<qlog::events::JsonEvent> =\n            serde_json::from_slice(buf);\n\n        if let Ok(event) = r {\n            self.qlog_events.push(qlog::reader::Event::Json(event));\n            return Ok(());\n        }\n\n        Err(\"not read\".to_string())\n    }\n\n    #[wasm_bindgen]\n    /// Returns the total count of packets sent in a parsed file.\n    ///\n    /// A simple function to demonstrate how to access basic information from\n    /// parsed log files.\n    pub fn total_packets_sent(&self) -> usize {\n        let mut ret = 0;\n        for v in self.ds.packet_sent.values() {\n            ret += v.len();\n        }\n\n        ret\n    }\n\n    #[wasm_bindgen]\n    pub fn populate_datastore(&mut self) {\n        for event in &self.qlog_events {\n            match event {\n                qlog::reader::Event::Qlog(ev) => {\n                    self.ds.consume_qlog_event(ev, true);\n                },\n\n                qlog::reader::Event::Json(_) => {\n                    // This will be handled elsewhere\n                },\n            }\n        }\n\n        // Always finish off the datastore regardless of readable stream type.\n        self.ds.hydrate_http_requests();\n        self.ds.finalize();\n    }\n\n    #[wasm_bindgen]\n    /// Run through the internal datastore to prepare data for plotting\n    ///\n    /// Some use cases don't need plots, so you can avoid calling this function\n    /// if that's the case.\n    pub fn populate_seriesstore(&mut self) {\n        self.ss = Some(SeriesStore::from_datastore(&self.ds));\n    }\n\n    #[wasm_bindgen]\n    pub fn get_chart_info(&self, canvas_id: &str) -> JsValue {\n        match canvas_id {\n            \"overview_canvas\" =>\n                self.mp_chart_info.expect(\"canvas exists\").into(),\n            \"rtt_canvas\" => self.rtt_chart_info.expect(\"canvas exists\").into(),\n            \"cc_canvas\" => self.cc_chart_info.expect(\"canvas exists\").into(),\n            \"flow_control_canvas\" =>\n                self.fc_chart_info.expect(\"canvas exists\").into(),\n            \"pkt-rx-canvas\" =>\n                self.pkt_rx_chart_info.expect(\"canvas exists\").into(),\n            \"pkt-tx-canvas\" =>\n                self.pkt_tx_chart_info.expect(\"canvas exists\").into(),\n            \"pkt-tx-counts-canvas\" =>\n                self.pkt_tx_chart_info.expect(\"canvas exists\").into(),\n            \"pkt-tx-delta-canvas\" =>\n                self.pkt_tx_delta_chart_info.expect(\"canvas exists\").into(),\n            \"pkt-tx-pacing-canvas\" =>\n                self.pkt_tx_pacing_chart_info.expect(\"canvas exists\").into(),\n            _ => JsValue::null(),\n        }\n    }\n\n    #[wasm_bindgen]\n    #[cfg(target_arch = \"wasm32\")]\n    /// Draws the multi-panel connection overview plot into the provided\n    /// canvas_id.\n    pub fn draw_connection_overview(\n        &mut self, mp_canvas_id: &str, display_legend: bool,\n        x_start: Option<f32>, x_end: Option<f32>,\n    ) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: x_start,\n                    end: x_end,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN,\n                area_margin: AREA_MARGIN,\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart =\n                plot_main_plot(&params, &self.display_name, ss, &mp_canvas_id);\n\n            self.mp_chart_info =\n                Some(ChartInfo::<f32, u64>::with_f32_u64(&chart));\n        }\n    }\n\n    #[wasm_bindgen]\n    #[cfg(target_arch = \"wasm32\")]\n    /// Draws the congestion plot into the provided canvas_id.\n    pub fn draw_cc_plot(\n        &mut self, cc_canvas_id: &str, display_legend: bool,\n        x_start: Option<f32>, x_end: Option<f32>,\n    ) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: x_start,\n                    end: x_end,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN,\n                area_margin: AREA_MARGIN,\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart = plot_cc_plot(&params, ss, &self.ds, &cc_canvas_id);\n\n            self.cc_chart_info =\n                Some(ChartInfo::<f32, u64>::with_f32_u64(&chart));\n        }\n    }\n\n    #[wasm_bindgen]\n    #[cfg(target_arch = \"wasm32\")]\n    /// Draws the rtt plot into the provided canvas_id.\n    pub fn draw_rtt_plot(\n        &mut self, rtt_canvas_id: &str, display_legend: bool,\n        x_start: Option<f32>, x_end: Option<f32>,\n    ) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: x_start,\n                    end: x_end,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN,\n                area_margin: AREA_MARGIN,\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart = plot_rtt_plot(&params, ss, &rtt_canvas_id);\n            self.rtt_chart_info =\n                Some(ChartInfo::<f32, f32>::with_f32_f32(&chart));\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the flow control plot into the provided canvas_id.\n    pub fn draw_flow_control(&mut self, canvas_id: &str, display_legend: bool) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: None,\n                    end: None,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN,\n                area_margin: AreaMargin { x: 40, y: 80 },\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart_type = ChartOutputType::Canvas {\n                canvas_id: canvas_id.to_string(),\n            };\n\n            let chart = plot_conn_flow_control_canvas(\n                &params,\n                &self.display_name,\n                ss,\n                &self.ds,\n                &chart_type,\n            );\n            self.fc_chart_info =\n                Some(ChartInfo::<f32, f32>::with_f32_u64(&chart));\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the stream sparks plot into the provided canvas_id.\n    pub fn draw_sparks(\n        &mut self, abs_dl_canvas_id: &str, abs_ul_canvas_id: &str,\n        rel_dl_canvas_id: &str, rel_ul_canvas_id: &str,\n    ) {\n        if let Some(ss) = &self.ss {\n            let spark_params = SparkPlotsParams {\n                clamp: ClampParams {\n                    start: None,\n                    end: None,\n                    stream_y_max: None,\n                },\n                colors: AppConfig::colors(false),\n                sparks_per_row: 1,\n                captions_on_top: false,\n                spark_dimension_x: 600,\n                caption_area_width: 600,\n                label_area_height: 1,\n                ..Default::default()\n            };\n\n            plot_sparks(\n                &spark_params,\n                &self.display_name,\n                ss,\n                &self.ds,\n                &ChartOutputType::Canvas {\n                    canvas_id: abs_dl_canvas_id.to_string(),\n                },\n                &ChartOutputType::Canvas {\n                    canvas_id: rel_dl_canvas_id.to_string(),\n                },\n                &ChartOutputType::Canvas {\n                    canvas_id: abs_ul_canvas_id.to_string(),\n                },\n                &ChartOutputType::Canvas {\n                    canvas_id: rel_ul_canvas_id.to_string(),\n                },\n            );\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the packet received plot into the provided canvas_id.\n    pub fn draw_packet_received(\n        &mut self, canvas_id: &str, display_legend: bool,\n    ) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: None,\n                    end: None,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN,\n                area_margin: AreaMargin { x: 40, y: 80 },\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart_type = ChartOutputType::Canvas {\n                canvas_id: canvas_id.to_string(),\n            };\n\n            plot_packet_received(\n                &params,\n                &self.display_name,\n                ss,\n                &self.ds,\n                &chart_type,\n            );\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the stream pending plot into the provided canvas_id.\n    pub fn draw_pending(&mut self, canvas_id: &str) {\n        if let Some(ss) = &self.ss {\n            let pending_params = PendingPlotParams {\n                clamp: ClampParams {\n                    start: None,\n                    end: None,\n                    stream_y_max: None,\n                },\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 600,\n                },\n                colors: AppConfig::colors(false),\n                display_chart_title: false,\n            };\n\n            let chart_type = ChartOutputType::Canvas {\n                canvas_id: canvas_id.to_string(),\n            };\n\n            plot_pending(\n                &pending_params,\n                &self.display_name,\n                ss,\n                &self.ds,\n                &chart_type,\n            );\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the packet sent plot into the provided canvas_id.\n    pub fn draw_packet_sent_plot(\n        &mut self, pkt_tx_canvas_id: &str, display_legend: bool,\n        x_start: Option<f32>, x_end: Option<f32>,\n    ) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: x_start,\n                    end: x_end,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN_TIGHT,\n                area_margin: AreaMargin { x: 40, y: 80 },\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart = plot_packet_sent_plot_canvas(\n                &params,\n                &self.display_name,\n                ss,\n                pkt_tx_canvas_id,\n            );\n\n            self.pkt_tx_chart_info =\n                Some(ChartInfo::<f32, u64>::with_f32_u64(&chart));\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the packet sent plot into the provided canvas_id.\n    pub fn draw_packet_sent_lost_delivered_count_plot(\n        &mut self, canvas_id: &str, display_legend: bool, x_start: Option<f32>,\n        x_end: Option<f32>,\n    ) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: x_start,\n                    end: x_end,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN_TIGHT,\n                area_margin: AreaMargin { x: 40, y: 80 },\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart = plot_packet_sent_lost_delivered_count_plot(\n                &params, ss, canvas_id,\n            );\n\n            self.pkt_tx_counts_chart_info =\n                Some(ChartInfo::<f32, u64>::with_f32_u64(&chart));\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the packet sent plot into the provided canvas_id.\n    pub fn draw_packet_sent_delta_plot(\n        &mut self, pkt_tx_delta_canvas_id: &str, display_legend: bool,\n        x_start: Option<f32>, x_end: Option<f32>,\n    ) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: x_start,\n                    end: x_end,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN_TIGHT,\n                area_margin: AreaMargin { x: 40, y: 5 },\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart = plot_packet_sent_delta_plot_canvas(\n                &params,\n                ss,\n                pkt_tx_delta_canvas_id,\n            );\n\n            self.pkt_tx_delta_chart_info =\n                Some(ChartInfo::<f32, u64>::with_u64_f32(&chart));\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the packet sent plot into the provided canvas_id.\n    pub fn draw_packet_sent_pacing_plot(\n        &mut self, pkt_tx_pacing_canvas_id: &str, display_legend: bool,\n        x_start: Option<f32>, x_end: Option<f32>,\n    ) {\n        if let Some(ss) = &self.ss {\n            let params = PlotParameters {\n                clamp: ClampParams {\n                    start: x_start,\n                    end: x_end,\n                    stream_y_max: None,\n                },\n                cwnd_y_max: None,\n                chart_size: ChartSize {\n                    width: 1042,\n                    height: 800,\n                },\n                colors: AppConfig::colors(false),\n                chart_margin: CHART_MARGIN_TIGHT,\n                area_margin: AreaMargin { x: 40, y: 80 },\n                display_chart_title: false,\n                display_legend,\n                display_minor_lines: false,\n            };\n\n            let chart = plot_packet_sent_pacing_rate_plot_canvas(\n                &params,\n                ss,\n                &self.ds,\n                pkt_tx_pacing_canvas_id,\n            );\n\n            self.pkt_tx_pacing_chart_info =\n                Some(ChartInfo::<f32, u64>::with_f32_u64(&chart));\n        }\n    }\n\n    #[wasm_bindgen]\n    /// Draws the stream multiplexing plot into the provided canvas_id.\n    pub fn draw_stream_multiplexing(&mut self, canvas_id: &str) {\n        if let Some(ss) = &self.ss {\n            let multiplex_params = MultiplexPlotsParams {\n                clamp: ClampParams {\n                    start: None,\n                    end: None,\n                    stream_y_max: None,\n                },\n                colors: AppConfig::colors(false),\n                ..Default::default()\n            };\n\n            let chart_type = ChartOutputType::Canvas {\n                canvas_id: canvas_id.to_string(),\n            };\n\n            plot_stream_multiplexing(\n                &multiplex_params,\n                &self.display_name,\n                ss,\n                &self.ds,\n                &chart_type,\n            );\n        }\n    }\n\n    #[wasm_bindgen]\n    pub fn populate_event_table(&self, event_div_id: &str) {\n        let window: Window = web_sys::window().unwrap();\n        let document: Document = window.document().unwrap();\n\n        let target_div: Element =\n            document.get_element_by_id(event_div_id).unwrap();\n\n        let event_table = event_list_html_from_sqlog(&self.qlog_events);\n        target_div.set_inner_html(&event_table);\n    }\n\n    #[wasm_bindgen]\n    /// TODO: example of accessing richer data from a parsed log\n    pub fn packet_sent(&self) -> Vec<Pointu64> {\n        let v = vec![Pointu64 { x: 0.1, y: 1 }];\n        v\n    }\n}\n"
  },
  {
    "path": "qlog-dancer/src/wirefilter.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse qlog::events::quic::QuicFrame;\nuse qlog::events::EventData;\nuse qlog::reader::Event;\nuse std::iter::FromIterator;\nuse std::vec;\nuse wirefilter::ExecutionContext;\nuse wirefilter::Scheme;\nuse wirefilter::TypedArray;\n\nuse crate::category_and_type_from_event;\n\nfn stream_ids(event: &Event) -> TypedArray<'_, i64> {\n    let mut ids: TypedArray<i64> = TypedArray::new();\n\n    match event {\n        Event::Qlog(event) => match &event.data {\n            EventData::QuicStreamDataMoved(v) =>\n                if let Some(id) = v.stream_id {\n                    ids.push(id as i64);\n                },\n            EventData::QuicPacketSent(v) => {\n                if let Some(frames) = &v.frames {\n                    for frame in frames {\n                        match frame {\n                            QuicFrame::ResetStream { stream_id, .. } =>\n                                ids.push(*stream_id as i64),\n                            QuicFrame::StopSending { stream_id, .. } =>\n                                ids.push(*stream_id as i64),\n                            QuicFrame::Stream { stream_id, .. } =>\n                                ids.push(*stream_id as i64),\n                            QuicFrame::MaxStreamData { stream_id, .. } =>\n                                ids.push(*stream_id as i64),\n                            QuicFrame::StreamDataBlocked {\n                                stream_id, ..\n                            } => ids.push(*stream_id as i64),\n\n                            // other frames are not related to streams\n                            _ => (),\n                        }\n                    }\n                }\n            },\n            EventData::Http3StreamTypeSet(v) => {\n                ids.push(v.stream_id as i64);\n            },\n            EventData::Http3FrameCreated(v) => {\n                ids.push(v.stream_id as i64);\n            },\n            EventData::Http3FrameParsed(v) => {\n                ids.push(v.stream_id as i64);\n            },\n\n            // other events are not related to streams\n            _ => (),\n        },\n\n        // TODO: try and fuzzy extract stream id\n        Event::Json(_event) => {},\n    }\n\n    ids\n}\n\npub fn filter_sqlog_events(mut events: Vec<Event>, filter: &str) -> Vec<Event> {\n    let mut ret = vec![];\n\n    let mut builder = Scheme! {\n        category: Bytes,\n        name: Bytes,\n        stream_id: Array(Int),\n    };\n\n    builder\n        .add_function(\"any\", wirefilter::AnyFunction {})\n        .unwrap();\n\n    let scheme = builder.build();\n    let ast = scheme.parse(filter).unwrap();\n    let filter = ast.compile();\n\n    // TODO: smarter filtering rather then drain / recreate\n    for event in events.drain(..) {\n        // Recreate context each time to appease borrow checker\n        let mut ctx = ExecutionContext::new(&scheme);\n\n        let filter_match = match &event {\n            Event::Qlog(ev) => {\n                let (cat, ty) = category_and_type_from_event(&ev);\n\n                ctx.set_field_value(\n                    scheme.get_field(\"category\").unwrap(),\n                    cat.clone(),\n                )\n                .unwrap();\n                ctx.set_field_value(\n                    scheme.get_field(\"name\").unwrap(),\n                    ty.clone(),\n                )\n                .unwrap();\n\n                ctx.set_field_value(\n                    scheme.get_field(\"stream_id\").unwrap(),\n                    stream_ids(&event),\n                )\n                .unwrap();\n\n                filter.execute(&ctx).unwrap()\n            },\n            Event::Json(ev) => {\n                let (cat, ty) = category_and_type_from_event(&ev);\n                ctx.set_field_value(\n                    scheme.get_field(\"category\").unwrap(),\n                    cat.clone(),\n                )\n                .unwrap();\n                ctx.set_field_value(\n                    scheme.get_field(\"name\").unwrap(),\n                    ty.clone(),\n                )\n                .unwrap();\n\n                ctx.set_field_value(\n                    scheme.get_field(\"stream_id\").unwrap(),\n                    stream_ids(&event),\n                )\n                .unwrap();\n                filter.execute(&ctx).unwrap()\n            },\n        };\n\n        if filter_match {\n            ret.push(event);\n        }\n    }\n\n    ret\n}\n\n#[cfg(test)]\nmod tests {\n    use qlog::events::quic::PacketHeader;\n    use qlog::events::quic::PacketSent;\n    use qlog::events::quic::PacketType::Initial;\n    use qlog::events::quic::QuicFrame;\n    use qlog::events::EventData::QuicPacketSent;\n    use qlog::events::RawInfo;\n    use qlog::reader::Event;\n    use smallvec::smallvec;\n\n    use crate::wirefilter::filter_sqlog_events;\n\n    fn stream_frame(stream_id: u64) -> QuicFrame {\n        QuicFrame::Stream {\n            stream_id,\n            offset: Some(0),\n            fin: Some(true),\n            raw: Some(RawInfo {\n                length: None,\n                payload_length: Some(10),\n                data: None,\n            }),\n        }\n    }\n\n    // Events aren't clonable in the version of qlog we have, so lazy solution for\n    // now\n    fn events() -> Vec<Event> {\n        let mut events = vec![];\n        let scid = [0x7e, 0x37, 0xe4, 0xdc, 0xc6, 0x68, 0x2d, 0xa8];\n        let dcid = [0x36, 0xce, 0x10, 0x4e, 0xee, 0x50, 0x10, 0x1c];\n        let pkt_hdr = PacketHeader::new(\n            Initial,\n            Some(0),\n            None,\n            None,\n            Some(1),\n            Some(&scid),\n            Some(&dcid),\n        );\n        let raw = RawInfo {\n            length: None,\n            payload_length: Some(0),\n            data: None,\n        };\n\n        let frames = vec![\n            QuicFrame::Crypto {\n                offset: 0,\n                raw: Some(raw),\n            },\n            stream_frame(1),\n            stream_frame(2),\n            stream_frame(3),\n            stream_frame(4),\n            stream_frame(5),\n        ];\n\n        let raw = RawInfo {\n            length: Some(1251),\n            payload_length: Some(1224),\n            data: None,\n        };\n\n        let event_data = QuicPacketSent(PacketSent {\n            header: pkt_hdr.clone(),\n            frames: Some(frames.into()),\n            stateless_reset_token: None,\n            supported_versions: None,\n            raw: Some(raw.clone()),\n            datagram_id: None,\n            is_mtu_probe_packet: None,\n            send_at_time: None,\n            trigger: None,\n        });\n\n        events.push(Event::Qlog(qlog::events::Event::with_time(0.0, event_data)));\n\n        let frames = vec![\n            stream_frame(0),\n            stream_frame(100),\n            stream_frame(200),\n            stream_frame(300),\n            stream_frame(400),\n        ];\n\n        let event_data = QuicPacketSent(PacketSent {\n            header: pkt_hdr.clone(),\n            frames: Some(frames.into()),\n            stateless_reset_token: None,\n            supported_versions: None,\n            raw: Some(raw.clone()),\n            datagram_id: None,\n            is_mtu_probe_packet: None,\n            send_at_time: None,\n            trigger: None,\n        });\n\n        events.push(Event::Qlog(qlog::events::Event::with_time(0.0, event_data)));\n\n        let frames = vec![\n            stream_frame(1),\n            stream_frame(100),\n            stream_frame(2),\n            stream_frame(200),\n        ];\n\n        let event_data = QuicPacketSent(PacketSent {\n            header: pkt_hdr,\n            frames: Some(frames.into()),\n            stateless_reset_token: None,\n            supported_versions: None,\n            raw: Some(raw),\n            datagram_id: None,\n            is_mtu_probe_packet: None,\n            send_at_time: None,\n            trigger: None,\n        });\n\n        events.push(Event::Qlog(qlog::events::Event::with_time(0.0, event_data)));\n\n        events\n    }\n\n    #[test]\n    fn test_stream_id_filter_no_match() {\n        let events = events();\n        assert_eq!(events.len(), 3);\n\n        let filter = \"any(stream_id[*]==13)\";\n        let filtered_events = filter_sqlog_events(events, filter);\n        assert!(filtered_events.is_empty());\n    }\n\n    #[test]\n    fn test_stream_id_filter_stream0() {\n        let events = events();\n        assert_eq!(events.len(), 3);\n\n        let filter = \"any(stream_id[*]==0)\";\n        let filtered_events = filter_sqlog_events(events, filter);\n        assert_eq!(filtered_events.len(), 1);\n\n        let ev = &filtered_events[0];\n        match ev {\n            Event::Qlog(event) => {\n                // assert_eq!\n                match &event.data {\n                    QuicPacketSent(packet_sent) => {\n                        assert_eq!(\n                            packet_sent.frames,\n                            Some(smallvec![\n                                stream_frame(0),\n                                stream_frame(100),\n                                stream_frame(200),\n                                stream_frame(300),\n                                stream_frame(400),\n                            ])\n                        );\n                    },\n                    _ => panic!(\"unexpected event data\"),\n                }\n            },\n            Event::Json(_json_event) => panic!(\"unexpected type\"),\n        }\n    }\n\n    #[test]\n    fn test_stream_id_filter_stream1() {\n        let events = events();\n        assert_eq!(events.len(), 3);\n\n        let filter = \"any(stream_id[*]==1)\";\n        let filtered_events = filter_sqlog_events(events, filter);\n        assert_eq!(filtered_events.len(), 2);\n\n        let ev = &filtered_events[0];\n\n        let raw = RawInfo {\n            length: None,\n            payload_length: Some(0),\n            data: None,\n        };\n\n        match ev {\n            Event::Qlog(event) => match &event.data {\n                QuicPacketSent(packet_sent) => {\n                    assert_eq!(\n                        packet_sent.frames,\n                        Some(smallvec![\n                            QuicFrame::Crypto {\n                                offset: 0,\n                                raw: Some(raw),\n                            },\n                            stream_frame(1),\n                            stream_frame(2),\n                            stream_frame(3),\n                            stream_frame(4),\n                            stream_frame(5),\n                        ])\n                    );\n                },\n                _ => panic!(\"unexpected event data\"),\n            },\n            Event::Json(_json_event) => panic!(\"unexpected type\"),\n        }\n\n        let ev = &filtered_events[1];\n        match ev {\n            Event::Qlog(event) => {\n                // assert_eq!\n                match &event.data {\n                    QuicPacketSent(packet_sent) => {\n                        assert_eq!(\n                            packet_sent.frames,\n                            Some(smallvec![\n                                stream_frame(1),\n                                stream_frame(100),\n                                stream_frame(2),\n                                stream_frame(200),\n                            ])\n                        );\n                    },\n                    _ => panic!(\"unexpected event data\"),\n                }\n            },\n            Event::Json(_json_event) => panic!(\"unexpected type\"),\n        }\n    }\n\n    #[test]\n    fn test_stream_id_filter_stream0_and_stream3() {\n        let events = events();\n        assert_eq!(events.len(), 3);\n\n        let filter = \"any(stream_id[*]==0) || any(stream_id[*]==3)\";\n        let filtered_events = filter_sqlog_events(events, filter);\n        assert_eq!(filtered_events.len(), 2);\n\n        let raw = RawInfo {\n            length: None,\n            payload_length: Some(0),\n            data: None,\n        };\n\n        let ev = &filtered_events[0];\n        match ev {\n            Event::Qlog(event) => {\n                // assert_eq!\n                match &event.data {\n                    QuicPacketSent(packet_sent) => {\n                        assert_eq!(\n                            packet_sent.frames,\n                            Some(smallvec![\n                                QuicFrame::Crypto {\n                                    offset: 0,\n                                    raw: Some(raw),\n                                },\n                                stream_frame(1),\n                                stream_frame(2),\n                                stream_frame(3),\n                                stream_frame(4),\n                                stream_frame(5),\n                            ])\n                        );\n                    },\n                    _ => panic!(\"unexpected event data\"),\n                }\n            },\n            Event::Json(_json_event) => panic!(\"unexpected type\"),\n        }\n\n        let ev = &filtered_events[1];\n        match ev {\n            Event::Qlog(event) => {\n                // assert_eq!\n                match &event.data {\n                    QuicPacketSent(packet_sent) => {\n                        assert_eq!(\n                            packet_sent.frames,\n                            Some(smallvec![\n                                stream_frame(0),\n                                stream_frame(100),\n                                stream_frame(200),\n                                stream_frame(300),\n                                stream_frame(400),\n                            ])\n                        );\n                    },\n                    _ => panic!(\"unexpected event data\"),\n                }\n            },\n            Event::Json(_json_event) => panic!(\"unexpected type\"),\n        }\n    }\n}\n"
  },
  {
    "path": "quiche/AGENTS.md",
    "content": "# quiche/ — Core QUIC + HTTP/3 Library\n\n## OVERVIEW\n\nLow-level QUIC transport and HTTP/3 in Rust. App provides IO/timers; this crate handles protocol state. Also exposes C FFI via `staticlib`/`cdylib`.\n\n## STRUCTURE\n\n```\nsrc/\n  lib.rs          (9k lines) Connection struct, Config, connect()/accept() entry points\n  h3/\n    mod.rs        (7.5k)     HTTP/3 connection — own Error/Result types, NOT quiche::Error\n    qpack/                   QPACK header compression\n  recovery/\n    mod.rs                   Recovery enum, RecoveryOps trait (enum_dispatch)\n    congestion/              Legacy CC (Cubic, Reno, Hystart++)\n    gcongestion/             Google-derived CC (BBR2) — behind `gcongestion` feature\n  stream/                    Stream state machine, flow control per-stream\n  tls/                       TLS backend abstraction (BoringSSL / OpenSSL)\n  crypto/                    Packet protection, key derivation\n  packet.rs       (2.3k)     Packet parsing, ConnectionId, Header\n  frame.rs                   QUIC frame encode/decode\n  path.rs                    Multi-path state, PathEvent, migration\n  pmtud.rs                   Path MTU discovery\n  cid.rs                     Connection ID management\n  ffi.rs          (2.3k)     C FFI — behind `ffi` feature\n  transport_params.rs        QUIC transport parameter encode/decode\n  flowcontrol.rs             Connection-level flow control\n  ranges.rs                  ACK range tracking\n  range_buf.rs               BufFactory/BufSplit traits for zero-copy buffer creation\n  dgram.rs                   DATAGRAM frame support\n  rand.rs                    Random number generation\n  minmax.rs                  Windowed min/max filter\n  test_utils.rs              Pipe struct for in-memory QUIC pairs (pub via `internal` feature)\n  tests.rs        (12k)      Integration tests\n  build.rs                   BoringSSL cmake build (NOTE: lives in src/, not crate root)\ninclude/\n  quiche.h        (1.2k)     C API header — mirrors ffi.rs\ndeps/\n  boringssl/                 Git submodule\n```\n\n## WHERE TO LOOK\n\n| Task | Start here |\n|------|-----------|\n| Connection lifecycle | `lib.rs` — `Connection` struct, `recv()`, `send()` |\n| HTTP/3 streams/headers | `h3/mod.rs` — `h3::Connection` |\n| Loss detection / CC | `recovery/mod.rs` → `congestion/` or `gcongestion/` |\n| Packet parse/serialize | `packet.rs`, `frame.rs` |\n| TLS handshake | `tls/mod.rs` — cfg-gated per backend |\n| C bindings | `ffi.rs` + `include/quiche.h` |\n| Test harness | `test_utils.rs` (`Pipe` struct) |\n| Build system | `src/build.rs` — BoringSSL cmake, cross-compile params |\n\n## ANTI-PATTERNS\n\n- **h3::Error != quiche::Error** — don't mix or convert carelessly; they have different variant sets.\n- **`Error::Done` is a success signal** in many read/write loops — not a failure.\n- **Don't add new CC impls** outside `recovery/` — two parallel impls (congestion + gcongestion) already exist.\n- **`unsafe` only at FFI boundaries** — `tls/`, `crypto/`, `ffi.rs`; don't add elsewhere.\n- **`#[cfg(feature = \"fuzzing\")]`** disables real crypto — never accidentally gate non-test code on it.\n\n## NOTES\n\n- `build.rs` is at `src/build.rs` (Cargo.toml: `build = \"src/build.rs\"`), not crate root.\n- Three TLS backends: `boringssl-vendored` (default), `boringssl-boring-crate`, `openssl` — mutually exclusive features.\n- `quiche::Error` is `Copy + Clone` — intentional for hot-path ergonomics.\n- `test_utils::Pipe` exposed via `internal` feature for downstream crate integration tests.\n- Tests use `rstest` with `#[values(\"cubic\", \"bbr2_gcongestion\")]` parameterization for CC coverage.\n- `QUICHE_BSSL_PATH` env var skips vendored BoringSSL build.\n- Crate-type: `lib` + `staticlib` + `cdylib` — the latter two for C consumers.\n- `BufFactory` trait (`range_buf.rs`) enables zero-copy buffer creation; `Connection<F>` is generic over it.\n"
  },
  {
    "path": "quiche/Cargo.toml",
    "content": "[package]\nname = \"quiche\"\nversion = \"0.26.1\"\ndescription = \"🥧 Savoury implementation of the QUIC transport protocol and HTTP/3\"\nrepository = { workspace = true }\nauthors = [\"Alessandro Ghedini <alessandro@ghedini.me>\"]\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\nreadme = { workspace = true }\nbuild = \"src/build.rs\"\ninclude = [\n  \"/*.md\",\n  \"/*.toml\",\n  \"/COPYING\",\n  \"/deps/boringssl/**/*.[chS]\",\n  \"/deps/boringssl/**/*.asm\",\n  \"/deps/boringssl/src/**/*.cc\",\n  \"/deps/boringssl/**/CMakeLists.txt\",\n  \"/deps/boringssl/**/sources.cmake\",\n  \"/deps/boringssl/LICENSE\",\n  \"/examples\",\n  \"/include\",\n  \"/quiche.svg\",\n  \"/src\",\n]\nrust-version = \"1.85\"\n\n[features]\ndefault = [\"boringssl-vendored\"]\n\n# Allow client connections to provide a custom DCID when initiating a\n# connection. Be aware that RFC 9000 places requirements for unpredictability and\n# length on the client DCID field. Enabling this feature can be dangerous if these\n# requirements are not satisfied.\ncustom-client-dcid = []\n\n# Build the vendored BoringSSL library.\nboringssl-vendored = []\n\n# Use the BoringSSL library provided by the boring crate.\nboringssl-boring-crate = [\"boring\", \"foreign-types-shared\"]\n\n# Build quiche against OpenSSL instead of BoringSSL.\nopenssl = [\"pkg-config\"]\n\n# Generate pkg-config metadata file for libquiche.\npkg-config-meta = []\n\n# Replaces quiche's original congestion control\n# implementation with one adapted from google/quiche.\ngcongestion = []\n\n# Equivalent to \"--cfg fuzzing\", but can also be checked in build.rs.\nfuzzing = []\n\n# Build and expose the FFI API.\nffi = [\"dep:cdylib-link-lines\"]\n\n# Exposes internal APIs that have no stability guarantees across versions.\ninternal = []\n\n# Enable qlog support with serde_json for extension data\nqlog = [\"dep:qlog\", \"dep:serde\", \"dep:serde_json\", \"dep:serde_with\"]\n\n[package.metadata.release]\ntag-prefix = \"\"\n\n[package.metadata.docs.rs]\nno-default-features = true\nfeatures = [\"boringssl-boring-crate\", \"qlog\", \"custom-client-dcid\"]\nrustdoc-args = [\"--cfg\", \"docsrs\"]\n\n[build-dependencies]\ncmake = \"0.1\"\npkg-config = { version = \"0.3\", optional = true }\ncdylib-link-lines = { version = \"0.1\", optional = true }\n\n[dependencies]\nboring = { workspace = true, optional = true }\neither = { version = \"1.8\", default-features = false }\nforeign-types-shared = { version = \"0.3.0\", optional = true }\nintrusive-collections = \"0.9.5\"\nlibc = { workspace = true }\nlibm = \"0.2\"\nlog = { workspace = true, features = [\"std\"] }\noctets = { workspace = true, features = [\"huffman_hpack\"] }\nqlog = { workspace = true, optional = true }\nserde = { workspace = true, optional = true }\nserde_json = { version = \"1.0\", optional = true }\nserde_with = { workspace = true, features = [\"macros\"], optional = true }\nsfv = { version = \"0.9\", optional = true }\nslab = \"0.4\"\nsmallvec = { workspace = true, features = [\"union\"] }\nenum_dispatch = \"0.3\"\n\n[target.\"cfg(windows)\".dependencies]\nwindows-sys = { version = \"0.59\", features = [\n  \"Win32_Networking_WinSock\",\n  \"Win32_Security_Cryptography\",\n] }\n\n[dev-dependencies]\nmio = { workspace = true, features = [\"net\", \"os-poll\"] }\nring = { workspace = true }\nrstest = { workspace = true }\nurl = { workspace = true }\n\n[lib]\ncrate-type = [\"lib\", \"staticlib\", \"cdylib\"]\n"
  },
  {
    "path": "quiche/examples/Makefile",
    "content": "OS := $(shell uname)\n\nSOURCE_DIR = ../src\nBUILD_DIR = $(CURDIR)/build\nLIB_DIR = $(BUILD_DIR)/debug\nINCLUDE_DIR = ../include\n\nINCS = -I$(INCLUDE_DIR)\nCFLAGS = -I. -Wall -Werror -pedantic -fsanitize=address -g\n\nLIBCRYPTO_DIR = $(dir $(shell find ${BUILD_DIR} -name libcrypto.a))\nLIBSSL_DIR = $(dir $(shell find ${BUILD_DIR} -name libssl.a))\n\nLDFLAGS = -L$(LIBCRYPTO_DIR) -L$(LIBSSL_DIR) -L$(LIB_DIR)\n\nifeq ($(OS), Darwin)\n# Default prefix of Apple Silicon macOS homebrew.\n# Intel will use /usr/local which is included by default.\nBREW_INC_DIR = /opt/homebrew/include/\nBREW_LIB_DIR = /opt/homebrew/lib/\n\nCFLAGS += -framework Security -I$(BREW_INC_DIR)\nLDFLAGS += -L$(BREW_LIB_DIR)\nendif\n\nLIBS = $(LIB_DIR)/libquiche.a -lev -ldl -pthread -lm\n\nall: client server http3-client http3-server\n\nclient: client.c $(INCLUDE_DIR)/quiche.h $(LIB_DIR)/libquiche.a\n\t$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS)\n\nserver: server.c $(INCLUDE_DIR)/quiche.h $(LIB_DIR)/libquiche.a\n\t$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS)\n\nhttp3-client: http3-client.c $(INCLUDE_DIR)/quiche.h $(LIB_DIR)/libquiche.a\n\t$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS)\n\nhttp3-server: http3-server.c $(INCLUDE_DIR)/quiche.h $(LIB_DIR)/libquiche.a\n\t$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(INCS) $(LIBS)\n\n$(LIB_DIR)/libquiche.a: $(shell find $(SOURCE_DIR) -type f -name '*.rs')\n\tcd .. && cargo build --target-dir $(BUILD_DIR) --features ffi\n\nclean:\n\t@$(RM) -rf client server http3-client http3-server build/ *.dSYM/\n"
  },
  {
    "path": "quiche/examples/README.md",
    "content": "How to build C examples\n-----------------------\n\n### Requirements\n\nYou will need the following libraries to build the C examples in this directory.\nYou can use your OS package manager (brew, apt, pkg, ...) or install them from\nsource.\n\n- [libev](http://software.schmorp.de/pkg/libev.html)\n- [uthash](https://troydhanson.github.io/uthash/)\n\n### Build\n\nSimply run `make` in this directory.\n\n```\n% make clean\n% make\n```\n"
  },
  {
    "path": "quiche/examples/cert-big.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC7TCCAdUCFDuGBhl3l5Z++VCLkvaav4yteBonMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwMzIzMTYwNzU0WhcNNDcwODA5MTYw\nNzU0WjAhMQswCQYDVQQGEwJHQjESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5bOL7LD9kiIagcVrZqZ13ZcR0KhMuzs\nbrqULbZKyqC+uBRgINxYJ7LPnJ4LPYuCt/nAaQ7CLXfKgzAMFu8eIK6UEvZA6+7b\n20E4rvOpPbTB/T4JbYZNQKyM9AEwr6j0P6vFgrWT7aBzhkmiqEe5vv/7ZOEGb+Ab\n+cvMeszfBbk93nyzKdNaUuh95x7/p0Ow315np2PRuoT0QQnA9zE/9eZ3Jah3cNZn\nNuQ6BDHlkegzTV5JhYYblRo/pmt2E9E0ha+NWsRLf3ZJUYhkYR3UqMltEKuLglCO\nVWBbPmKd4IZUNIotpKMVQSVb9agNBF49hH9iBhN3fBm7Hp8KBpjJLwIDAQABMA0G\nCSqGSIb3DQEBCwUAA4IBAQCo/Rn4spa5XFk0cCoKypP27DxePkGD9rQZk/CY4inV\nJV16anZ1pr9yfO61+m3fRKTZq7yxtHRDWxDdROHx9LqV1dXLAmh1ecV9Kn6/796O\nEHsOcVB0Lfi9Ili7//oUqlhGNploRuQbgWAXU+Eo1xJRWIXeedhzBSgEOMaQk3Zn\nTdYFhP0/Ao/fEdI4VULv1A43ztnZIB2KXWgUQoFT32woL47eWge8LxxVmmH3STtz\nnNcGnYxIorCQemDHDzMrvxRWgHxkpFGGqAhkFFyCmhKFPglKwt+yVTx26T8tShID\nISMj0rgVMptmtWKJfzNCvFG52gsuO4w3yGdjgjRRrBDm\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIC7TCCAdUCFDuGBhl3l5Z++VCLkvaav4yteBonMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwMzIzMTYwNzU0WhcNNDcwODA5MTYw\nNzU0WjAhMQswCQYDVQQGEwJHQjESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5bOL7LD9kiIagcVrZqZ13ZcR0KhMuzs\nbrqULbZKyqC+uBRgINxYJ7LPnJ4LPYuCt/nAaQ7CLXfKgzAMFu8eIK6UEvZA6+7b\n20E4rvOpPbTB/T4JbYZNQKyM9AEwr6j0P6vFgrWT7aBzhkmiqEe5vv/7ZOEGb+Ab\n+cvMeszfBbk93nyzKdNaUuh95x7/p0Ow315np2PRuoT0QQnA9zE/9eZ3Jah3cNZn\nNuQ6BDHlkegzTV5JhYYblRo/pmt2E9E0ha+NWsRLf3ZJUYhkYR3UqMltEKuLglCO\nVWBbPmKd4IZUNIotpKMVQSVb9agNBF49hH9iBhN3fBm7Hp8KBpjJLwIDAQABMA0G\nCSqGSIb3DQEBCwUAA4IBAQCo/Rn4spa5XFk0cCoKypP27DxePkGD9rQZk/CY4inV\nJV16anZ1pr9yfO61+m3fRKTZq7yxtHRDWxDdROHx9LqV1dXLAmh1ecV9Kn6/796O\nEHsOcVB0Lfi9Ili7//oUqlhGNploRuQbgWAXU+Eo1xJRWIXeedhzBSgEOMaQk3Zn\nTdYFhP0/Ao/fEdI4VULv1A43ztnZIB2KXWgUQoFT32woL47eWge8LxxVmmH3STtz\nnNcGnYxIorCQemDHDzMrvxRWgHxkpFGGqAhkFFyCmhKFPglKwt+yVTx26T8tShID\nISMj0rgVMptmtWKJfzNCvFG52gsuO4w3yGdjgjRRrBDm\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIC7TCCAdUCFDuGBhl3l5Z++VCLkvaav4yteBonMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwMzIzMTYwNzU0WhcNNDcwODA5MTYw\nNzU0WjAhMQswCQYDVQQGEwJHQjESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5bOL7LD9kiIagcVrZqZ13ZcR0KhMuzs\nbrqULbZKyqC+uBRgINxYJ7LPnJ4LPYuCt/nAaQ7CLXfKgzAMFu8eIK6UEvZA6+7b\n20E4rvOpPbTB/T4JbYZNQKyM9AEwr6j0P6vFgrWT7aBzhkmiqEe5vv/7ZOEGb+Ab\n+cvMeszfBbk93nyzKdNaUuh95x7/p0Ow315np2PRuoT0QQnA9zE/9eZ3Jah3cNZn\nNuQ6BDHlkegzTV5JhYYblRo/pmt2E9E0ha+NWsRLf3ZJUYhkYR3UqMltEKuLglCO\nVWBbPmKd4IZUNIotpKMVQSVb9agNBF49hH9iBhN3fBm7Hp8KBpjJLwIDAQABMA0G\nCSqGSIb3DQEBCwUAA4IBAQCo/Rn4spa5XFk0cCoKypP27DxePkGD9rQZk/CY4inV\nJV16anZ1pr9yfO61+m3fRKTZq7yxtHRDWxDdROHx9LqV1dXLAmh1ecV9Kn6/796O\nEHsOcVB0Lfi9Ili7//oUqlhGNploRuQbgWAXU+Eo1xJRWIXeedhzBSgEOMaQk3Zn\nTdYFhP0/Ao/fEdI4VULv1A43ztnZIB2KXWgUQoFT32woL47eWge8LxxVmmH3STtz\nnNcGnYxIorCQemDHDzMrvxRWgHxkpFGGqAhkFFyCmhKFPglKwt+yVTx26T8tShID\nISMj0rgVMptmtWKJfzNCvFG52gsuO4w3yGdjgjRRrBDm\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIC7TCCAdUCFDuGBhl3l5Z++VCLkvaav4yteBonMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwMzIzMTYwNzU0WhcNNDcwODA5MTYw\nNzU0WjAhMQswCQYDVQQGEwJHQjESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5bOL7LD9kiIagcVrZqZ13ZcR0KhMuzs\nbrqULbZKyqC+uBRgINxYJ7LPnJ4LPYuCt/nAaQ7CLXfKgzAMFu8eIK6UEvZA6+7b\n20E4rvOpPbTB/T4JbYZNQKyM9AEwr6j0P6vFgrWT7aBzhkmiqEe5vv/7ZOEGb+Ab\n+cvMeszfBbk93nyzKdNaUuh95x7/p0Ow315np2PRuoT0QQnA9zE/9eZ3Jah3cNZn\nNuQ6BDHlkegzTV5JhYYblRo/pmt2E9E0ha+NWsRLf3ZJUYhkYR3UqMltEKuLglCO\nVWBbPmKd4IZUNIotpKMVQSVb9agNBF49hH9iBhN3fBm7Hp8KBpjJLwIDAQABMA0G\nCSqGSIb3DQEBCwUAA4IBAQCo/Rn4spa5XFk0cCoKypP27DxePkGD9rQZk/CY4inV\nJV16anZ1pr9yfO61+m3fRKTZq7yxtHRDWxDdROHx9LqV1dXLAmh1ecV9Kn6/796O\nEHsOcVB0Lfi9Ili7//oUqlhGNploRuQbgWAXU+Eo1xJRWIXeedhzBSgEOMaQk3Zn\nTdYFhP0/Ao/fEdI4VULv1A43ztnZIB2KXWgUQoFT32woL47eWge8LxxVmmH3STtz\nnNcGnYxIorCQemDHDzMrvxRWgHxkpFGGqAhkFFyCmhKFPglKwt+yVTx26T8tShID\nISMj0rgVMptmtWKJfzNCvFG52gsuO4w3yGdjgjRRrBDm\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIC7TCCAdUCFDuGBhl3l5Z++VCLkvaav4yteBonMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwMzIzMTYwNzU0WhcNNDcwODA5MTYw\nNzU0WjAhMQswCQYDVQQGEwJHQjESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5bOL7LD9kiIagcVrZqZ13ZcR0KhMuzs\nbrqULbZKyqC+uBRgINxYJ7LPnJ4LPYuCt/nAaQ7CLXfKgzAMFu8eIK6UEvZA6+7b\n20E4rvOpPbTB/T4JbYZNQKyM9AEwr6j0P6vFgrWT7aBzhkmiqEe5vv/7ZOEGb+Ab\n+cvMeszfBbk93nyzKdNaUuh95x7/p0Ow315np2PRuoT0QQnA9zE/9eZ3Jah3cNZn\nNuQ6BDHlkegzTV5JhYYblRo/pmt2E9E0ha+NWsRLf3ZJUYhkYR3UqMltEKuLglCO\nVWBbPmKd4IZUNIotpKMVQSVb9agNBF49hH9iBhN3fBm7Hp8KBpjJLwIDAQABMA0G\nCSqGSIb3DQEBCwUAA4IBAQCo/Rn4spa5XFk0cCoKypP27DxePkGD9rQZk/CY4inV\nJV16anZ1pr9yfO61+m3fRKTZq7yxtHRDWxDdROHx9LqV1dXLAmh1ecV9Kn6/796O\nEHsOcVB0Lfi9Ili7//oUqlhGNploRuQbgWAXU+Eo1xJRWIXeedhzBSgEOMaQk3Zn\nTdYFhP0/Ao/fEdI4VULv1A43ztnZIB2KXWgUQoFT32woL47eWge8LxxVmmH3STtz\nnNcGnYxIorCQemDHDzMrvxRWgHxkpFGGqAhkFFyCmhKFPglKwt+yVTx26T8tShID\nISMj0rgVMptmtWKJfzNCvFG52gsuO4w3yGdjgjRRrBDm\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "quiche/examples/cert.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC7TCCAdUCFDuGBhl3l5Z++VCLkvaav4yteBonMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwMzIzMTYwNzU0WhcNNDcwODA5MTYw\nNzU0WjAhMQswCQYDVQQGEwJHQjESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz5bOL7LD9kiIagcVrZqZ13ZcR0KhMuzs\nbrqULbZKyqC+uBRgINxYJ7LPnJ4LPYuCt/nAaQ7CLXfKgzAMFu8eIK6UEvZA6+7b\n20E4rvOpPbTB/T4JbYZNQKyM9AEwr6j0P6vFgrWT7aBzhkmiqEe5vv/7ZOEGb+Ab\n+cvMeszfBbk93nyzKdNaUuh95x7/p0Ow315np2PRuoT0QQnA9zE/9eZ3Jah3cNZn\nNuQ6BDHlkegzTV5JhYYblRo/pmt2E9E0ha+NWsRLf3ZJUYhkYR3UqMltEKuLglCO\nVWBbPmKd4IZUNIotpKMVQSVb9agNBF49hH9iBhN3fBm7Hp8KBpjJLwIDAQABMA0G\nCSqGSIb3DQEBCwUAA4IBAQCo/Rn4spa5XFk0cCoKypP27DxePkGD9rQZk/CY4inV\nJV16anZ1pr9yfO61+m3fRKTZq7yxtHRDWxDdROHx9LqV1dXLAmh1ecV9Kn6/796O\nEHsOcVB0Lfi9Ili7//oUqlhGNploRuQbgWAXU+Eo1xJRWIXeedhzBSgEOMaQk3Zn\nTdYFhP0/Ao/fEdI4VULv1A43ztnZIB2KXWgUQoFT32woL47eWge8LxxVmmH3STtz\nnNcGnYxIorCQemDHDzMrvxRWgHxkpFGGqAhkFFyCmhKFPglKwt+yVTx26T8tShID\nISMj0rgVMptmtWKJfzNCvFG52gsuO4w3yGdjgjRRrBDm\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "quiche/examples/cert.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPls4vssP2SIhq\nBxWtmpnXdlxHQqEy7OxuupQttkrKoL64FGAg3Fgnss+cngs9i4K3+cBpDsItd8qD\nMAwW7x4grpQS9kDr7tvbQTiu86k9tMH9Pglthk1ArIz0ATCvqPQ/q8WCtZPtoHOG\nSaKoR7m+//tk4QZv4Bv5y8x6zN8FuT3efLMp01pS6H3nHv+nQ7DfXmenY9G6hPRB\nCcD3MT/15nclqHdw1mc25DoEMeWR6DNNXkmFhhuVGj+ma3YT0TSFr41axEt/dklR\niGRhHdSoyW0Qq4uCUI5VYFs+Yp3ghlQ0ii2koxVBJVv1qA0EXj2Ef2IGE3d8Gbse\nnwoGmMkvAgMBAAECggEBAMtFkpUmablKgTnBwjqCvs47OlUVK6AgW8x5qwuwC0Cr\nctXyLcc/vJry/1UPdVZIvDHGv+Cf8Qhw2r7nV49FiqzaBmki9aOR+3uRPB4kvr6L\nt8Fw8+5pqlAAJu3wFGqN+M44N2mswDPaAAWpKTu7MGmVY+f+aT03qG1MYOiGoISK\ngP6DHiinddD38spM2muyCUyFZk9a+aBEfaQzZoU3gc0yB6R/qBOWZ7NIoIUMicku\nZf3L6/06uunyZp+ueR83j1YWbg3JoYKlGAuQtDRF709+MQrim8lKTnfuHiBeZKYZ\nGNLSo7lGjrp6ccSyfXmlA36hSfdlrWtZJ4+utZShftECgYEA+NNOFNa1BLfDw3ot\na6L4W6FE45B32bLbnBdg8foyEYrwzHLPFCbws1Z60pNr7NaCHDIMiKVOXvKQa78d\nqdWuPUVJ83uVs9GI8tAo00RAvBn6ut9yaaLa8mIv6ZpfU20IgE5sDjB7IBY9tTVd\nEDyJcDuKQXzQ48qmEw86wINQMd0CgYEA1ZMdt7yLnpDiYa6M/BuKjp7PWKcRlzVM\nBcCEYHA4LJ6xEOH4y9DEx2y5ljwOcXgJhXAfAyGQr7s1xiP/nXurqfmdP8u7bawp\nVwuWJ8Vv0ZXITaU0isezG2Dpnseuion3qSraWlmWUlWLVVgKETZmk7cF7VIXa0NT\nLFREdObI5HsCgYBUbm8KRyi5Zxm4VNbgtTYM8ZYMmdLxPe2i85PjyAABT+IRncuC\njQwT7n5Swc9XWBpiMuFp5J3JPgmfZgRMwsMS61YClqbfk3Qi4FtaBMjqiu43Rubt\nzWL56DNV0xoRlufRkcq8rdq5spJR0L+5aLFCMhHh0taW1QaxZPOMq4IkyQKBgQC3\nGetubGzewqPyzuz77ri5URm+jW0dT4ofnE9hRpRCXMK9EJ52TkOGHYZ2cIKJcTno\ndpl/27Tpk/ykJJSu9SnVDbVszkOf4OuIPty6uCAHdPxG5Q3ItTCulkVz5QmUqHf1\nRlHxB8FCUSilQFdRLmx+03h3X9vID+4soQoXlwxAJQKBgE5SQpN+TG5V+E4zHgNd\n6cy6gA5dGDJ0KbsgxJwlKTFA9nIcs2ssBxLY9U4x75EGuqpeVNmq6xwwmPtBs0rp\nM3W4zdFrZQ3BneFRW7WbSBbsUSprkJW/p4GXa17GzGUq/MDXlGhNlApP1nknzFvE\nxGaH0/H/TZxpLCogVP9npUkj\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "quiche/examples/client.c",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n//       notice, this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#include <inttypes.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <unistd.h>\n\n#include <fcntl.h>\n#include <errno.h>\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netdb.h>\n\n#include <ev.h>\n\n#include <quiche.h>\n\n#define LOCAL_CONN_ID_LEN 16\n\n#define MAX_DATAGRAM_SIZE 1350\n\nstruct conn_io {\n    ev_timer timer;\n\n    int sock;\n\n    struct sockaddr_storage local_addr;\n    socklen_t local_addr_len;\n\n    quiche_conn *conn;\n};\n\nstatic void debug_log(const char *line, void *argp) {\n    fprintf(stderr, \"%s\\n\", line);\n}\n\nstatic void flush_egress(struct ev_loop *loop, struct conn_io *conn_io) {\n    static uint8_t out[MAX_DATAGRAM_SIZE];\n\n    quiche_send_info send_info;\n\n    while (1) {\n        ssize_t written = quiche_conn_send(conn_io->conn, out, sizeof(out),\n                                           &send_info);\n\n        if (written == QUICHE_ERR_DONE) {\n            fprintf(stderr, \"done writing\\n\");\n            break;\n        }\n\n        if (written < 0) {\n            fprintf(stderr, \"failed to create packet: %zd\\n\", written);\n            return;\n        }\n\n        ssize_t sent = sendto(conn_io->sock, out, written, 0,\n                              (struct sockaddr *) &send_info.to,\n                              send_info.to_len);\n\n        if (sent != written) {\n            perror(\"failed to send\");\n            return;\n        }\n\n        fprintf(stderr, \"sent %zd bytes\\n\", sent);\n    }\n\n    double t = quiche_conn_timeout_as_nanos(conn_io->conn) / 1e9f;\n    conn_io->timer.repeat = t;\n    ev_timer_again(loop, &conn_io->timer);\n}\n\nstatic void recv_cb(EV_P_ ev_io *w, int revents) {\n    static bool req_sent = false;\n\n    struct conn_io *conn_io = w->data;\n\n    static uint8_t buf[65535];\n\n    while (1) {\n        struct sockaddr_storage peer_addr;\n        socklen_t peer_addr_len = sizeof(peer_addr);\n        memset(&peer_addr, 0, peer_addr_len);\n\n        ssize_t read = recvfrom(conn_io->sock, buf, sizeof(buf), 0,\n                                (struct sockaddr *) &peer_addr,\n                                &peer_addr_len);\n\n        if (read < 0) {\n            if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {\n                fprintf(stderr, \"recv would block\\n\");\n                break;\n            }\n\n            perror(\"failed to read\");\n            return;\n        }\n\n        quiche_recv_info recv_info = {\n            (struct sockaddr *) &peer_addr,\n            peer_addr_len,\n\n            (struct sockaddr *) &conn_io->local_addr,\n            conn_io->local_addr_len,\n        };\n\n        ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info);\n\n        if (done < 0) {\n            fprintf(stderr, \"failed to process packet\\n\");\n            continue;\n        }\n\n        fprintf(stderr, \"recv %zd bytes\\n\", done);\n    }\n\n    fprintf(stderr, \"done reading\\n\");\n\n    if (quiche_conn_is_closed(conn_io->conn)) {\n        fprintf(stderr, \"connection closed\\n\");\n\n        ev_break(EV_A_ EVBREAK_ONE);\n        return;\n    }\n\n    if (quiche_conn_is_established(conn_io->conn) && !req_sent) {\n        const uint8_t *app_proto;\n        size_t app_proto_len;\n\n        quiche_conn_application_proto(conn_io->conn, &app_proto, &app_proto_len);\n\n        fprintf(stderr, \"connection established: %.*s\\n\",\n                (int) app_proto_len, app_proto);\n\n        const static uint8_t r[] = \"GET /index.html\\r\\n\";\n        uint64_t error_code;\n        if (quiche_conn_stream_send(conn_io->conn, 4, r, sizeof(r), true, &error_code) < 0) {\n            fprintf(stderr, \"failed to send HTTP request: %\" PRIu64 \"\\n\", error_code);\n            return;\n        }\n\n        fprintf(stderr, \"sent HTTP request\\n\");\n\n        req_sent = true;\n    }\n\n    if (quiche_conn_is_established(conn_io->conn)) {\n        uint64_t s = 0;\n\n        quiche_stream_iter *readable = quiche_conn_readable(conn_io->conn);\n\n        while (quiche_stream_iter_next(readable, &s)) {\n            fprintf(stderr, \"stream %\" PRIu64 \" is readable\\n\", s);\n\n            bool fin = false;\n            uint64_t error_code;\n            ssize_t recv_len = quiche_conn_stream_recv(conn_io->conn, s,\n                                                       buf, sizeof(buf),\n                                                       &fin, &error_code);\n            if (recv_len < 0) {\n                break;\n            }\n\n            printf(\"%.*s\", (int) recv_len, buf);\n\n            if (fin) {\n                if (quiche_conn_close(conn_io->conn, true, 0, NULL, 0) < 0) {\n                    fprintf(stderr, \"failed to close connection\\n\");\n                }\n            }\n        }\n\n        quiche_stream_iter_free(readable);\n    }\n\n    flush_egress(loop, conn_io);\n}\n\nstatic void timeout_cb(EV_P_ ev_timer *w, int revents) {\n    struct conn_io *conn_io = w->data;\n    quiche_conn_on_timeout(conn_io->conn);\n\n    fprintf(stderr, \"timeout\\n\");\n\n    flush_egress(loop, conn_io);\n\n    if (quiche_conn_is_closed(conn_io->conn)) {\n        quiche_stats stats;\n        quiche_path_stats path_stats;\n\n        quiche_conn_stats(conn_io->conn, &stats);\n        quiche_conn_path_stats(conn_io->conn, 0, &path_stats);\n\n        fprintf(stderr, \"connection closed, recv=%zu sent=%zu lost=%zu rtt=%\" PRIu64 \"ns\\n\",\n                stats.recv, stats.sent, stats.lost, path_stats.rtt);\n\n        ev_break(EV_A_ EVBREAK_ONE);\n        return;\n    }\n}\n\nint main(int argc, char *argv[]) {\n    const char *host = argv[1];\n    const char *port = argv[2];\n\n    const struct addrinfo hints = {\n        .ai_family = PF_UNSPEC,\n        .ai_socktype = SOCK_DGRAM,\n        .ai_protocol = IPPROTO_UDP\n    };\n\n    quiche_enable_debug_logging(debug_log, NULL);\n\n    struct addrinfo *peer;\n    if (getaddrinfo(host, port, &hints, &peer) != 0) {\n        perror(\"failed to resolve host\");\n        return -1;\n    }\n\n    int sock = socket(peer->ai_family, SOCK_DGRAM, 0);\n    if (sock < 0) {\n        perror(\"failed to create socket\");\n        return -1;\n    }\n\n    if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {\n        perror(\"failed to make socket non-blocking\");\n        return -1;\n    }\n\n    quiche_config *config = quiche_config_new(0xbabababa);\n    if (config == NULL) {\n        fprintf(stderr, \"failed to create config\\n\");\n        return -1;\n    }\n\n    quiche_config_set_application_protos(config,\n        (uint8_t *) \"\\x0ahq-interop\\x05hq-29\\x05hq-28\\x05hq-27\\x08http/0.9\", 38);\n\n    quiche_config_set_max_idle_timeout(config, 5000);\n    quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);\n    quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);\n    quiche_config_set_initial_max_data(config, 10000000);\n    quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000);\n    quiche_config_set_initial_max_stream_data_uni(config, 1000000);\n    quiche_config_set_initial_max_streams_bidi(config, 100);\n    quiche_config_set_initial_max_streams_uni(config, 100);\n    quiche_config_set_disable_active_migration(config, true);\n\n    if (getenv(\"SSLKEYLOGFILE\")) {\n      quiche_config_log_keys(config);\n    }\n\n    uint8_t scid[LOCAL_CONN_ID_LEN];\n    int rng = open(\"/dev/urandom\", O_RDONLY);\n    if (rng < 0) {\n        perror(\"failed to open /dev/urandom\");\n        return -1;\n    }\n\n    ssize_t rand_len = read(rng, &scid, sizeof(scid));\n    if (rand_len < 0) {\n        perror(\"failed to create connection ID\");\n        return -1;\n    }\n\n    struct conn_io *conn_io = malloc(sizeof(*conn_io));\n    if (conn_io == NULL) {\n        fprintf(stderr, \"failed to allocate connection IO\\n\");\n        return -1;\n    }\n\n    conn_io->local_addr_len = sizeof(conn_io->local_addr);\n    if (getsockname(sock, (struct sockaddr *)&conn_io->local_addr,\n                    &conn_io->local_addr_len) != 0)\n    {\n        perror(\"failed to get local address of socket\");\n        return -1;\n    };\n\n    quiche_conn *conn = quiche_connect(host, (const uint8_t *) scid, sizeof(scid),\n                                       (struct sockaddr *) &conn_io->local_addr,\n                                       conn_io->local_addr_len,\n                                       peer->ai_addr, peer->ai_addrlen, config);\n\n    if (conn == NULL) {\n        fprintf(stderr, \"failed to create connection\\n\");\n        return -1;\n    }\n\n    conn_io->sock = sock;\n    conn_io->conn = conn;\n\n    ev_io watcher;\n\n    struct ev_loop *loop = ev_default_loop(0);\n\n    ev_io_init(&watcher, recv_cb, conn_io->sock, EV_READ);\n    ev_io_start(loop, &watcher);\n    watcher.data = conn_io;\n\n    ev_init(&conn_io->timer, timeout_cb);\n    conn_io->timer.data = conn_io;\n\n    flush_egress(loop, conn_io);\n\n    ev_loop(loop, 0);\n\n    freeaddrinfo(peer);\n\n    quiche_conn_free(conn);\n\n    quiche_config_free(config);\n\n    return 0;\n}\n"
  },
  {
    "path": "quiche/examples/client.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[macro_use]\nextern crate log;\n\nuse ring::rand::*;\n\nconst MAX_DATAGRAM_SIZE: usize = 1350;\n\nconst HTTP_REQ_STREAM_ID: u64 = 4;\n\nfn main() {\n    let mut buf = [0; 65535];\n    let mut out = [0; MAX_DATAGRAM_SIZE];\n\n    let mut args = std::env::args();\n\n    let cmd = &args.next().unwrap();\n\n    if args.len() != 1 {\n        println!(\"Usage: {cmd} URL\");\n        println!(\"\\nSee tools/apps/ for more complete implementations.\");\n        return;\n    }\n\n    let url = url::Url::parse(&args.next().unwrap()).unwrap();\n\n    // Setup the event loop.\n    let mut poll = mio::Poll::new().unwrap();\n    let mut events = mio::Events::with_capacity(1024);\n\n    // Resolve server address.\n    let peer_addr = url.socket_addrs(|| None).unwrap()[0];\n\n    // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the\n    // server address. This is needed on macOS and BSD variants that don't\n    // support binding to IN6ADDR_ANY for both v4 and v6.\n    let bind_addr = match peer_addr {\n        std::net::SocketAddr::V4(_) => \"0.0.0.0:0\",\n        std::net::SocketAddr::V6(_) => \"[::]:0\",\n    };\n\n    // Create the UDP socket backing the QUIC connection, and register it with\n    // the event loop.\n    let mut socket =\n        mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap();\n    poll.registry()\n        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)\n        .unwrap();\n\n    // Create the configuration for the QUIC connection.\n    let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n\n    // *CAUTION*: this should not be set to `false` in production!!!\n    config.verify_peer(false);\n\n    config\n        .set_application_protos(&[\n            b\"hq-interop\",\n            b\"hq-29\",\n            b\"hq-28\",\n            b\"hq-27\",\n            b\"http/0.9\",\n        ])\n        .unwrap();\n\n    config.set_max_idle_timeout(5000);\n    config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_initial_max_data(10_000_000);\n    config.set_initial_max_stream_data_bidi_local(1_000_000);\n    config.set_initial_max_stream_data_bidi_remote(1_000_000);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(100);\n    config.set_disable_active_migration(true);\n\n    // Generate a random source connection ID for the connection.\n    let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n    SystemRandom::new().fill(&mut scid[..]).unwrap();\n\n    let scid = quiche::ConnectionId::from_ref(&scid);\n\n    // Get local address.\n    let local_addr = socket.local_addr().unwrap();\n\n    // Create a QUIC connection and initiate handshake.\n    let mut conn =\n        quiche::connect(url.domain(), &scid, local_addr, peer_addr, &mut config)\n            .unwrap();\n\n    info!(\n        \"connecting to {:} from {:} with scid {}\",\n        peer_addr,\n        socket.local_addr().unwrap(),\n        hex_dump(&scid)\n    );\n\n    let (write, send_info) = conn.send(&mut out).expect(\"initial send failed\");\n\n    while let Err(e) = socket.send_to(&out[..write], send_info.to) {\n        if e.kind() == std::io::ErrorKind::WouldBlock {\n            debug!(\"send() would block\");\n            continue;\n        }\n\n        panic!(\"send() failed: {e:?}\");\n    }\n\n    debug!(\"written {write}\");\n\n    let req_start = std::time::Instant::now();\n\n    let mut req_sent = false;\n\n    loop {\n        poll.poll(&mut events, conn.timeout()).unwrap();\n\n        // Read incoming UDP packets from the socket and feed them to quiche,\n        // until there are no more packets to read.\n        'read: loop {\n            // If the event loop reported no events, it means that the timeout\n            // has expired, so handle it without attempting to read packets. We\n            // will then proceed with the send loop.\n            if events.is_empty() {\n                debug!(\"timed out\");\n\n                conn.on_timeout();\n                break 'read;\n            }\n\n            let (len, from) = match socket.recv_from(&mut buf) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    // There are no more UDP packets to read, so end the read\n                    // loop.\n                    if e.kind() == std::io::ErrorKind::WouldBlock {\n                        debug!(\"recv() would block\");\n                        break 'read;\n                    }\n\n                    panic!(\"recv() failed: {e:?}\");\n                },\n            };\n\n            debug!(\"got {len} bytes\");\n\n            let recv_info = quiche::RecvInfo {\n                to: socket.local_addr().unwrap(),\n                from,\n            };\n\n            // Process potentially coalesced packets.\n            let read = match conn.recv(&mut buf[..len], recv_info) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    error!(\"recv failed: {e:?}\");\n                    continue 'read;\n                },\n            };\n\n            debug!(\"processed {read} bytes\");\n        }\n\n        debug!(\"done reading\");\n\n        if conn.is_closed() {\n            info!(\"connection closed, {:?}\", conn.stats());\n            break;\n        }\n\n        // Send an HTTP request as soon as the connection is established.\n        if conn.is_established() && !req_sent {\n            info!(\"sending HTTP request for {}\", url.path());\n\n            let req = format!(\"GET {}\\r\\n\", url.path());\n            conn.stream_send(HTTP_REQ_STREAM_ID, req.as_bytes(), true)\n                .unwrap();\n\n            req_sent = true;\n        }\n\n        // Process all readable streams.\n        for s in conn.readable() {\n            while let Ok((read, fin)) = conn.stream_recv(s, &mut buf) {\n                debug!(\"received {read} bytes\");\n\n                let stream_buf = &buf[..read];\n\n                debug!(\n                    \"stream {} has {} bytes (fin? {})\",\n                    s,\n                    stream_buf.len(),\n                    fin\n                );\n\n                print!(\"{}\", unsafe {\n                    std::str::from_utf8_unchecked(stream_buf)\n                });\n\n                // The server reported that it has no more data to send, which\n                // we got the full response. Close the connection.\n                if s == HTTP_REQ_STREAM_ID && fin {\n                    info!(\n                        \"response received in {:?}, closing...\",\n                        req_start.elapsed()\n                    );\n\n                    conn.close(true, 0x00, b\"kthxbye\").unwrap();\n                }\n            }\n        }\n\n        // Generate outgoing QUIC packets and send them on the UDP socket, until\n        // quiche reports that there are no more packets to be sent.\n        loop {\n            let (write, send_info) = match conn.send(&mut out) {\n                Ok(v) => v,\n\n                Err(quiche::Error::Done) => {\n                    debug!(\"done writing\");\n                    break;\n                },\n\n                Err(e) => {\n                    error!(\"send failed: {e:?}\");\n\n                    conn.close(false, 0x1, b\"fail\").ok();\n                    break;\n                },\n            };\n\n            if let Err(e) = socket.send_to(&out[..write], send_info.to) {\n                if e.kind() == std::io::ErrorKind::WouldBlock {\n                    debug!(\"send() would block\");\n                    break;\n                }\n\n                panic!(\"send() failed: {e:?}\");\n            }\n\n            debug!(\"written {write}\");\n        }\n\n        if conn.is_closed() {\n            info!(\"connection closed, {:?}\", conn.stats());\n            break;\n        }\n    }\n}\n\nfn hex_dump(buf: &[u8]) -> String {\n    let vec: Vec<String> = buf.iter().map(|b| format!(\"{b:02x}\")).collect();\n\n    vec.join(\"\")\n}\n"
  },
  {
    "path": "quiche/examples/gen-certs.sh",
    "content": "#!/bin/bash\nset -ex\ncd $(dirname $0)\nopenssl req -new -x509 -batch -nodes -days 10000 -keyout rootca.key -out rootca.crt\nopenssl req -new -batch -nodes -sha256 -keyout cert.key -out cert.csr -subj '/C=GB/CN=quic.tech'\nopenssl x509 -req -days 10000 -in cert.csr -CA rootca.crt -CAkey rootca.key -CAcreateserial -out cert.crt\nopenssl verify -CAfile rootca.crt cert.crt\ncp cert.crt cert-big.crt\ncat cert.crt >> cert-big.crt\ncat cert.crt >> cert-big.crt\ncat cert.crt >> cert-big.crt\ncat cert.crt >> cert-big.crt\nrm cert.csr\nrm rootca.key\nrm rootca.srl\n"
  },
  {
    "path": "quiche/examples/http3-client.c",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n//       notice, this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#include <inttypes.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <unistd.h>\n\n#include <fcntl.h>\n#include <errno.h>\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netdb.h>\n\n#include <ev.h>\n\n#include <quiche.h>\n\n#define LOCAL_CONN_ID_LEN 16\n\n#define MAX_DATAGRAM_SIZE 1350\n\nstruct conn_io {\n    ev_timer timer;\n\n    const char *host;\n\n    int sock;\n\n    struct sockaddr_storage local_addr;\n    socklen_t local_addr_len;\n\n    quiche_conn *conn;\n\n    quiche_h3_conn *http3;\n};\n\nstatic void debug_log(const char *line, void *argp) {\n    fprintf(stderr, \"%s\\n\", line);\n}\n\nstatic void flush_egress(struct ev_loop *loop, struct conn_io *conn_io) {\n    static uint8_t out[MAX_DATAGRAM_SIZE];\n\n    quiche_send_info send_info;\n\n    while (1) {\n        ssize_t written = quiche_conn_send(conn_io->conn, out, sizeof(out),\n                                           &send_info);\n\n        if (written == QUICHE_ERR_DONE) {\n            fprintf(stderr, \"done writing\\n\");\n            break;\n        }\n\n        if (written < 0) {\n            fprintf(stderr, \"failed to create packet: %zd\\n\", written);\n            return;\n        }\n\n        ssize_t sent = sendto(conn_io->sock, out, written, 0,\n                              (struct sockaddr *) &send_info.to,\n                              send_info.to_len);\n\n        if (sent != written) {\n            perror(\"failed to send\");\n            return;\n        }\n\n        fprintf(stderr, \"sent %zd bytes\\n\", sent);\n    }\n\n    double t = quiche_conn_timeout_as_nanos(conn_io->conn) / 1e9f;\n    conn_io->timer.repeat = t;\n    ev_timer_again(loop, &conn_io->timer);\n}\n\nstatic int for_each_setting(uint64_t identifier, uint64_t value,\n                           void *argp) {\n    fprintf(stderr, \"got HTTP/3 SETTING: %\" PRIu64 \"=%\" PRIu64 \"\\n\",\n            identifier, value);\n\n    return 0;\n}\n\nstatic int for_each_header(uint8_t *name, size_t name_len,\n                           uint8_t *value, size_t value_len,\n                           void *argp) {\n    fprintf(stderr, \"got HTTP header: %.*s=%.*s\\n\",\n            (int) name_len, name, (int) value_len, value);\n\n    return 0;\n}\n\nstatic void recv_cb(EV_P_ ev_io *w, int revents) {\n    static bool req_sent = false;\n    static bool settings_received = false;\n\n    struct conn_io *conn_io = w->data;\n\n    static uint8_t buf[65535];\n\n    while (1) {\n        struct sockaddr_storage peer_addr;\n        socklen_t peer_addr_len = sizeof(peer_addr);\n        memset(&peer_addr, 0, peer_addr_len);\n\n        ssize_t read = recvfrom(conn_io->sock, buf, sizeof(buf), 0,\n                                (struct sockaddr *) &peer_addr,\n                                &peer_addr_len);\n\n        if (read < 0) {\n            if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {\n                fprintf(stderr, \"recv would block\\n\");\n                break;\n            }\n\n            perror(\"failed to read\");\n            return;\n        }\n\n        quiche_recv_info recv_info = {\n            (struct sockaddr *) &peer_addr,\n            peer_addr_len,\n\n            (struct sockaddr *) &conn_io->local_addr,\n            conn_io->local_addr_len,\n        };\n\n        ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info);\n\n        if (done < 0) {\n            fprintf(stderr, \"failed to process packet: %zd\\n\", done);\n            continue;\n        }\n\n        fprintf(stderr, \"recv %zd bytes\\n\", done);\n    }\n\n    fprintf(stderr, \"done reading\\n\");\n\n    if (quiche_conn_is_closed(conn_io->conn)) {\n        fprintf(stderr, \"connection closed\\n\");\n\n        ev_break(EV_A_ EVBREAK_ONE);\n        return;\n    }\n\n    if (quiche_conn_is_established(conn_io->conn) && !req_sent) {\n        const uint8_t *app_proto;\n        size_t app_proto_len;\n\n        quiche_conn_application_proto(conn_io->conn, &app_proto, &app_proto_len);\n\n        fprintf(stderr, \"connection established: %.*s\\n\",\n                (int) app_proto_len, app_proto);\n\n        quiche_h3_config *config = quiche_h3_config_new();\n        if (config == NULL) {\n            fprintf(stderr, \"failed to create HTTP/3 config\\n\");\n            return;\n        }\n\n        conn_io->http3 = quiche_h3_conn_new_with_transport(conn_io->conn, config);\n        if (conn_io->http3 == NULL) {\n            fprintf(stderr, \"failed to create HTTP/3 connection\\n\");\n            return;\n        }\n\n        quiche_h3_config_free(config);\n\n        quiche_h3_header headers[] = {\n            {\n                .name = (const uint8_t *) \":method\",\n                .name_len = sizeof(\":method\") - 1,\n\n                .value = (const uint8_t *) \"GET\",\n                .value_len = sizeof(\"GET\") - 1,\n            },\n\n            {\n                .name = (const uint8_t *) \":scheme\",\n                .name_len = sizeof(\":scheme\") - 1,\n\n                .value = (const uint8_t *) \"https\",\n                .value_len = sizeof(\"https\") - 1,\n            },\n\n            {\n                .name = (const uint8_t *) \":authority\",\n                .name_len = sizeof(\":authority\") - 1,\n\n                .value = (const uint8_t *) conn_io->host,\n                .value_len = strlen(conn_io->host),\n            },\n\n            {\n                .name = (const uint8_t *) \":path\",\n                .name_len = sizeof(\":path\") - 1,\n\n                .value = (const uint8_t *) \"/\",\n                .value_len = sizeof(\"/\") - 1,\n            },\n\n            {\n                .name = (const uint8_t *) \"user-agent\",\n                .name_len = sizeof(\"user-agent\") - 1,\n\n                .value = (const uint8_t *) \"quiche\",\n                .value_len = sizeof(\"quiche\") - 1,\n            },\n        };\n\n        int64_t stream_id = quiche_h3_send_request(conn_io->http3,\n                                                   conn_io->conn,\n                                                   headers, 5, true);\n\n        fprintf(stderr, \"sent HTTP request %\" PRId64 \"\\n\", stream_id);\n\n        req_sent = true;\n    }\n\n    if (quiche_conn_is_established(conn_io->conn)) {\n        quiche_h3_event *ev;\n\n        while (1) {\n            int64_t s = quiche_h3_conn_poll(conn_io->http3,\n                                            conn_io->conn,\n                                            &ev);\n\n            if (s < 0) {\n                break;\n            }\n\n            if (!settings_received) {\n                int rc = quiche_h3_for_each_setting(conn_io->http3,\n                                                    for_each_setting,\n                                                    NULL);\n\n                if (rc == 0) {\n                    settings_received = true;\n                }\n            }\n\n            switch (quiche_h3_event_type(ev)) {\n                case QUICHE_H3_EVENT_HEADERS: {\n                    int rc = quiche_h3_event_for_each_header(ev, for_each_header,\n                                                             NULL);\n\n                    if (rc != 0) {\n                        fprintf(stderr, \"failed to process headers\");\n                    }\n\n                    break;\n                }\n\n                case QUICHE_H3_EVENT_DATA: {\n                    for (;;) {\n                        ssize_t len = quiche_h3_recv_body(conn_io->http3,\n                                                          conn_io->conn, s,\n                                                          buf, sizeof(buf));\n\n                        if (len <= 0) {\n                            break;\n                        }\n\n                        printf(\"%.*s\", (int) len, buf);\n                    }\n\n                    break;\n                }\n\n                case QUICHE_H3_EVENT_FINISHED:\n                    if (quiche_conn_close(conn_io->conn, true, 0, NULL, 0) < 0) {\n                        fprintf(stderr, \"failed to close connection\\n\");\n                    }\n                    break;\n\n                case QUICHE_H3_EVENT_RESET:\n                    fprintf(stderr, \"request was reset\\n\");\n\n                    if (quiche_conn_close(conn_io->conn, true, 0, NULL, 0) < 0) {\n                        fprintf(stderr, \"failed to close connection\\n\");\n                    }\n                    break;\n\n                case QUICHE_H3_EVENT_PRIORITY_UPDATE:\n                    break;\n\n                case QUICHE_H3_EVENT_GOAWAY: {\n                    fprintf(stderr, \"got GOAWAY\\n\");\n                    break;\n                }\n            }\n\n            quiche_h3_event_free(ev);\n        }\n    }\n\n    flush_egress(loop, conn_io);\n}\n\nstatic void timeout_cb(EV_P_ ev_timer *w, int revents) {\n    struct conn_io *conn_io = w->data;\n    quiche_conn_on_timeout(conn_io->conn);\n\n    fprintf(stderr, \"timeout\\n\");\n\n    flush_egress(loop, conn_io);\n\n    if (quiche_conn_is_closed(conn_io->conn)) {\n        quiche_stats stats;\n        quiche_path_stats path_stats;\n\n        quiche_conn_stats(conn_io->conn, &stats);\n        quiche_conn_path_stats(conn_io->conn, 0, &path_stats);\n\n        fprintf(stderr, \"connection closed, recv=%zu sent=%zu lost=%zu rtt=%\" PRIu64 \"ns\\n\",\n                stats.recv, stats.sent, stats.lost, path_stats.rtt);\n\n        ev_break(EV_A_ EVBREAK_ONE);\n        return;\n    }\n}\n\nint main(int argc, char *argv[]) {\n    const char *host = argv[1];\n    const char *port = argv[2];\n\n    const struct addrinfo hints = {\n        .ai_family = PF_UNSPEC,\n        .ai_socktype = SOCK_DGRAM,\n        .ai_protocol = IPPROTO_UDP\n    };\n\n    quiche_enable_debug_logging(debug_log, NULL);\n\n    struct addrinfo *peer;\n    if (getaddrinfo(host, port, &hints, &peer) != 0) {\n        perror(\"failed to resolve host\");\n        return -1;\n    }\n\n    int sock = socket(peer->ai_family, SOCK_DGRAM, 0);\n    if (sock < 0) {\n        perror(\"failed to create socket\");\n        return -1;\n    }\n\n    if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {\n        perror(\"failed to make socket non-blocking\");\n        return -1;\n    }\n\n    quiche_config *config = quiche_config_new(0xbabababa);\n    if (config == NULL) {\n        fprintf(stderr, \"failed to create config\\n\");\n        return -1;\n    }\n\n    quiche_config_set_application_protos(config,\n        (uint8_t *) QUICHE_H3_APPLICATION_PROTOCOL,\n        sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);\n\n    quiche_config_set_max_idle_timeout(config, 5000);\n    quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);\n    quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);\n    quiche_config_set_initial_max_data(config, 10000000);\n    quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000);\n    quiche_config_set_initial_max_stream_data_bidi_remote(config, 1000000);\n    quiche_config_set_initial_max_stream_data_uni(config, 1000000);\n    quiche_config_set_initial_max_streams_bidi(config, 100);\n    quiche_config_set_initial_max_streams_uni(config, 100);\n    quiche_config_set_disable_active_migration(config, true);\n\n    if (getenv(\"SSLKEYLOGFILE\")) {\n      quiche_config_log_keys(config);\n    }\n\n    // ABC: old config creation here\n\n    uint8_t scid[LOCAL_CONN_ID_LEN];\n    int rng = open(\"/dev/urandom\", O_RDONLY);\n    if (rng < 0) {\n        perror(\"failed to open /dev/urandom\");\n        return -1;\n    }\n\n    ssize_t rand_len = read(rng, &scid, sizeof(scid));\n    if (rand_len < 0) {\n        perror(\"failed to create connection ID\");\n        return -1;\n    }\n\n    struct conn_io *conn_io = malloc(sizeof(*conn_io));\n    if (conn_io == NULL) {\n        fprintf(stderr, \"failed to allocate connection IO\\n\");\n        return -1;\n    }\n\n    conn_io->local_addr_len = sizeof(conn_io->local_addr);\n    if (getsockname(sock, (struct sockaddr *)&conn_io->local_addr,\n                    &conn_io->local_addr_len) != 0)\n    {\n        perror(\"failed to get local address of socket\");\n        return -1;\n    };\n\n    quiche_conn *conn = quiche_connect(host, (const uint8_t *) scid, sizeof(scid),\n                                       (struct sockaddr *) &conn_io->local_addr,\n                                       conn_io->local_addr_len,\n                                       peer->ai_addr, peer->ai_addrlen, config);\n\n    if (conn == NULL) {\n        fprintf(stderr, \"failed to create connection\\n\");\n        return -1;\n    }\n\n    conn_io->sock = sock;\n    conn_io->conn = conn;\n    conn_io->host = host;\n\n    ev_io watcher;\n\n    struct ev_loop *loop = ev_default_loop(0);\n\n    ev_io_init(&watcher, recv_cb, conn_io->sock, EV_READ);\n    ev_io_start(loop, &watcher);\n    watcher.data = conn_io;\n\n    ev_init(&conn_io->timer, timeout_cb);\n    conn_io->timer.data = conn_io;\n\n    flush_egress(loop, conn_io);\n\n    ev_loop(loop, 0);\n\n    freeaddrinfo(peer);\n\n    quiche_h3_conn_free(conn_io->http3);\n\n    quiche_conn_free(conn);\n\n    quiche_config_free(config);\n\n    return 0;\n}\n"
  },
  {
    "path": "quiche/examples/http3-client.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[macro_use]\nextern crate log;\n\nuse quiche::h3::NameValue;\n\nuse ring::rand::*;\n\nconst MAX_DATAGRAM_SIZE: usize = 1350;\n\nfn main() {\n    let mut buf = [0; 65535];\n    let mut out = [0; MAX_DATAGRAM_SIZE];\n\n    let mut args = std::env::args();\n\n    let cmd = &args.next().unwrap();\n\n    if args.len() != 1 {\n        println!(\"Usage: {cmd} URL\");\n        println!(\"\\nSee tools/apps/ for more complete implementations.\");\n        return;\n    }\n\n    let url = url::Url::parse(&args.next().unwrap()).unwrap();\n\n    // Setup the event loop.\n    let mut poll = mio::Poll::new().unwrap();\n    let mut events = mio::Events::with_capacity(1024);\n\n    // Resolve server address.\n    let peer_addr = url.socket_addrs(|| None).unwrap()[0];\n\n    // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the\n    // server address. This is needed on macOS and BSD variants that don't\n    // support binding to IN6ADDR_ANY for both v4 and v6.\n    let bind_addr = match peer_addr {\n        std::net::SocketAddr::V4(_) => \"0.0.0.0:0\",\n        std::net::SocketAddr::V6(_) => \"[::]:0\",\n    };\n\n    // Create the UDP socket backing the QUIC connection, and register it with\n    // the event loop.\n    let mut socket =\n        mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap();\n    poll.registry()\n        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)\n        .unwrap();\n\n    // Create the configuration for the QUIC connection.\n    let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n\n    // *CAUTION*: this should not be set to `false` in production!!!\n    config.verify_peer(false);\n\n    config\n        .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)\n        .unwrap();\n\n    config.set_max_idle_timeout(5000);\n    config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_initial_max_data(10_000_000);\n    config.set_initial_max_stream_data_bidi_local(1_000_000);\n    config.set_initial_max_stream_data_bidi_remote(1_000_000);\n    config.set_initial_max_stream_data_uni(1_000_000);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(100);\n    config.set_disable_active_migration(true);\n\n    let mut http3_conn = None;\n\n    // Generate a random source connection ID for the connection.\n    let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n    SystemRandom::new().fill(&mut scid[..]).unwrap();\n\n    let scid = quiche::ConnectionId::from_ref(&scid);\n\n    // Get local address.\n    let local_addr = socket.local_addr().unwrap();\n\n    // Create a QUIC connection and initiate handshake.\n    let mut conn =\n        quiche::connect(url.domain(), &scid, local_addr, peer_addr, &mut config)\n            .unwrap();\n\n    info!(\n        \"connecting to {:} from {:} with scid {}\",\n        peer_addr,\n        socket.local_addr().unwrap(),\n        hex_dump(&scid)\n    );\n\n    let (write, send_info) = conn.send(&mut out).expect(\"initial send failed\");\n\n    while let Err(e) = socket.send_to(&out[..write], send_info.to) {\n        if e.kind() == std::io::ErrorKind::WouldBlock {\n            debug!(\"send() would block\");\n            continue;\n        }\n\n        panic!(\"send() failed: {e:?}\");\n    }\n\n    debug!(\"written {write}\");\n\n    let h3_config = quiche::h3::Config::new().unwrap();\n\n    // Prepare request.\n    let mut path = String::from(url.path());\n\n    if let Some(query) = url.query() {\n        path.push('?');\n        path.push_str(query);\n    }\n\n    let req = vec![\n        quiche::h3::Header::new(b\":method\", b\"GET\"),\n        quiche::h3::Header::new(b\":scheme\", url.scheme().as_bytes()),\n        quiche::h3::Header::new(\n            b\":authority\",\n            url.host_str().unwrap().as_bytes(),\n        ),\n        quiche::h3::Header::new(b\":path\", path.as_bytes()),\n        quiche::h3::Header::new(b\"user-agent\", b\"quiche\"),\n    ];\n\n    let req_start = std::time::Instant::now();\n\n    let mut req_sent = false;\n\n    loop {\n        poll.poll(&mut events, conn.timeout()).unwrap();\n\n        // Read incoming UDP packets from the socket and feed them to quiche,\n        // until there are no more packets to read.\n        'read: loop {\n            // If the event loop reported no events, it means that the timeout\n            // has expired, so handle it without attempting to read packets. We\n            // will then proceed with the send loop.\n            if events.is_empty() {\n                debug!(\"timed out\");\n\n                conn.on_timeout();\n\n                break 'read;\n            }\n\n            let (len, from) = match socket.recv_from(&mut buf) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    // There are no more UDP packets to read, so end the read\n                    // loop.\n                    if e.kind() == std::io::ErrorKind::WouldBlock {\n                        debug!(\"recv() would block\");\n                        break 'read;\n                    }\n\n                    panic!(\"recv() failed: {e:?}\");\n                },\n            };\n\n            debug!(\"got {len} bytes\");\n\n            let recv_info = quiche::RecvInfo {\n                to: local_addr,\n                from,\n            };\n\n            // Process potentially coalesced packets.\n            let read = match conn.recv(&mut buf[..len], recv_info) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    error!(\"recv failed: {e:?}\");\n                    continue 'read;\n                },\n            };\n\n            debug!(\"processed {read} bytes\");\n        }\n\n        debug!(\"done reading\");\n\n        if conn.is_closed() {\n            info!(\"connection closed, {:?}\", conn.stats());\n            break;\n        }\n\n        // Create a new HTTP/3 connection once the QUIC connection is established.\n        if conn.is_established() && http3_conn.is_none() {\n            http3_conn = Some(\n                quiche::h3::Connection::with_transport(&mut conn, &h3_config)\n                .expect(\"Unable to create HTTP/3 connection, check the server's uni stream limit and window size\"),\n            );\n        }\n\n        // Send HTTP requests once the QUIC connection is established, and until\n        // all requests have been sent.\n        if let Some(h3_conn) = &mut http3_conn {\n            if !req_sent {\n                info!(\"sending HTTP request {req:?}\");\n\n                h3_conn.send_request(&mut conn, &req, true).unwrap();\n\n                req_sent = true;\n            }\n        }\n\n        if let Some(http3_conn) = &mut http3_conn {\n            // Process HTTP/3 events.\n            loop {\n                match http3_conn.poll(&mut conn) {\n                    Ok((stream_id, quiche::h3::Event::Headers { list, .. })) => {\n                        info!(\n                            \"got response headers {:?} on stream id {}\",\n                            hdrs_to_strings(&list),\n                            stream_id\n                        );\n                    },\n\n                    Ok((stream_id, quiche::h3::Event::Data)) => {\n                        while let Ok(read) =\n                            http3_conn.recv_body(&mut conn, stream_id, &mut buf)\n                        {\n                            debug!(\n                                \"got {read} bytes of response data on stream {stream_id}\"\n                            );\n\n                            print!(\"{}\", unsafe {\n                                std::str::from_utf8_unchecked(&buf[..read])\n                            });\n                        }\n                    },\n\n                    Ok((_stream_id, quiche::h3::Event::Finished)) => {\n                        info!(\n                            \"response received in {:?}, closing...\",\n                            req_start.elapsed()\n                        );\n\n                        conn.close(true, 0x100, b\"kthxbye\").unwrap();\n                    },\n\n                    Ok((_stream_id, quiche::h3::Event::Reset(e))) => {\n                        error!(\"request was reset by peer with {e}, closing...\");\n\n                        conn.close(true, 0x100, b\"kthxbye\").unwrap();\n                    },\n\n                    Ok((_, quiche::h3::Event::PriorityUpdate)) => unreachable!(),\n\n                    Ok((goaway_id, quiche::h3::Event::GoAway)) => {\n                        info!(\"GOAWAY id={goaway_id}\");\n                    },\n\n                    Err(quiche::h3::Error::Done) => {\n                        break;\n                    },\n\n                    Err(e) => {\n                        error!(\"HTTP/3 processing failed: {e:?}\");\n\n                        break;\n                    },\n                }\n            }\n        }\n\n        // Generate outgoing QUIC packets and send them on the UDP socket, until\n        // quiche reports that there are no more packets to be sent.\n        loop {\n            let (write, send_info) = match conn.send(&mut out) {\n                Ok(v) => v,\n\n                Err(quiche::Error::Done) => {\n                    debug!(\"done writing\");\n                    break;\n                },\n\n                Err(e) => {\n                    error!(\"send failed: {e:?}\");\n\n                    conn.close(false, 0x1, b\"fail\").ok();\n                    break;\n                },\n            };\n\n            if let Err(e) = socket.send_to(&out[..write], send_info.to) {\n                if e.kind() == std::io::ErrorKind::WouldBlock {\n                    debug!(\"send() would block\");\n                    break;\n                }\n\n                panic!(\"send() failed: {e:?}\");\n            }\n\n            debug!(\"written {write}\");\n        }\n\n        if conn.is_closed() {\n            info!(\"connection closed, {:?}\", conn.stats());\n            break;\n        }\n    }\n}\n\nfn hex_dump(buf: &[u8]) -> String {\n    let vec: Vec<String> = buf.iter().map(|b| format!(\"{b:02x}\")).collect();\n\n    vec.join(\"\")\n}\n\npub fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> {\n    hdrs.iter()\n        .map(|h| {\n            let name = String::from_utf8_lossy(h.name()).to_string();\n            let value = String::from_utf8_lossy(h.value()).to_string();\n\n            (name, value)\n        })\n        .collect()\n}\n"
  },
  {
    "path": "quiche/examples/http3-server.c",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n//       notice, this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#include <inttypes.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <unistd.h>\n\n#include <fcntl.h>\n#include <errno.h>\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netdb.h>\n\n#include <ev.h>\n#include <uthash.h>\n\n#include <quiche.h>\n\n#define LOCAL_CONN_ID_LEN 16\n\n#define MAX_DATAGRAM_SIZE 1350\n\n#define MAX_TOKEN_LEN \\\n    sizeof(\"quiche\") - 1 + \\\n    sizeof(struct sockaddr_storage) + \\\n    QUICHE_MAX_CONN_ID_LEN\n\nstruct connections {\n    int sock;\n\n    struct sockaddr *local_addr;\n    socklen_t local_addr_len;\n\n    struct conn_io *h;\n};\n\nstruct conn_io {\n    ev_timer timer;\n\n    int sock;\n\n    uint8_t cid[LOCAL_CONN_ID_LEN];\n\n    quiche_conn *conn;\n    quiche_h3_conn *http3;\n\n    struct sockaddr_storage peer_addr;\n    socklen_t peer_addr_len;\n\n    UT_hash_handle hh;\n};\n\nstatic quiche_config *config = NULL;\n\nstatic quiche_h3_config *http3_config = NULL;\n\nstatic struct connections *conns = NULL;\n\nstatic void timeout_cb(EV_P_ ev_timer *w, int revents);\n\nstatic void debug_log(const char *line, void *argp) {\n    fprintf(stderr, \"%s\\n\", line);\n}\n\nstatic void flush_egress(struct ev_loop *loop, struct conn_io *conn_io) {\n    static uint8_t out[MAX_DATAGRAM_SIZE];\n\n    quiche_send_info send_info;\n\n    while (1) {\n        ssize_t written = quiche_conn_send(conn_io->conn, out, sizeof(out),\n                                           &send_info);\n\n        if (written == QUICHE_ERR_DONE) {\n            fprintf(stderr, \"done writing\\n\");\n            break;\n        }\n\n        if (written < 0) {\n            fprintf(stderr, \"failed to create packet: %zd\\n\", written);\n            return;\n        }\n\n        ssize_t sent = sendto(conn_io->sock, out, written, 0,\n                              (struct sockaddr *) &conn_io->peer_addr,\n                              conn_io->peer_addr_len);\n        if (sent != written) {\n            perror(\"failed to send\");\n            return;\n        }\n\n        fprintf(stderr, \"sent %zd bytes\\n\", sent);\n    }\n\n    double t = quiche_conn_timeout_as_nanos(conn_io->conn) / 1e9f;\n    conn_io->timer.repeat = t;\n    ev_timer_again(loop, &conn_io->timer);\n}\n\nstatic void mint_token(const uint8_t *dcid, size_t dcid_len,\n                       struct sockaddr_storage *addr, socklen_t addr_len,\n                       uint8_t *token, size_t *token_len) {\n    memcpy(token, \"quiche\", sizeof(\"quiche\") - 1);\n    memcpy(token + sizeof(\"quiche\") - 1, addr, addr_len);\n    memcpy(token + sizeof(\"quiche\") - 1 + addr_len, dcid, dcid_len);\n\n    *token_len = sizeof(\"quiche\") - 1 + addr_len + dcid_len;\n}\n\nstatic bool validate_token(const uint8_t *token, size_t token_len,\n                           struct sockaddr_storage *addr, socklen_t addr_len,\n                           uint8_t *odcid, size_t *odcid_len) {\n    if ((token_len < sizeof(\"quiche\") - 1) ||\n         memcmp(token, \"quiche\", sizeof(\"quiche\") - 1)) {\n        return false;\n    }\n\n    token += sizeof(\"quiche\") - 1;\n    token_len -= sizeof(\"quiche\") - 1;\n\n    if ((token_len < addr_len) || memcmp(token, addr, addr_len)) {\n        return false;\n    }\n\n    token += addr_len;\n    token_len -= addr_len;\n\n    if (*odcid_len < token_len) {\n        return false;\n    }\n\n    memcpy(odcid, token, token_len);\n    *odcid_len = token_len;\n\n    return true;\n}\n\nstatic uint8_t *gen_cid(uint8_t *cid, size_t cid_len) {\n    int rng = open(\"/dev/urandom\", O_RDONLY);\n    if (rng < 0) {\n        perror(\"failed to open /dev/urandom\");\n        return NULL;\n    }\n\n    ssize_t rand_len = read(rng, cid, cid_len);\n    if (rand_len < 0) {\n        perror(\"failed to create connection ID\");\n        return NULL;\n    }\n\n    return cid;\n}\n\nstatic struct conn_io *create_conn(uint8_t *scid, size_t scid_len,\n                                   uint8_t *odcid, size_t odcid_len,\n                                   struct sockaddr *local_addr,\n                                   socklen_t local_addr_len,\n                                   struct sockaddr_storage *peer_addr,\n                                   socklen_t peer_addr_len)\n{\n    struct conn_io *conn_io = calloc(1, sizeof(*conn_io));\n    if (conn_io == NULL) {\n        fprintf(stderr, \"failed to allocate connection IO\\n\");\n        return NULL;\n    }\n\n    if (scid_len != LOCAL_CONN_ID_LEN) {\n        fprintf(stderr, \"failed, scid length too short\\n\");\n    }\n\n    memcpy(conn_io->cid, scid, LOCAL_CONN_ID_LEN);\n\n    quiche_conn *conn = quiche_accept(conn_io->cid, LOCAL_CONN_ID_LEN,\n                                      odcid, odcid_len,\n                                      local_addr,\n                                      local_addr_len,\n                                      (struct sockaddr *) peer_addr,\n                                      peer_addr_len,\n                                      config);\n\n    if (conn == NULL) {\n        fprintf(stderr, \"failed to create connection\\n\");\n        return NULL;\n    }\n\n    conn_io->sock = conns->sock;\n    conn_io->conn = conn;\n\n    memcpy(&conn_io->peer_addr, peer_addr, peer_addr_len);\n    conn_io->peer_addr_len = peer_addr_len;\n\n    ev_init(&conn_io->timer, timeout_cb);\n    conn_io->timer.data = conn_io;\n\n    HASH_ADD(hh, conns->h, cid, LOCAL_CONN_ID_LEN, conn_io);\n\n    fprintf(stderr, \"new connection\\n\");\n\n    return conn_io;\n}\n\nstatic int for_each_header(uint8_t *name, size_t name_len,\n                           uint8_t *value, size_t value_len,\n                           void *argp) {\n    fprintf(stderr, \"got HTTP header: %.*s=%.*s\\n\",\n            (int) name_len, name, (int) value_len, value);\n\n    return 0;\n}\n\nstatic void recv_cb(EV_P_ ev_io *w, int revents) {\n    struct conn_io *tmp, *conn_io = NULL;\n\n    static uint8_t buf[65535];\n    static uint8_t out[MAX_DATAGRAM_SIZE];\n\n    while (1) {\n        struct sockaddr_storage peer_addr;\n        socklen_t peer_addr_len = sizeof(peer_addr);\n        memset(&peer_addr, 0, peer_addr_len);\n\n        ssize_t read = recvfrom(conns->sock, buf, sizeof(buf), 0,\n                                (struct sockaddr *) &peer_addr,\n                                &peer_addr_len);\n\n        if (read < 0) {\n            if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {\n                fprintf(stderr, \"recv would block\\n\");\n                break;\n            }\n\n            perror(\"failed to read\");\n            return;\n        }\n\n        uint8_t type;\n        uint32_t version;\n\n        uint8_t scid[QUICHE_MAX_CONN_ID_LEN];\n        size_t scid_len = sizeof(scid);\n\n        uint8_t dcid[QUICHE_MAX_CONN_ID_LEN];\n        size_t dcid_len = sizeof(dcid);\n\n        uint8_t odcid[QUICHE_MAX_CONN_ID_LEN];\n        size_t odcid_len = sizeof(odcid);\n\n        uint8_t token[MAX_TOKEN_LEN];\n        size_t token_len = sizeof(token);\n\n        int rc = quiche_header_info(buf, read, LOCAL_CONN_ID_LEN, &version,\n                                    &type, scid, &scid_len, dcid, &dcid_len,\n                                    token, &token_len);\n        if (rc < 0) {\n            fprintf(stderr, \"failed to parse header: %d\\n\", rc);\n            return;\n        }\n\n        HASH_FIND(hh, conns->h, dcid, dcid_len, conn_io);\n\n        if (conn_io == NULL) {\n            if (!quiche_version_is_supported(version)) {\n                fprintf(stderr, \"version negotiation\\n\");\n\n                ssize_t written = quiche_negotiate_version(scid, scid_len,\n                                                           dcid, dcid_len,\n                                                           out, sizeof(out));\n\n                if (written < 0) {\n                    fprintf(stderr, \"failed to create vneg packet: %zd\\n\",\n                            written);\n                    continue;\n                }\n\n                ssize_t sent = sendto(conns->sock, out, written, 0,\n                                      (struct sockaddr *) &peer_addr,\n                                      peer_addr_len);\n                if (sent != written) {\n                    perror(\"failed to send\");\n                    continue;\n                }\n\n                fprintf(stderr, \"sent %zd bytes\\n\", sent);\n                continue;\n            }\n\n            if (token_len == 0) {\n                fprintf(stderr, \"stateless retry\\n\");\n\n                mint_token(dcid, dcid_len, &peer_addr, peer_addr_len,\n                           token, &token_len);\n\n                uint8_t new_cid[LOCAL_CONN_ID_LEN];\n\n                if (gen_cid(new_cid, LOCAL_CONN_ID_LEN) == NULL) {\n                    continue;\n                }\n\n                ssize_t written = quiche_retry(scid, scid_len,\n                                               dcid, dcid_len,\n                                               new_cid, LOCAL_CONN_ID_LEN,\n                                               token, token_len,\n                                               version, out, sizeof(out));\n\n                if (written < 0) {\n                    fprintf(stderr, \"failed to create retry packet: %zd\\n\",\n                            written);\n                    continue;\n                }\n\n                ssize_t sent = sendto(conns->sock, out, written, 0,\n                                      (struct sockaddr *) &peer_addr,\n                                      peer_addr_len);\n                if (sent != written) {\n                    perror(\"failed to send\");\n                    continue;\n                }\n\n                fprintf(stderr, \"sent %zd bytes\\n\", sent);\n                continue;\n            }\n\n\n            if (!validate_token(token, token_len, &peer_addr, peer_addr_len,\n                               odcid, &odcid_len)) {\n                fprintf(stderr, \"invalid address validation token\\n\");\n                continue;\n            }\n\n            conn_io = create_conn(dcid, dcid_len, odcid, odcid_len,\n                                  conns->local_addr, conns->local_addr_len,\n                                  &peer_addr, peer_addr_len);\n\n            if (conn_io == NULL) {\n                continue;\n            }\n        }\n\n        quiche_recv_info recv_info = {\n            (struct sockaddr *)&peer_addr,\n            peer_addr_len,\n\n            conns->local_addr,\n            conns->local_addr_len,\n        };\n\n        ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info);\n\n        if (done < 0) {\n            fprintf(stderr, \"failed to process packet: %zd\\n\", done);\n            continue;\n        }\n\n        fprintf(stderr, \"recv %zd bytes\\n\", done);\n\n        if (quiche_conn_is_established(conn_io->conn)) {\n            quiche_h3_event *ev;\n\n            if (conn_io->http3 == NULL) {\n                conn_io->http3 = quiche_h3_conn_new_with_transport(conn_io->conn,\n                                                                   http3_config);\n                if (conn_io->http3 == NULL) {\n                    fprintf(stderr, \"failed to create HTTP/3 connection\\n\");\n                    continue;\n                }\n            }\n\n            while (1) {\n                int64_t s = quiche_h3_conn_poll(conn_io->http3,\n                                                conn_io->conn,\n                                                &ev);\n\n                if (s < 0) {\n                    break;\n                }\n\n                switch (quiche_h3_event_type(ev)) {\n                    case QUICHE_H3_EVENT_HEADERS: {\n                        int rc = quiche_h3_event_for_each_header(ev,\n                                                                 for_each_header,\n                                                                 NULL);\n\n                        if (rc != 0) {\n                            fprintf(stderr, \"failed to process headers\\n\");\n                        }\n\n                        quiche_h3_header headers[] = {\n                            {\n                                .name = (const uint8_t *) \":status\",\n                                .name_len = sizeof(\":status\") - 1,\n\n                                .value = (const uint8_t *) \"200\",\n                                .value_len = sizeof(\"200\") - 1,\n                            },\n\n                            {\n                                .name = (const uint8_t *) \"server\",\n                                .name_len = sizeof(\"server\") - 1,\n\n                                .value = (const uint8_t *) \"quiche\",\n                                .value_len = sizeof(\"quiche\") - 1,\n                            },\n\n                            {\n                                .name = (const uint8_t *) \"content-length\",\n                                .name_len = sizeof(\"content-length\") - 1,\n\n                                .value = (const uint8_t *) \"5\",\n                                .value_len = sizeof(\"5\") - 1,\n                            },\n                        };\n\n                        quiche_h3_send_response(conn_io->http3, conn_io->conn,\n                                                s, headers, 3, false);\n\n                        quiche_h3_send_body(conn_io->http3, conn_io->conn,\n                                            s, (uint8_t *) \"byez\\n\", 5, true);\n                        break;\n                    }\n\n                    case QUICHE_H3_EVENT_DATA: {\n                        fprintf(stderr, \"got HTTP data\\n\");\n                        break;\n                    }\n\n                    case QUICHE_H3_EVENT_FINISHED:\n                        break;\n\n                    case QUICHE_H3_EVENT_RESET:\n                        break;\n\n                    case QUICHE_H3_EVENT_PRIORITY_UPDATE:\n                        break;\n\n                    case QUICHE_H3_EVENT_GOAWAY: {\n                        fprintf(stderr, \"got GOAWAY\\n\");\n                        break;\n                    }\n                }\n\n                quiche_h3_event_free(ev);\n            }\n        }\n    }\n\n    HASH_ITER(hh, conns->h, conn_io, tmp) {\n        flush_egress(loop, conn_io);\n\n        if (quiche_conn_is_closed(conn_io->conn)) {\n            quiche_stats stats;\n            quiche_path_stats path_stats;\n\n            quiche_conn_stats(conn_io->conn, &stats);\n            quiche_conn_path_stats(conn_io->conn, 0, &path_stats);\n\n            fprintf(stderr, \"connection closed, recv=%zu sent=%zu lost=%zu rtt=%\" PRIu64 \"ns cwnd=%zu\\n\",\n                    stats.recv, stats.sent, stats.lost, path_stats.rtt, path_stats.cwnd);\n\n            HASH_DELETE(hh, conns->h, conn_io);\n\n            ev_timer_stop(loop, &conn_io->timer);\n\n            quiche_conn_free(conn_io->conn);\n            free(conn_io);\n        }\n    }\n}\n\nstatic void timeout_cb(EV_P_ ev_timer *w, int revents) {\n    struct conn_io *conn_io = w->data;\n    quiche_conn_on_timeout(conn_io->conn);\n\n    fprintf(stderr, \"timeout\\n\");\n\n    flush_egress(loop, conn_io);\n\n    if (quiche_conn_is_closed(conn_io->conn)) {\n        quiche_stats stats;\n        quiche_path_stats path_stats;\n\n        quiche_conn_stats(conn_io->conn, &stats);\n        quiche_conn_path_stats(conn_io->conn, 0, &path_stats);\n\n        fprintf(stderr, \"connection closed, recv=%zu sent=%zu lost=%zu rtt=%\" PRIu64 \"ns cwnd=%zu\\n\",\n                stats.recv, stats.sent, stats.lost, path_stats.rtt, path_stats.cwnd);\n\n        HASH_DELETE(hh, conns->h, conn_io);\n\n        ev_timer_stop(loop, &conn_io->timer);\n        quiche_conn_free(conn_io->conn);\n        free(conn_io);\n\n        return;\n    }\n}\n\nint main(int argc, char *argv[]) {\n    const char *host = argv[1];\n    const char *port = argv[2];\n\n    const struct addrinfo hints = {\n        .ai_family = PF_UNSPEC,\n        .ai_socktype = SOCK_DGRAM,\n        .ai_protocol = IPPROTO_UDP\n    };\n\n    quiche_enable_debug_logging(debug_log, NULL);\n\n    struct addrinfo *local;\n    if (getaddrinfo(host, port, &hints, &local) != 0) {\n        perror(\"failed to resolve host\");\n        return -1;\n    }\n\n    int sock = socket(local->ai_family, SOCK_DGRAM, 0);\n    if (sock < 0) {\n        perror(\"failed to create socket\");\n        return -1;\n    }\n\n    if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {\n        perror(\"failed to make socket non-blocking\");\n        return -1;\n    }\n\n    if (bind(sock, local->ai_addr, local->ai_addrlen) < 0) {\n        perror(\"failed to connect socket\");\n        return -1;\n    }\n\n    config = quiche_config_new(QUICHE_PROTOCOL_VERSION);\n    if (config == NULL) {\n        fprintf(stderr, \"failed to create config\\n\");\n        return -1;\n    }\n\n    quiche_config_load_cert_chain_from_pem_file(config, \"./cert.crt\");\n    quiche_config_load_priv_key_from_pem_file(config, \"./cert.key\");\n\n    quiche_config_set_application_protos(config,\n        (uint8_t *) QUICHE_H3_APPLICATION_PROTOCOL,\n        sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);\n\n    quiche_config_set_max_idle_timeout(config, 5000);\n    quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);\n    quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);\n    quiche_config_set_initial_max_data(config, 10000000);\n    quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000);\n    quiche_config_set_initial_max_stream_data_bidi_remote(config, 1000000);\n    quiche_config_set_initial_max_stream_data_uni(config, 1000000);\n    quiche_config_set_initial_max_streams_bidi(config, 100);\n    quiche_config_set_initial_max_streams_uni(config, 100);\n    quiche_config_set_disable_active_migration(config, true);\n    quiche_config_set_cc_algorithm(config, QUICHE_CC_RENO);\n\n    http3_config = quiche_h3_config_new();\n    if (http3_config == NULL) {\n        fprintf(stderr, \"failed to create HTTP/3 config\\n\");\n        return -1;\n    }\n\n    struct connections c;\n    c.sock = sock;\n    c.h = NULL;\n    c.local_addr = local->ai_addr;\n    c.local_addr_len = local->ai_addrlen;\n\n    conns = &c;\n\n    ev_io watcher;\n\n    struct ev_loop *loop = ev_default_loop(0);\n\n    ev_io_init(&watcher, recv_cb, sock, EV_READ);\n    ev_io_start(loop, &watcher);\n    watcher.data = &c;\n\n    ev_loop(loop, 0);\n\n    freeaddrinfo(local);\n\n    quiche_h3_config_free(http3_config);\n\n    quiche_config_free(config);\n\n    return 0;\n}\n"
  },
  {
    "path": "quiche/examples/http3-server.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[macro_use]\nextern crate log;\n\nuse std::net;\n\nuse std::collections::HashMap;\n\nuse ring::rand::*;\n\nuse quiche::h3::NameValue;\n\nconst MAX_DATAGRAM_SIZE: usize = 1350;\n\nstruct PartialResponse {\n    headers: Option<Vec<quiche::h3::Header>>,\n\n    body: Vec<u8>,\n\n    written: usize,\n}\n\nstruct Client {\n    conn: quiche::Connection,\n\n    http3_conn: Option<quiche::h3::Connection>,\n\n    partial_responses: HashMap<u64, PartialResponse>,\n}\n\ntype ClientMap = HashMap<quiche::ConnectionId<'static>, Client>;\n\nfn main() {\n    let mut buf = [0; 65535];\n    let mut out = [0; MAX_DATAGRAM_SIZE];\n\n    let mut args = std::env::args();\n\n    let cmd = &args.next().unwrap();\n\n    if args.len() != 0 {\n        println!(\"Usage: {cmd}\");\n        println!(\"\\nSee tools/apps/ for more complete implementations.\");\n        return;\n    }\n\n    // Setup the event loop.\n    let mut poll = mio::Poll::new().unwrap();\n    let mut events = mio::Events::with_capacity(1024);\n\n    // Create the UDP listening socket, and register it with the event loop.\n    let mut socket =\n        mio::net::UdpSocket::bind(\"127.0.0.1:4433\".parse().unwrap()).unwrap();\n    poll.registry()\n        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)\n        .unwrap();\n\n    // Create the configuration for the QUIC connections.\n    let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n\n    config\n        .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)\n        .unwrap();\n\n    config.set_max_idle_timeout(5000);\n    config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_initial_max_data(10_000_000);\n    config.set_initial_max_stream_data_bidi_local(1_000_000);\n    config.set_initial_max_stream_data_bidi_remote(1_000_000);\n    config.set_initial_max_stream_data_uni(1_000_000);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(100);\n    config.set_disable_active_migration(true);\n    config.enable_early_data();\n\n    let h3_config = quiche::h3::Config::new().unwrap();\n\n    let rng = SystemRandom::new();\n    let conn_id_seed =\n        ring::hmac::Key::generate(ring::hmac::HMAC_SHA256, &rng).unwrap();\n\n    let mut clients = ClientMap::new();\n\n    let local_addr = socket.local_addr().unwrap();\n\n    loop {\n        // Find the shorter timeout from all the active connections.\n        //\n        // TODO: use event loop that properly supports timers\n        let timeout = clients.values().filter_map(|c| c.conn.timeout()).min();\n\n        poll.poll(&mut events, timeout).unwrap();\n\n        // Read incoming UDP packets from the socket and feed them to quiche,\n        // until there are no more packets to read.\n        'read: loop {\n            // If the event loop reported no events, it means that the timeout\n            // has expired, so handle it without attempting to read packets. We\n            // will then proceed with the send loop.\n            if events.is_empty() {\n                debug!(\"timed out\");\n\n                clients.values_mut().for_each(|c| c.conn.on_timeout());\n\n                break 'read;\n            }\n\n            let (len, from) = match socket.recv_from(&mut buf) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    // There are no more UDP packets to read, so end the read\n                    // loop.\n                    if e.kind() == std::io::ErrorKind::WouldBlock {\n                        debug!(\"recv() would block\");\n                        break 'read;\n                    }\n\n                    panic!(\"recv() failed: {e:?}\");\n                },\n            };\n\n            debug!(\"got {len} bytes\");\n\n            let pkt_buf = &mut buf[..len];\n\n            // Parse the QUIC packet's header.\n            let hdr = match quiche::Header::from_slice(\n                pkt_buf,\n                quiche::MAX_CONN_ID_LEN,\n            ) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    error!(\"Parsing packet header failed: {e:?}\");\n                    continue 'read;\n                },\n            };\n\n            trace!(\"got packet {hdr:?}\");\n\n            let conn_id = ring::hmac::sign(&conn_id_seed, &hdr.dcid);\n            let conn_id = &conn_id.as_ref()[..quiche::MAX_CONN_ID_LEN];\n            let conn_id = conn_id.to_vec().into();\n\n            // Lookup a connection based on the packet's connection ID. If there\n            // is no connection matching, create a new one.\n            let client = if !clients.contains_key(&hdr.dcid) &&\n                !clients.contains_key(&conn_id)\n            {\n                if hdr.ty != quiche::Type::Initial {\n                    error!(\"Packet is not Initial\");\n                    continue 'read;\n                }\n\n                if !quiche::version_is_supported(hdr.version) {\n                    warn!(\"Doing version negotiation\");\n\n                    let len =\n                        quiche::negotiate_version(&hdr.scid, &hdr.dcid, &mut out)\n                            .unwrap();\n\n                    let out = &out[..len];\n\n                    if let Err(e) = socket.send_to(out, from) {\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            debug!(\"send() would block\");\n                            break;\n                        }\n\n                        panic!(\"send() failed: {e:?}\");\n                    }\n                    continue 'read;\n                }\n\n                let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n                scid.copy_from_slice(&conn_id);\n\n                let scid = quiche::ConnectionId::from_ref(&scid);\n\n                // Token is always present in Initial packets.\n                let token = hdr.token.as_ref().unwrap();\n\n                // Do stateless retry if the client didn't send a token.\n                if token.is_empty() {\n                    warn!(\"Doing stateless retry\");\n\n                    let new_token = mint_token(&hdr, &from);\n\n                    let len = quiche::retry(\n                        &hdr.scid,\n                        &hdr.dcid,\n                        &scid,\n                        &new_token,\n                        hdr.version,\n                        &mut out,\n                    )\n                    .unwrap();\n\n                    let out = &out[..len];\n\n                    if let Err(e) = socket.send_to(out, from) {\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            debug!(\"send() would block\");\n                            break;\n                        }\n\n                        panic!(\"send() failed: {e:?}\");\n                    }\n                    continue 'read;\n                }\n\n                let odcid = validate_token(&from, token);\n\n                // The token was not valid, meaning the retry failed, so\n                // drop the packet.\n                if odcid.is_none() {\n                    error!(\"Invalid address validation token\");\n                    continue 'read;\n                }\n\n                if scid.len() != hdr.dcid.len() {\n                    error!(\"Invalid destination connection ID\");\n                    continue 'read;\n                }\n\n                // Reuse the source connection ID we sent in the Retry packet,\n                // instead of changing it again.\n                let scid = hdr.dcid.clone();\n\n                debug!(\"New connection: dcid={:?} scid={:?}\", hdr.dcid, scid);\n\n                let conn = quiche::accept(\n                    &scid,\n                    odcid.as_ref(),\n                    local_addr,\n                    from,\n                    &mut config,\n                )\n                .unwrap();\n\n                let client = Client {\n                    conn,\n                    http3_conn: None,\n                    partial_responses: HashMap::new(),\n                };\n\n                clients.insert(scid.clone(), client);\n\n                clients.get_mut(&scid).unwrap()\n            } else {\n                match clients.get_mut(&hdr.dcid) {\n                    Some(v) => v,\n\n                    None => clients.get_mut(&conn_id).unwrap(),\n                }\n            };\n\n            let recv_info = quiche::RecvInfo {\n                to: socket.local_addr().unwrap(),\n                from,\n            };\n\n            // Process potentially coalesced packets.\n            let read = match client.conn.recv(pkt_buf, recv_info) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    error!(\"{} recv failed: {:?}\", client.conn.trace_id(), e);\n                    continue 'read;\n                },\n            };\n\n            debug!(\"{} processed {} bytes\", client.conn.trace_id(), read);\n\n            // Create a new HTTP/3 connection as soon as the QUIC connection\n            // is established.\n            if (client.conn.is_in_early_data() || client.conn.is_established()) &&\n                client.http3_conn.is_none()\n            {\n                debug!(\n                    \"{} QUIC handshake completed, now trying HTTP/3\",\n                    client.conn.trace_id()\n                );\n\n                let h3_conn = match quiche::h3::Connection::with_transport(\n                    &mut client.conn,\n                    &h3_config,\n                ) {\n                    Ok(v) => v,\n\n                    Err(e) => {\n                        error!(\"failed to create HTTP/3 connection: {e}\");\n                        continue 'read;\n                    },\n                };\n\n                // TODO: sanity check h3 connection before adding to map\n                client.http3_conn = Some(h3_conn);\n            }\n\n            if client.http3_conn.is_some() {\n                // Handle writable streams.\n                for stream_id in client.conn.writable() {\n                    handle_writable(client, stream_id);\n                }\n            }\n\n            if let Some(http3_conn) = client.http3_conn.as_mut() {\n                // Process HTTP/3 events.\n                loop {\n                    match http3_conn.poll(&mut client.conn) {\n                        Ok((\n                            stream_id,\n                            quiche::h3::Event::Headers { list, .. },\n                        )) => {\n                            handle_request(\n                                &mut client.conn,\n                                http3_conn,\n                                stream_id,\n                                &list,\n                                &mut client.partial_responses,\n                                \"examples/root\",\n                            );\n                        },\n\n                        Ok((stream_id, quiche::h3::Event::Data)) => {\n                            info!(\n                                \"{} got data on stream id {}\",\n                                client.conn.trace_id(),\n                                stream_id\n                            );\n                        },\n\n                        Ok((_stream_id, quiche::h3::Event::Finished)) => (),\n\n                        Ok((_stream_id, quiche::h3::Event::Reset { .. })) => (),\n\n                        Ok((\n                            _prioritized_element_id,\n                            quiche::h3::Event::PriorityUpdate,\n                        )) => (),\n\n                        Ok((_goaway_id, quiche::h3::Event::GoAway)) => (),\n\n                        Err(quiche::h3::Error::Done) => {\n                            break;\n                        },\n\n                        Err(e) => {\n                            error!(\n                                \"{} HTTP/3 error {:?}\",\n                                client.conn.trace_id(),\n                                e\n                            );\n\n                            break;\n                        },\n                    }\n                }\n            }\n        }\n\n        // Generate outgoing QUIC packets for all active connections and send\n        // them on the UDP socket, until quiche reports that there are no more\n        // packets to be sent.\n        for client in clients.values_mut() {\n            loop {\n                let (write, send_info) = match client.conn.send(&mut out) {\n                    Ok(v) => v,\n\n                    Err(quiche::Error::Done) => {\n                        debug!(\"{} done writing\", client.conn.trace_id());\n                        break;\n                    },\n\n                    Err(e) => {\n                        error!(\"{} send failed: {:?}\", client.conn.trace_id(), e);\n\n                        client.conn.close(false, 0x1, b\"fail\").ok();\n                        break;\n                    },\n                };\n\n                if let Err(e) = socket.send_to(&out[..write], send_info.to) {\n                    if e.kind() == std::io::ErrorKind::WouldBlock {\n                        debug!(\"send() would block\");\n                        break;\n                    }\n\n                    panic!(\"send() failed: {e:?}\");\n                }\n\n                debug!(\"{} written {} bytes\", client.conn.trace_id(), write);\n            }\n        }\n\n        // Garbage collect closed connections.\n        clients.retain(|_, ref mut c| {\n            debug!(\"Collecting garbage\");\n\n            if c.conn.is_closed() {\n                info!(\n                    \"{} connection collected {:?}\",\n                    c.conn.trace_id(),\n                    c.conn.stats()\n                );\n            }\n\n            !c.conn.is_closed()\n        });\n    }\n}\n\n/// Generate a stateless retry token.\n///\n/// The token includes the static string `\"quiche\"` followed by the IP address\n/// of the client and by the original destination connection ID generated by the\n/// client.\n///\n/// Note that this function is only an example and doesn't do any cryptographic\n/// authenticate of the token. *It should not be used in production system*.\nfn mint_token(hdr: &quiche::Header, src: &net::SocketAddr) -> Vec<u8> {\n    let mut token = Vec::new();\n\n    token.extend_from_slice(b\"quiche\");\n\n    let addr = match src.ip() {\n        std::net::IpAddr::V4(a) => a.octets().to_vec(),\n        std::net::IpAddr::V6(a) => a.octets().to_vec(),\n    };\n\n    token.extend_from_slice(&addr);\n    token.extend_from_slice(&hdr.dcid);\n\n    token\n}\n\n/// Validates a stateless retry token.\n///\n/// This checks that the ticket includes the `\"quiche\"` static string, and that\n/// the client IP address matches the address stored in the ticket.\n///\n/// Note that this function is only an example and doesn't do any cryptographic\n/// authenticate of the token. *It should not be used in production system*.\nfn validate_token<'a>(\n    src: &net::SocketAddr, token: &'a [u8],\n) -> Option<quiche::ConnectionId<'a>> {\n    if token.len() < 6 {\n        return None;\n    }\n\n    if &token[..6] != b\"quiche\" {\n        return None;\n    }\n\n    let token = &token[6..];\n\n    let addr = match src.ip() {\n        std::net::IpAddr::V4(a) => a.octets().to_vec(),\n        std::net::IpAddr::V6(a) => a.octets().to_vec(),\n    };\n\n    if token.len() < addr.len() || &token[..addr.len()] != addr.as_slice() {\n        return None;\n    }\n\n    Some(quiche::ConnectionId::from_ref(&token[addr.len()..]))\n}\n\n/// Handles incoming HTTP/3 requests.\nfn handle_request(\n    conn: &mut quiche::Connection, http3_conn: &mut quiche::h3::Connection,\n    stream_id: u64, headers: &[quiche::h3::Header],\n    partial_responses: &mut HashMap<u64, PartialResponse>, root: &str,\n) {\n    info!(\n        \"{} got request {:?} on stream id {}\",\n        conn.trace_id(),\n        hdrs_to_strings(headers),\n        stream_id\n    );\n\n    // We decide the response based on headers alone, so stop reading the\n    // request stream so that any body is ignored and pointless Data events\n    // are not generated.\n    conn.stream_shutdown(stream_id, quiche::Shutdown::Read, 0)\n        .unwrap();\n\n    let (headers, body) = build_response(root, headers);\n\n    match http3_conn.send_response(conn, stream_id, &headers, false) {\n        Ok(v) => v,\n\n        Err(quiche::h3::Error::StreamBlocked) => {\n            let response = PartialResponse {\n                headers: Some(headers),\n                body,\n                written: 0,\n            };\n\n            partial_responses.insert(stream_id, response);\n            return;\n        },\n\n        Err(e) => {\n            error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n            return;\n        },\n    }\n\n    let written = match http3_conn.send_body(conn, stream_id, &body, true) {\n        Ok(v) => v,\n\n        Err(quiche::h3::Error::Done) => 0,\n\n        Err(e) => {\n            error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n            return;\n        },\n    };\n\n    if written < body.len() {\n        let response = PartialResponse {\n            headers: None,\n            body,\n            written,\n        };\n\n        partial_responses.insert(stream_id, response);\n    }\n}\n\n/// Builds an HTTP/3 response given a request.\nfn build_response(\n    root: &str, request: &[quiche::h3::Header],\n) -> (Vec<quiche::h3::Header>, Vec<u8>) {\n    let mut file_path = std::path::PathBuf::from(root);\n    let mut path = std::path::Path::new(\"\");\n    let mut method = None;\n\n    // Look for the request's path and method.\n    for hdr in request {\n        match hdr.name() {\n            b\":path\" =>\n                path = std::path::Path::new(\n                    std::str::from_utf8(hdr.value()).unwrap(),\n                ),\n\n            b\":method\" => method = Some(hdr.value()),\n\n            _ => (),\n        }\n    }\n\n    let (status, body) = match method {\n        Some(b\"GET\") => {\n            for c in path.components() {\n                if let std::path::Component::Normal(v) = c {\n                    file_path.push(v)\n                }\n            }\n\n            match std::fs::read(file_path.as_path()) {\n                Ok(data) => (200, data),\n\n                Err(_) => (404, b\"Not Found!\".to_vec()),\n            }\n        },\n\n        _ => (405, Vec::new()),\n    };\n\n    let headers = vec![\n        quiche::h3::Header::new(b\":status\", status.to_string().as_bytes()),\n        quiche::h3::Header::new(b\"server\", b\"quiche\"),\n        quiche::h3::Header::new(\n            b\"content-length\",\n            body.len().to_string().as_bytes(),\n        ),\n    ];\n\n    (headers, body)\n}\n\n/// Handles newly writable streams.\nfn handle_writable(client: &mut Client, stream_id: u64) {\n    let conn = &mut client.conn;\n    let http3_conn = &mut client.http3_conn.as_mut().unwrap();\n\n    debug!(\"{} stream {} is writable\", conn.trace_id(), stream_id);\n\n    if !client.partial_responses.contains_key(&stream_id) {\n        return;\n    }\n\n    let resp = client.partial_responses.get_mut(&stream_id).unwrap();\n\n    if let Some(ref headers) = resp.headers {\n        match http3_conn.send_response(conn, stream_id, headers, false) {\n            Ok(_) => (),\n\n            Err(quiche::h3::Error::StreamBlocked) => {\n                return;\n            },\n\n            Err(e) => {\n                error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n                return;\n            },\n        }\n    }\n\n    resp.headers = None;\n\n    let body = &resp.body[resp.written..];\n\n    let written = match http3_conn.send_body(conn, stream_id, body, true) {\n        Ok(v) => v,\n\n        Err(quiche::h3::Error::Done) => 0,\n\n        Err(e) => {\n            client.partial_responses.remove(&stream_id);\n\n            error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n            return;\n        },\n    };\n\n    resp.written += written;\n\n    if resp.written == resp.body.len() {\n        client.partial_responses.remove(&stream_id);\n    }\n}\n\npub fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> {\n    hdrs.iter()\n        .map(|h| {\n            let name = String::from_utf8_lossy(h.name()).to_string();\n            let value = String::from_utf8_lossy(h.value()).to_string();\n\n            (name, value)\n        })\n        .collect()\n}\n"
  },
  {
    "path": "quiche/examples/qpack-decode.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[macro_use]\nextern crate log;\n\nuse std::fs::File;\n\nuse std::io::prelude::*;\n\nuse quiche::h3::NameValue;\n\nuse quiche::h3::qpack;\n\nfn main() {\n    // TODO: parse params from file name.\n\n    let mut args = std::env::args();\n\n    let cmd = &args.next().unwrap();\n\n    if args.len() != 1 {\n        println!(\"Usage: {cmd} FILE\");\n        return;\n    }\n\n    let mut file = File::open(args.next().unwrap()).unwrap();\n\n    let mut dec = qpack::Decoder::new();\n\n    loop {\n        let mut stream_id: [u8; 8] = [0; 8];\n        let mut len: [u8; 4] = [0; 4];\n\n        let _ = file.read(&mut stream_id).unwrap();\n        let stream_id = u64::from_be_bytes(stream_id);\n\n        let _ = file.read(&mut len).unwrap();\n        let len = u32::from_be_bytes(len) as usize;\n\n        let mut data = vec![0; len];\n\n        let data_len = file.read(&mut data).unwrap();\n\n        if data_len == 0 {\n            break;\n        }\n\n        debug!(\"Got stream={stream_id} len={len}\");\n\n        if stream_id == 0 {\n            dec.control(&mut data[..len]).unwrap();\n            continue;\n        }\n\n        for hdr in dec.decode(&data[..len], u64::MAX).unwrap() {\n            let name = std::str::from_utf8(hdr.name()).unwrap();\n            let value = std::str::from_utf8(hdr.value()).unwrap();\n            println!(\"{name}\\t{value}\");\n        }\n\n        println!();\n    }\n}\n"
  },
  {
    "path": "quiche/examples/qpack-encode.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[macro_use]\nextern crate log;\n\nuse std::fs::File;\n\nuse std::io::prelude::*;\nuse std::io::BufReader;\n\nuse quiche::h3;\n\nfn main() {\n    let mut args = std::env::args();\n\n    let cmd = &args.next().unwrap();\n\n    if args.len() != 1 {\n        println!(\"Usage: {cmd} FILE\");\n        return;\n    }\n\n    let file = File::open(args.next().unwrap()).unwrap();\n    let file = BufReader::new(&file);\n\n    let mut enc = h3::qpack::Encoder::new();\n\n    let mut headers: Vec<h3::Header> = Vec::new();\n\n    let mut stream_id = 1u64;\n\n    for line in file.lines().map(Result::unwrap) {\n        if line.starts_with('#') {\n            continue;\n        }\n\n        if line.is_empty() {\n            let mut out = [0u8; 65535];\n\n            let len = enc.encode(&headers, &mut out).unwrap();\n\n            debug!(\"Writing header block stream={stream_id} len={len}\");\n\n            std::io::stdout()\n                .write_all(&stream_id.to_be_bytes())\n                .unwrap();\n            std::io::stdout()\n                .write_all(&(len as u32).to_be_bytes())\n                .unwrap();\n            std::io::stdout().write_all(&out[..len]).unwrap();\n\n            stream_id += 1;\n\n            headers.clear();\n\n            continue;\n        }\n\n        let name = line.split('\\t').next().unwrap();\n        let value = line.split('\\t').next_back().unwrap();\n\n        headers.push(h3::Header::new(name.as_bytes(), value.as_bytes()));\n    }\n}\n"
  },
  {
    "path": "quiche/examples/rootca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDazCCAlOgAwIBAgIUAxoIpwJReHnJMSdGsRjjKRMdg/AwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAzMjMxNjA3NTRaFw00NzA4\nMDkxNjA3NTRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQCzwqx42InprkvjNlkfJNHY/FKJam6VG2D25SBeW0cw\n1Il38xLA9YQYTiSFePfLBt4cLnK3Na+opqg/2A9PG0iY9tpj5w2TmPnvWD+4AN5Y\n+KFwT9mGgbWSJ3vl2r/H5KU7qqBmfXPGYMHhIFU0objRKc40qww/tUCa8j3G4a5l\nzcKc2LpGeeeKtcpExJSkscXKNlMCLTIXcDVuX+i43KCacvKBg+hwNML8Jwg6pE9Y\nkvxJbnl8IfApexHKSrP6Kie9BiB4tVvbjGmQaiGM3zQAbTuaPD+le1ZYGnoxjzn6\n+cWpcPWPNEmM+zVWavIQD5rLxNW4dA4FhczLfGf1Cra7AgMBAAGjUzBRMB0GA1Ud\nDgQWBBRQ+lQtDANNRd9cfskISijXoCSiiDAfBgNVHSMEGDAWgBRQ+lQtDANNRd9c\nfskISijXoCSiiDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCK\ndfuwKWM8iou5wSNZIND433CiXpa24ZEIesurAJ6Y9QzQlbS/K6Kp/tM4gr/kqzfe\ni8dkUtCPKBCTDQ1nuQ4Wgf9hVnoN/uct7eYKoO7gJtySdP0TqHNDtDoHPMglHN04\nvjf4A1HOECbCjAt9PD46as65Tbjbs2wT6pdcYkHWLHLQ25I13yKK2bSNgXBYTbD0\nxQIF2nw8f+CnHk4Ho2+NFJ2gl8DKfELXevI11F2eoQIcJauLM4gFhAjHWwpRmU5W\npE3qMq2LzzmDnaBli7vDGJcUnyk1upUS5vM9+RKZYjH8aVydBzXvmnkP+rFYwwIt\nGwgB/MplEB8BXUTaVYB+\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "quiche/examples/server.c",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n//       notice, this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#include <inttypes.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <unistd.h>\n\n#include <fcntl.h>\n#include <errno.h>\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netdb.h>\n\n#include <ev.h>\n#include <uthash.h>\n\n#include <quiche.h>\n\n#define LOCAL_CONN_ID_LEN 16\n\n#define MAX_DATAGRAM_SIZE 1350\n\n#define MAX_TOKEN_LEN \\\n    sizeof(\"quiche\") - 1 + \\\n    sizeof(struct sockaddr_storage) + \\\n    QUICHE_MAX_CONN_ID_LEN\n\nstruct connections {\n    int sock;\n\n    struct sockaddr *local_addr;\n    socklen_t local_addr_len;\n\n    struct conn_io *h;\n};\n\nstruct conn_io {\n    ev_timer timer;\n\n    int sock;\n\n    uint8_t cid[LOCAL_CONN_ID_LEN];\n\n    quiche_conn *conn;\n\n    struct sockaddr_storage peer_addr;\n    socklen_t peer_addr_len;\n\n    UT_hash_handle hh;\n};\n\nstatic quiche_config *config = NULL;\n\nstatic struct connections *conns = NULL;\n\nstatic void timeout_cb(EV_P_ ev_timer *w, int revents);\n\nstatic void debug_log(const char *line, void *argp) {\n    fprintf(stderr, \"%s\\n\", line);\n}\n\nstatic void flush_egress(struct ev_loop *loop, struct conn_io *conn_io) {\n    static uint8_t out[MAX_DATAGRAM_SIZE];\n\n    quiche_send_info send_info;\n\n    while (1) {\n        ssize_t written = quiche_conn_send(conn_io->conn, out, sizeof(out),\n                                           &send_info);\n\n        if (written == QUICHE_ERR_DONE) {\n            fprintf(stderr, \"done writing\\n\");\n            break;\n        }\n\n        if (written < 0) {\n            fprintf(stderr, \"failed to create packet: %zd\\n\", written);\n            return;\n        }\n\n        ssize_t sent = sendto(conn_io->sock, out, written, 0,\n                              (struct sockaddr *) &send_info.to,\n                              send_info.to_len);\n\n        if (sent != written) {\n            perror(\"failed to send\");\n            return;\n        }\n\n        fprintf(stderr, \"sent %zd bytes\\n\", sent);\n    }\n\n    double t = quiche_conn_timeout_as_nanos(conn_io->conn) / 1e9f;\n    conn_io->timer.repeat = t;\n    ev_timer_again(loop, &conn_io->timer);\n}\n\nstatic void mint_token(const uint8_t *dcid, size_t dcid_len,\n                       struct sockaddr_storage *addr, socklen_t addr_len,\n                       uint8_t *token, size_t *token_len) {\n    memcpy(token, \"quiche\", sizeof(\"quiche\") - 1);\n    memcpy(token + sizeof(\"quiche\") - 1, addr, addr_len);\n    memcpy(token + sizeof(\"quiche\") - 1 + addr_len, dcid, dcid_len);\n\n    *token_len = sizeof(\"quiche\") - 1 + addr_len + dcid_len;\n}\n\nstatic bool validate_token(const uint8_t *token, size_t token_len,\n                           struct sockaddr_storage *addr, socklen_t addr_len,\n                           uint8_t *odcid, size_t *odcid_len) {\n    if ((token_len < sizeof(\"quiche\") - 1) ||\n         memcmp(token, \"quiche\", sizeof(\"quiche\") - 1)) {\n        return false;\n    }\n\n    token += sizeof(\"quiche\") - 1;\n    token_len -= sizeof(\"quiche\") - 1;\n\n    if ((token_len < addr_len) || memcmp(token, addr, addr_len)) {\n        return false;\n    }\n\n    token += addr_len;\n    token_len -= addr_len;\n\n    if (*odcid_len < token_len) {\n        return false;\n    }\n\n    memcpy(odcid, token, token_len);\n    *odcid_len = token_len;\n\n    return true;\n}\n\nstatic uint8_t *gen_cid(uint8_t *cid, size_t cid_len) {\n    int rng = open(\"/dev/urandom\", O_RDONLY);\n    if (rng < 0) {\n        perror(\"failed to open /dev/urandom\");\n        return NULL;\n    }\n\n    ssize_t rand_len = read(rng, cid, cid_len);\n    if (rand_len < 0) {\n        perror(\"failed to create connection ID\");\n        return NULL;\n    }\n\n    return cid;\n}\n\nstatic struct conn_io *create_conn(uint8_t *scid, size_t scid_len,\n                                   uint8_t *odcid, size_t odcid_len,\n                                   struct sockaddr *local_addr,\n                                   socklen_t local_addr_len,\n                                   struct sockaddr_storage *peer_addr,\n                                   socklen_t peer_addr_len)\n{\n    struct conn_io *conn_io = calloc(1, sizeof(*conn_io));\n    if (conn_io == NULL) {\n        fprintf(stderr, \"failed to allocate connection IO\\n\");\n        return NULL;\n    }\n\n    if (scid_len != LOCAL_CONN_ID_LEN) {\n        fprintf(stderr, \"failed, scid length too short\\n\");\n    }\n\n    memcpy(conn_io->cid, scid, LOCAL_CONN_ID_LEN);\n\n    quiche_conn *conn = quiche_accept(conn_io->cid, LOCAL_CONN_ID_LEN,\n                                      odcid, odcid_len,\n                                      local_addr,\n                                      local_addr_len,\n                                      (struct sockaddr *) peer_addr,\n                                      peer_addr_len,\n                                      config);\n\n    if (conn == NULL) {\n        fprintf(stderr, \"failed to create connection\\n\");\n        return NULL;\n    }\n\n    conn_io->sock = conns->sock;\n    conn_io->conn = conn;\n\n    memcpy(&conn_io->peer_addr, peer_addr, peer_addr_len);\n    conn_io->peer_addr_len = peer_addr_len;\n\n    ev_init(&conn_io->timer, timeout_cb);\n    conn_io->timer.data = conn_io;\n\n    HASH_ADD(hh, conns->h, cid, LOCAL_CONN_ID_LEN, conn_io);\n\n    fprintf(stderr, \"new connection\\n\");\n\n    return conn_io;\n}\n\nstatic void recv_cb(EV_P_ ev_io *w, int revents) {\n    struct conn_io *tmp, *conn_io = NULL;\n\n    static uint8_t buf[65535];\n    static uint8_t out[MAX_DATAGRAM_SIZE];\n\n    while (1) {\n        struct sockaddr_storage peer_addr;\n        socklen_t peer_addr_len = sizeof(peer_addr);\n        memset(&peer_addr, 0, peer_addr_len);\n\n        ssize_t read = recvfrom(conns->sock, buf, sizeof(buf), 0,\n                                (struct sockaddr *) &peer_addr,\n                                &peer_addr_len);\n\n        if (read < 0) {\n            if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {\n                fprintf(stderr, \"recv would block\\n\");\n                break;\n            }\n\n            perror(\"failed to read\");\n            return;\n        }\n\n        uint8_t type;\n        uint32_t version;\n\n        uint8_t scid[QUICHE_MAX_CONN_ID_LEN];\n        size_t scid_len = sizeof(scid);\n\n        uint8_t dcid[QUICHE_MAX_CONN_ID_LEN];\n        size_t dcid_len = sizeof(dcid);\n\n        uint8_t odcid[QUICHE_MAX_CONN_ID_LEN];\n        size_t odcid_len = sizeof(odcid);\n\n        uint8_t token[MAX_TOKEN_LEN];\n        size_t token_len = sizeof(token);\n\n        int rc = quiche_header_info(buf, read, LOCAL_CONN_ID_LEN, &version,\n                                    &type, scid, &scid_len, dcid, &dcid_len,\n                                    token, &token_len);\n        if (rc < 0) {\n            fprintf(stderr, \"failed to parse header: %d\\n\", rc);\n            continue;\n        }\n\n        HASH_FIND(hh, conns->h, dcid, dcid_len, conn_io);\n\n        if (conn_io == NULL) {\n            if (!quiche_version_is_supported(version)) {\n                fprintf(stderr, \"version negotiation\\n\");\n\n                ssize_t written = quiche_negotiate_version(scid, scid_len,\n                                                           dcid, dcid_len,\n                                                           out, sizeof(out));\n\n                if (written < 0) {\n                    fprintf(stderr, \"failed to create vneg packet: %zd\\n\",\n                            written);\n                    continue;\n                }\n\n                ssize_t sent = sendto(conns->sock, out, written, 0,\n                                      (struct sockaddr *) &peer_addr,\n                                      peer_addr_len);\n                if (sent != written) {\n                    perror(\"failed to send\");\n                    continue;\n                }\n\n                fprintf(stderr, \"sent %zd bytes\\n\", sent);\n                continue;\n            }\n\n            if (token_len == 0) {\n                fprintf(stderr, \"stateless retry\\n\");\n\n                mint_token(dcid, dcid_len, &peer_addr, peer_addr_len,\n                           token, &token_len);\n\n                uint8_t new_cid[LOCAL_CONN_ID_LEN];\n\n                if (gen_cid(new_cid, LOCAL_CONN_ID_LEN) == NULL) {\n                    continue;\n                }\n\n                ssize_t written = quiche_retry(scid, scid_len,\n                                               dcid, dcid_len,\n                                               new_cid, LOCAL_CONN_ID_LEN,\n                                               token, token_len,\n                                               version, out, sizeof(out));\n\n                if (written < 0) {\n                    fprintf(stderr, \"failed to create retry packet: %zd\\n\",\n                            written);\n                    continue;\n                }\n\n                ssize_t sent = sendto(conns->sock, out, written, 0,\n                                      (struct sockaddr *) &peer_addr,\n                                      peer_addr_len);\n                if (sent != written) {\n                    perror(\"failed to send\");\n                    continue;\n                }\n\n                fprintf(stderr, \"sent %zd bytes\\n\", sent);\n                continue;\n            }\n\n\n            if (!validate_token(token, token_len, &peer_addr, peer_addr_len,\n                               odcid, &odcid_len)) {\n                fprintf(stderr, \"invalid address validation token\\n\");\n                continue;\n            }\n\n            conn_io = create_conn(dcid, dcid_len, odcid, odcid_len,\n                                  conns->local_addr, conns->local_addr_len,\n                                  &peer_addr, peer_addr_len);\n\n            if (conn_io == NULL) {\n                continue;\n            }\n        }\n\n        quiche_recv_info recv_info = {\n            (struct sockaddr *)&peer_addr,\n            peer_addr_len,\n\n            conns->local_addr,\n            conns->local_addr_len,\n        };\n\n        ssize_t done = quiche_conn_recv(conn_io->conn, buf, read, &recv_info);\n\n        if (done < 0) {\n            fprintf(stderr, \"failed to process packet: %zd\\n\", done);\n            continue;\n        }\n\n        fprintf(stderr, \"recv %zd bytes\\n\", done);\n\n        if (quiche_conn_is_established(conn_io->conn)) {\n            uint64_t s = 0;\n\n            quiche_stream_iter *readable = quiche_conn_readable(conn_io->conn);\n\n            while (quiche_stream_iter_next(readable, &s)) {\n                fprintf(stderr, \"stream %\" PRIu64 \" is readable\\n\", s);\n\n                bool fin = false;\n                uint64_t error_code;\n                ssize_t recv_len = quiche_conn_stream_recv(conn_io->conn, s,\n                                                           buf, sizeof(buf),\n                                                           &fin, &error_code);\n                if (recv_len < 0) {\n                    break;\n                }\n\n                if (fin) {\n                    static const char *resp = \"byez\\n\";\n                    uint64_t error_code;\n                    quiche_conn_stream_send(conn_io->conn, s, (uint8_t *) resp,\n                                            5, true, &error_code);\n                }\n            }\n\n            quiche_stream_iter_free(readable);\n        }\n    }\n\n    HASH_ITER(hh, conns->h, conn_io, tmp) {\n        flush_egress(loop, conn_io);\n\n        if (quiche_conn_is_closed(conn_io->conn)) {\n            quiche_stats stats;\n            quiche_path_stats path_stats;\n\n            quiche_conn_stats(conn_io->conn, &stats);\n            quiche_conn_path_stats(conn_io->conn, 0, &path_stats);\n\n            fprintf(stderr, \"connection closed, recv=%zu sent=%zu lost=%zu rtt=%\" PRIu64 \"ns cwnd=%zu\\n\",\n                    stats.recv, stats.sent, stats.lost, path_stats.rtt, path_stats.cwnd);\n\n            HASH_DELETE(hh, conns->h, conn_io);\n\n            ev_timer_stop(loop, &conn_io->timer);\n            quiche_conn_free(conn_io->conn);\n            free(conn_io);\n        }\n    }\n}\n\nstatic void timeout_cb(EV_P_ ev_timer *w, int revents) {\n    struct conn_io *conn_io = w->data;\n    quiche_conn_on_timeout(conn_io->conn);\n\n    fprintf(stderr, \"timeout\\n\");\n\n    flush_egress(loop, conn_io);\n\n    if (quiche_conn_is_closed(conn_io->conn)) {\n        quiche_stats stats;\n        quiche_path_stats path_stats;\n\n        quiche_conn_stats(conn_io->conn, &stats);\n        quiche_conn_path_stats(conn_io->conn, 0, &path_stats);\n\n        fprintf(stderr, \"connection closed, recv=%zu sent=%zu lost=%zu rtt=%\" PRIu64 \"ns cwnd=%zu\\n\",\n                stats.recv, stats.sent, stats.lost, path_stats.rtt, path_stats.cwnd);\n\n        HASH_DELETE(hh, conns->h, conn_io);\n\n        ev_timer_stop(loop, &conn_io->timer);\n        quiche_conn_free(conn_io->conn);\n        free(conn_io);\n\n        return;\n    }\n}\n\nint main(int argc, char *argv[]) {\n    const char *host = argv[1];\n    const char *port = argv[2];\n\n    const struct addrinfo hints = {\n        .ai_family = PF_UNSPEC,\n        .ai_socktype = SOCK_DGRAM,\n        .ai_protocol = IPPROTO_UDP\n    };\n\n    quiche_enable_debug_logging(debug_log, NULL);\n\n    struct addrinfo *local;\n    if (getaddrinfo(host, port, &hints, &local) != 0) {\n        perror(\"failed to resolve host\");\n        return -1;\n    }\n\n    int sock = socket(local->ai_family, SOCK_DGRAM, 0);\n    if (sock < 0) {\n        perror(\"failed to create socket\");\n        return -1;\n    }\n\n    if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {\n        perror(\"failed to make socket non-blocking\");\n        return -1;\n    }\n\n    if (bind(sock, local->ai_addr, local->ai_addrlen) < 0) {\n        perror(\"failed to connect socket\");\n        return -1;\n    }\n\n    config = quiche_config_new(QUICHE_PROTOCOL_VERSION);\n    if (config == NULL) {\n        fprintf(stderr, \"failed to create config\\n\");\n        return -1;\n    }\n\n    quiche_config_load_cert_chain_from_pem_file(config, \"./cert.crt\");\n    quiche_config_load_priv_key_from_pem_file(config, \"./cert.key\");\n\n    quiche_config_set_application_protos(config,\n        (uint8_t *) \"\\x0ahq-interop\\x05hq-29\\x05hq-28\\x05hq-27\\x08http/0.9\", 38);\n\n    quiche_config_set_max_idle_timeout(config, 5000);\n    quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);\n    quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);\n    quiche_config_set_initial_max_data(config, 10000000);\n    quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000);\n    quiche_config_set_initial_max_stream_data_bidi_remote(config, 1000000);\n    quiche_config_set_initial_max_streams_bidi(config, 100);\n    quiche_config_set_cc_algorithm(config, QUICHE_CC_RENO);\n\n    struct connections c;\n    c.sock = sock;\n    c.h = NULL;\n    c.local_addr = local->ai_addr;\n    c.local_addr_len = local->ai_addrlen;\n\n    conns = &c;\n\n    ev_io watcher;\n\n    struct ev_loop *loop = ev_default_loop(0);\n\n    ev_io_init(&watcher, recv_cb, sock, EV_READ);\n    ev_io_start(loop, &watcher);\n    watcher.data = &c;\n\n    ev_loop(loop, 0);\n\n    freeaddrinfo(local);\n\n    quiche_config_free(config);\n\n    return 0;\n}\n"
  },
  {
    "path": "quiche/examples/server.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[macro_use]\nextern crate log;\n\nuse std::net;\n\nuse std::collections::HashMap;\n\nuse ring::rand::*;\n\nconst MAX_DATAGRAM_SIZE: usize = 1350;\n\nstruct PartialResponse {\n    body: Vec<u8>,\n\n    written: usize,\n}\n\nstruct Client {\n    conn: quiche::Connection,\n\n    partial_responses: HashMap<u64, PartialResponse>,\n}\n\ntype ClientMap = HashMap<quiche::ConnectionId<'static>, Client>;\n\nfn main() {\n    let mut buf = [0; 65535];\n    let mut out = [0; MAX_DATAGRAM_SIZE];\n\n    let mut args = std::env::args();\n\n    let cmd = &args.next().unwrap();\n\n    if args.len() != 0 {\n        println!(\"Usage: {cmd}\");\n        println!(\"\\nSee tools/apps/ for more complete implementations.\");\n        return;\n    }\n\n    // Setup the event loop.\n    let mut poll = mio::Poll::new().unwrap();\n    let mut events = mio::Events::with_capacity(1024);\n\n    // Create the UDP listening socket, and register it with the event loop.\n    let mut socket =\n        mio::net::UdpSocket::bind(\"127.0.0.1:4433\".parse().unwrap()).unwrap();\n    poll.registry()\n        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)\n        .unwrap();\n\n    // Create the configuration for the QUIC connections.\n    let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n\n    config\n        .set_application_protos(&[\n            b\"hq-interop\",\n            b\"hq-29\",\n            b\"hq-28\",\n            b\"hq-27\",\n            b\"http/0.9\",\n        ])\n        .unwrap();\n\n    config.set_max_idle_timeout(5000);\n    config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_initial_max_data(10_000_000);\n    config.set_initial_max_stream_data_bidi_local(1_000_000);\n    config.set_initial_max_stream_data_bidi_remote(1_000_000);\n    config.set_initial_max_stream_data_uni(1_000_000);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(100);\n    config.set_disable_active_migration(true);\n    config.enable_early_data();\n\n    let rng = SystemRandom::new();\n    let conn_id_seed =\n        ring::hmac::Key::generate(ring::hmac::HMAC_SHA256, &rng).unwrap();\n\n    let mut clients = ClientMap::new();\n\n    let local_addr = socket.local_addr().unwrap();\n\n    loop {\n        // Find the shorter timeout from all the active connections.\n        //\n        // TODO: use event loop that properly supports timers\n        let timeout = clients.values().filter_map(|c| c.conn.timeout()).min();\n\n        poll.poll(&mut events, timeout).unwrap();\n\n        // Read incoming UDP packets from the socket and feed them to quiche,\n        // until there are no more packets to read.\n        'read: loop {\n            // If the event loop reported no events, it means that the timeout\n            // has expired, so handle it without attempting to read packets. We\n            // will then proceed with the send loop.\n            if events.is_empty() {\n                debug!(\"timed out\");\n\n                clients.values_mut().for_each(|c| c.conn.on_timeout());\n\n                break 'read;\n            }\n\n            let (len, from) = match socket.recv_from(&mut buf) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    // There are no more UDP packets to read, so end the read\n                    // loop.\n                    if e.kind() == std::io::ErrorKind::WouldBlock {\n                        debug!(\"recv() would block\");\n                        break 'read;\n                    }\n\n                    panic!(\"recv() failed: {e:?}\");\n                },\n            };\n\n            debug!(\"got {len} bytes\");\n\n            let pkt_buf = &mut buf[..len];\n\n            // Parse the QUIC packet's header.\n            let hdr = match quiche::Header::from_slice(\n                pkt_buf,\n                quiche::MAX_CONN_ID_LEN,\n            ) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    error!(\"Parsing packet header failed: {e:?}\");\n                    continue 'read;\n                },\n            };\n\n            trace!(\"got packet {hdr:?}\");\n\n            let conn_id = ring::hmac::sign(&conn_id_seed, &hdr.dcid);\n            let conn_id = &conn_id.as_ref()[..quiche::MAX_CONN_ID_LEN];\n            let conn_id = conn_id.to_vec().into();\n\n            // Lookup a connection based on the packet's connection ID. If there\n            // is no connection matching, create a new one.\n            let client = if !clients.contains_key(&hdr.dcid) &&\n                !clients.contains_key(&conn_id)\n            {\n                if hdr.ty != quiche::Type::Initial {\n                    error!(\"Packet is not Initial\");\n                    continue 'read;\n                }\n\n                if !quiche::version_is_supported(hdr.version) {\n                    warn!(\"Doing version negotiation\");\n\n                    let len =\n                        quiche::negotiate_version(&hdr.scid, &hdr.dcid, &mut out)\n                            .unwrap();\n\n                    let out = &out[..len];\n\n                    if let Err(e) = socket.send_to(out, from) {\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            debug!(\"send() would block\");\n                            break;\n                        }\n\n                        panic!(\"send() failed: {e:?}\");\n                    }\n                    continue 'read;\n                }\n\n                let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n                scid.copy_from_slice(&conn_id);\n\n                let scid = quiche::ConnectionId::from_ref(&scid);\n\n                // Token is always present in Initial packets.\n                let token = hdr.token.as_ref().unwrap();\n\n                // Do stateless retry if the client didn't send a token.\n                if token.is_empty() {\n                    warn!(\"Doing stateless retry\");\n\n                    let new_token = mint_token(&hdr, &from);\n\n                    let len = quiche::retry(\n                        &hdr.scid,\n                        &hdr.dcid,\n                        &scid,\n                        &new_token,\n                        hdr.version,\n                        &mut out,\n                    )\n                    .unwrap();\n\n                    let out = &out[..len];\n\n                    if let Err(e) = socket.send_to(out, from) {\n                        if e.kind() == std::io::ErrorKind::WouldBlock {\n                            debug!(\"send() would block\");\n                            break;\n                        }\n\n                        panic!(\"send() failed: {e:?}\");\n                    }\n                    continue 'read;\n                }\n\n                let odcid = validate_token(&from, token);\n\n                // The token was not valid, meaning the retry failed, so\n                // drop the packet.\n                if odcid.is_none() {\n                    error!(\"Invalid address validation token\");\n                    continue 'read;\n                }\n\n                if scid.len() != hdr.dcid.len() {\n                    error!(\"Invalid destination connection ID\");\n                    continue 'read;\n                }\n\n                // Reuse the source connection ID we sent in the Retry packet,\n                // instead of changing it again.\n                let scid = hdr.dcid.clone();\n\n                debug!(\"New connection: dcid={:?} scid={:?}\", hdr.dcid, scid);\n\n                let conn = quiche::accept(\n                    &scid,\n                    odcid.as_ref(),\n                    local_addr,\n                    from,\n                    &mut config,\n                )\n                .unwrap();\n\n                let client = Client {\n                    conn,\n                    partial_responses: HashMap::new(),\n                };\n\n                clients.insert(scid.clone(), client);\n\n                clients.get_mut(&scid).unwrap()\n            } else {\n                match clients.get_mut(&hdr.dcid) {\n                    Some(v) => v,\n\n                    None => clients.get_mut(&conn_id).unwrap(),\n                }\n            };\n\n            let recv_info = quiche::RecvInfo {\n                to: socket.local_addr().unwrap(),\n                from,\n            };\n\n            // Process potentially coalesced packets.\n            let read = match client.conn.recv(pkt_buf, recv_info) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    error!(\"{} recv failed: {:?}\", client.conn.trace_id(), e);\n                    continue 'read;\n                },\n            };\n\n            debug!(\"{} processed {} bytes\", client.conn.trace_id(), read);\n\n            if client.conn.is_in_early_data() || client.conn.is_established() {\n                // Handle writable streams.\n                for stream_id in client.conn.writable() {\n                    handle_writable(client, stream_id);\n                }\n\n                // Process all readable streams.\n                for s in client.conn.readable() {\n                    while let Ok((read, fin)) =\n                        client.conn.stream_recv(s, &mut buf)\n                    {\n                        debug!(\n                            \"{} received {} bytes\",\n                            client.conn.trace_id(),\n                            read\n                        );\n\n                        let stream_buf = &buf[..read];\n\n                        debug!(\n                            \"{} stream {} has {} bytes (fin? {})\",\n                            client.conn.trace_id(),\n                            s,\n                            stream_buf.len(),\n                            fin\n                        );\n\n                        handle_stream(client, s, stream_buf, \"examples/root\");\n                    }\n                }\n            }\n        }\n\n        // Generate outgoing QUIC packets for all active connections and send\n        // them on the UDP socket, until quiche reports that there are no more\n        // packets to be sent.\n        for client in clients.values_mut() {\n            loop {\n                let (write, send_info) = match client.conn.send(&mut out) {\n                    Ok(v) => v,\n\n                    Err(quiche::Error::Done) => {\n                        debug!(\"{} done writing\", client.conn.trace_id());\n                        break;\n                    },\n\n                    Err(e) => {\n                        error!(\"{} send failed: {:?}\", client.conn.trace_id(), e);\n\n                        client.conn.close(false, 0x1, b\"fail\").ok();\n                        break;\n                    },\n                };\n\n                if let Err(e) = socket.send_to(&out[..write], send_info.to) {\n                    if e.kind() == std::io::ErrorKind::WouldBlock {\n                        debug!(\"send() would block\");\n                        break;\n                    }\n\n                    panic!(\"send() failed: {e:?}\");\n                }\n\n                debug!(\"{} written {} bytes\", client.conn.trace_id(), write);\n            }\n        }\n\n        // Garbage collect closed connections.\n        clients.retain(|_, ref mut c| {\n            debug!(\"Collecting garbage\");\n\n            if c.conn.is_closed() {\n                info!(\n                    \"{} connection collected {:?}\",\n                    c.conn.trace_id(),\n                    c.conn.stats()\n                );\n            }\n\n            !c.conn.is_closed()\n        });\n    }\n}\n\n/// Generate a stateless retry token.\n///\n/// The token includes the static string `\"quiche\"` followed by the IP address\n/// of the client and by the original destination connection ID generated by the\n/// client.\n///\n/// Note that this function is only an example and doesn't do any cryptographic\n/// authenticate of the token. *It should not be used in production system*.\nfn mint_token(hdr: &quiche::Header, src: &net::SocketAddr) -> Vec<u8> {\n    let mut token = Vec::new();\n\n    token.extend_from_slice(b\"quiche\");\n\n    let addr = match src.ip() {\n        std::net::IpAddr::V4(a) => a.octets().to_vec(),\n        std::net::IpAddr::V6(a) => a.octets().to_vec(),\n    };\n\n    token.extend_from_slice(&addr);\n    token.extend_from_slice(&hdr.dcid);\n\n    token\n}\n\n/// Validates a stateless retry token.\n///\n/// This checks that the ticket includes the `\"quiche\"` static string, and that\n/// the client IP address matches the address stored in the ticket.\n///\n/// Note that this function is only an example and doesn't do any cryptographic\n/// authenticate of the token. *It should not be used in production system*.\nfn validate_token<'a>(\n    src: &net::SocketAddr, token: &'a [u8],\n) -> Option<quiche::ConnectionId<'a>> {\n    if token.len() < 6 {\n        return None;\n    }\n\n    if &token[..6] != b\"quiche\" {\n        return None;\n    }\n\n    let token = &token[6..];\n\n    let addr = match src.ip() {\n        std::net::IpAddr::V4(a) => a.octets().to_vec(),\n        std::net::IpAddr::V6(a) => a.octets().to_vec(),\n    };\n\n    if token.len() < addr.len() || &token[..addr.len()] != addr.as_slice() {\n        return None;\n    }\n\n    Some(quiche::ConnectionId::from_ref(&token[addr.len()..]))\n}\n\n/// Handles incoming HTTP/0.9 requests.\nfn handle_stream(client: &mut Client, stream_id: u64, buf: &[u8], root: &str) {\n    let conn = &mut client.conn;\n\n    if buf.len() > 4 && &buf[..4] == b\"GET \" {\n        let uri = &buf[4..buf.len()];\n        let uri = String::from_utf8(uri.to_vec()).unwrap();\n        let uri = String::from(uri.lines().next().unwrap());\n        let uri = std::path::Path::new(&uri);\n        let mut path = std::path::PathBuf::from(root);\n\n        for c in uri.components() {\n            if let std::path::Component::Normal(v) = c {\n                path.push(v)\n            }\n        }\n\n        info!(\n            \"{} got GET request for {:?} on stream {}\",\n            conn.trace_id(),\n            path,\n            stream_id\n        );\n\n        let body = std::fs::read(path.as_path())\n            .unwrap_or_else(|_| b\"Not Found!\\r\\n\".to_vec());\n\n        info!(\n            \"{} sending response of size {} on stream {}\",\n            conn.trace_id(),\n            body.len(),\n            stream_id\n        );\n\n        let written = match conn.stream_send(stream_id, &body, true) {\n            Ok(v) => v,\n\n            Err(quiche::Error::Done) => 0,\n\n            Err(e) => {\n                error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n                return;\n            },\n        };\n\n        if written < body.len() {\n            let response = PartialResponse { body, written };\n            client.partial_responses.insert(stream_id, response);\n        }\n    }\n}\n\n/// Handles newly writable streams.\nfn handle_writable(client: &mut Client, stream_id: u64) {\n    let conn = &mut client.conn;\n\n    debug!(\"{} stream {} is writable\", conn.trace_id(), stream_id);\n\n    if !client.partial_responses.contains_key(&stream_id) {\n        return;\n    }\n\n    let resp = client.partial_responses.get_mut(&stream_id).unwrap();\n    let body = &resp.body[resp.written..];\n\n    let written = match conn.stream_send(stream_id, body, true) {\n        Ok(v) => v,\n\n        Err(quiche::Error::Done) => 0,\n\n        Err(e) => {\n            client.partial_responses.remove(&stream_id);\n\n            error!(\"{} stream send failed {:?}\", conn.trace_id(), e);\n            return;\n        },\n    };\n\n    resp.written += written;\n\n    if resp.written == resp.body.len() {\n        client.partial_responses.remove(&stream_id);\n    }\n}\n"
  },
  {
    "path": "quiche/include/quiche.h",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright\n//       notice, this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#ifndef QUICHE_H\n#define QUICHE_H\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <stddef.h>\n\n#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#include <time.h>\n#else\n#include <sys/socket.h>\n#include <sys/time.h>\n#endif\n\n#ifdef __unix__\n#include <sys/types.h>\n#endif\n#ifdef _MSC_VER\n#include <BaseTsd.h>\n#define ssize_t SSIZE_T\n#endif\n\n#if defined(__cplusplus)\nextern \"C\" {\n#endif\n\n// QUIC transport API.\n//\n\n// The current QUIC wire version.\n#define QUICHE_PROTOCOL_VERSION 0x00000001\n\n// The maximum length of a connection ID.\n#define QUICHE_MAX_CONN_ID_LEN 20\n\n// The minimum length of Initial packets sent by a client.\n#define QUICHE_MIN_CLIENT_INITIAL_LEN 1200\n\nenum quiche_error {\n    // There is no more work to do.\n    QUICHE_ERR_DONE = -1,\n\n    // The provided buffer is too short.\n    QUICHE_ERR_BUFFER_TOO_SHORT = -2,\n\n    // The provided packet cannot be parsed because its version is unknown.\n    QUICHE_ERR_UNKNOWN_VERSION = -3,\n\n    // The provided packet cannot be parsed because it contains an invalid\n    // frame.\n    QUICHE_ERR_INVALID_FRAME = -4,\n\n    // The provided packet cannot be parsed.\n    QUICHE_ERR_INVALID_PACKET = -5,\n\n    // The operation cannot be completed because the connection is in an\n    // invalid state.\n    QUICHE_ERR_INVALID_STATE = -6,\n\n    // The operation cannot be completed because the stream is in an\n    // invalid state.\n    QUICHE_ERR_INVALID_STREAM_STATE = -7,\n\n    // The peer's transport params cannot be parsed.\n    QUICHE_ERR_INVALID_TRANSPORT_PARAM = -8,\n\n    // A cryptographic operation failed.\n    QUICHE_ERR_CRYPTO_FAIL = -9,\n\n    // The TLS handshake failed.\n    QUICHE_ERR_TLS_FAIL = -10,\n\n    // The peer violated the local flow control limits.\n    QUICHE_ERR_FLOW_CONTROL = -11,\n\n    // The peer violated the local stream limits.\n    QUICHE_ERR_STREAM_LIMIT = -12,\n\n    // The specified stream was stopped by the peer.\n    QUICHE_ERR_STREAM_STOPPED = -15,\n\n    // The specified stream was reset by the peer.\n    QUICHE_ERR_STREAM_RESET = -16,\n\n    // The received data exceeds the stream's final size.\n    QUICHE_ERR_FINAL_SIZE = -13,\n\n    // Error in congestion control.\n    QUICHE_ERR_CONGESTION_CONTROL = -14,\n\n    // Too many identifiers were provided.\n    QUICHE_ERR_ID_LIMIT = -17,\n\n    // Not enough available identifiers.\n    QUICHE_ERR_OUT_OF_IDENTIFIERS = -18,\n\n    // Error in key update.\n    QUICHE_ERR_KEY_UPDATE = -19,\n\n    // The peer sent more data in CRYPTO frames than we can buffer.\n    QUICHE_ERR_CRYPTO_BUFFER_EXCEEDED = -20,\n\n    // The peer sent an ACK frame with an invalid range.\n    QUICHE_ERR_INVALID_ACK_RANGE = -21,\n\n    // The peer send an ACK frame for a skipped packet used for Optimistic ACK\n    // mitigation.\n    QUICHE_ERR_OPTIMISTIC_ACK_DETECTED = -22,\n\n    /// An invalid DCID was used when connecting to a remote peer.\n    QUICHE_ERR_INVALID_DCID_INITIALIZATION = -23,\n};\n\n// Returns a human readable string with the quiche version number.\nconst char *quiche_version(void);\n\n// Enables logging. |cb| will be called with log messages\nint quiche_enable_debug_logging(void (*cb)(const char *line, void *argp),\n                                void *argp);\n\n// Stores configuration shared between multiple connections.\ntypedef struct quiche_config quiche_config;\n\n// Creates a config object with the given version.\nquiche_config *quiche_config_new(uint32_t version);\n\n// Configures the given certificate chain.\nint quiche_config_load_cert_chain_from_pem_file(quiche_config *config,\n                                                const char *path);\n\n// Configures the given private key.\nint quiche_config_load_priv_key_from_pem_file(quiche_config *config,\n                                              const char *path);\n\n// Specifies a file where trusted CA certificates are stored for the purposes of certificate verification.\nint quiche_config_load_verify_locations_from_file(quiche_config *config,\n                                                  const char *path);\n\n// Specifies a directory where trusted CA certificates are stored for the purposes of certificate verification.\nint quiche_config_load_verify_locations_from_directory(quiche_config *config,\n                                                       const char *path);\n\n// Configures whether to verify the peer's certificate.\nvoid quiche_config_verify_peer(quiche_config *config, bool v);\n\n// Configures whether to send GREASE.\nvoid quiche_config_grease(quiche_config *config, bool v);\n\n// Configures whether to do path MTU discovery.\nvoid quiche_config_discover_pmtu(quiche_config *config, bool v);\n\n// Enables logging of secrets.\nvoid quiche_config_log_keys(quiche_config *config);\n\n// Enables sending or receiving early data.\nvoid quiche_config_enable_early_data(quiche_config *config);\n\n// Configures the list of supported application protocols.\nint quiche_config_set_application_protos(quiche_config *config,\n                                         const uint8_t *protos,\n                                         size_t protos_len);\n\n// Sets the anti-amplification limit factor.\nvoid quiche_config_set_max_amplification_factor(quiche_config *config, size_t v);\n\n// Sets the `max_idle_timeout` transport parameter, in milliseconds, default is\n// no timeout.\nvoid quiche_config_set_max_idle_timeout(quiche_config *config, uint64_t v);\n\n// Sets the `max_udp_payload_size transport` parameter.\nvoid quiche_config_set_max_recv_udp_payload_size(quiche_config *config, size_t v);\n\n// Sets the maximum outgoing UDP payload size.\nvoid quiche_config_set_max_send_udp_payload_size(quiche_config *config, size_t v);\n\n// Sets the `initial_max_data` transport parameter.\nvoid quiche_config_set_initial_max_data(quiche_config *config, uint64_t v);\n\n// Sets the `initial_max_stream_data_bidi_local` transport parameter.\nvoid quiche_config_set_initial_max_stream_data_bidi_local(quiche_config *config, uint64_t v);\n\n// Sets the `initial_max_stream_data_bidi_remote` transport parameter.\nvoid quiche_config_set_initial_max_stream_data_bidi_remote(quiche_config *config, uint64_t v);\n\n// Sets the `initial_max_stream_data_uni` transport parameter.\nvoid quiche_config_set_initial_max_stream_data_uni(quiche_config *config, uint64_t v);\n\n// Sets the `initial_max_streams_bidi` transport parameter.\nvoid quiche_config_set_initial_max_streams_bidi(quiche_config *config, uint64_t v);\n\n// Sets the `initial_max_streams_uni` transport parameter.\nvoid quiche_config_set_initial_max_streams_uni(quiche_config *config, uint64_t v);\n\n// Sets the `ack_delay_exponent` transport parameter.\nvoid quiche_config_set_ack_delay_exponent(quiche_config *config, uint64_t v);\n\n// Sets the `max_ack_delay` transport parameter.\nvoid quiche_config_set_max_ack_delay(quiche_config *config, uint64_t v);\n\n// Sets the `disable_active_migration` transport parameter.\nvoid quiche_config_set_disable_active_migration(quiche_config *config, bool v);\n\n// Sets the congestion control algorithm used by string.\nint quiche_config_set_cc_algorithm_name(quiche_config *config, const char *algo);\n\n// Sets the initial cwnd for the connection in terms of packet count.\nvoid quiche_config_set_initial_congestion_window_packets(quiche_config *config, size_t packets);\n\nenum quiche_cc_algorithm {\n    QUICHE_CC_RENO = 0,\n    QUICHE_CC_CUBIC = 1,\n    QUICHE_CC_BBR2_GCONGESTION = 4,\n};\n\n// Sets the congestion control algorithm used.\nvoid quiche_config_set_cc_algorithm(quiche_config *config, enum quiche_cc_algorithm algo);\n\n// Configures whether to use HyStart++.\nvoid quiche_config_enable_hystart(quiche_config *config, bool v);\n\n// Configures whether to enable pacing (enabled by default).\nvoid quiche_config_enable_pacing(quiche_config *config, bool v);\n\n// Configures whether to enable the CUBIC idle restart fix (enabled by default).\nvoid quiche_config_set_enable_cubic_idle_restart_fix(quiche_config *config,\n                                                     bool v);\n\n// Configures max pacing rate to be used.\nvoid quiche_config_set_max_pacing_rate(quiche_config *config, uint64_t v);\n\n// Configures whether to enable receiving DATAGRAM frames.\nvoid quiche_config_enable_dgram(quiche_config *config, bool enabled,\n                                size_t recv_queue_len,\n                                size_t send_queue_len);\n\n// Sets the maximum connection window.\nvoid quiche_config_set_max_connection_window(quiche_config *config, uint64_t v);\n\n// Sets the maximum stream window.\nvoid quiche_config_set_max_stream_window(quiche_config *config, uint64_t v);\n\n// Sets the limit of active connection IDs.\nvoid quiche_config_set_active_connection_id_limit(quiche_config *config, uint64_t v);\n\n// Sets the initial stateless reset token. |v| must contain 16 bytes, otherwise the behaviour is undefined.\nvoid quiche_config_set_stateless_reset_token(quiche_config *config, const uint8_t *v);\n\n// Sets whether the QUIC connection should avoid reusing DCIDs over different paths.\nvoid quiche_config_set_disable_dcid_reuse(quiche_config *config, bool v);\n\n// Configures the session ticket key material.\nint quiche_config_set_ticket_key(quiche_config *config, const uint8_t *key, size_t key_len);\n\n// Frees the config object.\nvoid quiche_config_free(quiche_config *config);\n\n// Extracts version, type, source / destination connection ID and address\n// verification token from the packet in |buf|.\nint quiche_header_info(const uint8_t *buf, size_t buf_len, size_t dcil,\n                       uint32_t *version, uint8_t *type,\n                       uint8_t *scid, size_t *scid_len,\n                       uint8_t *dcid, size_t *dcid_len,\n                       uint8_t *token, size_t *token_len);\n\n// A QUIC connection.\ntypedef struct quiche_conn quiche_conn;\n\n// Creates a new server-side connection.\nquiche_conn *quiche_accept(const uint8_t *scid, size_t scid_len,\n                           const uint8_t *odcid, size_t odcid_len,\n                           const struct sockaddr *local, socklen_t local_len,\n                           const struct sockaddr *peer, socklen_t peer_len,\n                           quiche_config *config);\n\n// Creates a new client-side connection.\nquiche_conn *quiche_connect(const char *server_name,\n                            const uint8_t *scid, size_t scid_len,\n                            const struct sockaddr *local, socklen_t local_len,\n                            const struct sockaddr *peer, socklen_t peer_len,\n                            quiche_config *config);\n\n// Writes a version negotiation packet.\nssize_t quiche_negotiate_version(const uint8_t *scid, size_t scid_len,\n                                 const uint8_t *dcid, size_t dcid_len,\n                                 uint8_t *out, size_t out_len);\n\n// Writes a retry packet.\nssize_t quiche_retry(const uint8_t *scid, size_t scid_len,\n                     const uint8_t *dcid, size_t dcid_len,\n                     const uint8_t *new_scid, size_t new_scid_len,\n                     const uint8_t *token, size_t token_len,\n                     uint32_t version, uint8_t *out, size_t out_len);\n\n// Returns true if the given protocol version is supported.\nbool quiche_version_is_supported(uint32_t version);\n\nquiche_conn *quiche_conn_new_with_tls(const uint8_t *scid, size_t scid_len,\n                                      const uint8_t *odcid, size_t odcid_len,\n                                      const struct sockaddr *local, socklen_t local_len,\n                                      const struct sockaddr *peer, socklen_t peer_len,\n                                      const quiche_config *config, void *ssl,\n                                      bool is_server);\n\n// Needs to have custom-client-dcid feature enabled on compile time. Otherwise will always return NULL.\nquiche_conn *quiche_conn_new_with_tls_and_client_dcid(const uint8_t *scid, size_t scid_len,\n                                      const uint8_t *dcid, size_t dcid_len,\n                                      const struct sockaddr *local, socklen_t local_len,\n                                      const struct sockaddr *peer, socklen_t peer_len,\n                                      const quiche_config *config, void *ssl);\n\n// Enables keylog to the specified file path. Returns true on success.\nbool quiche_conn_set_keylog_path(quiche_conn *conn, const char *path);\n\n// Enables keylog to the specified file descriptor. Unix only.\nvoid quiche_conn_set_keylog_fd(quiche_conn *conn, int fd);\n\n// Enables qlog to the specified file path. Returns true on success.\nbool quiche_conn_set_qlog_path(quiche_conn *conn, const char *path,\n                          const char *log_title, const char *log_desc);\n\n// Enables qlog to the specified file descriptor. Unix only.\nvoid quiche_conn_set_qlog_fd(quiche_conn *conn, int fd, const char *log_title,\n                             const char *log_desc);\n\n// Configures the given session for resumption.\nint quiche_conn_set_session(quiche_conn *conn, const uint8_t *buf, size_t buf_len);\n\n// Sets the `max_idle_timeout` transport parameter, in milliseconds, default is\n// no timeout.\nint quiche_conn_set_max_idle_timeout(quiche_conn *conn, uint64_t v);\n\ntypedef struct {\n    // The remote address the packet was received from.\n    struct sockaddr *from;\n    socklen_t from_len;\n\n    // The local address the packet was received on.\n    struct sockaddr *to;\n    socklen_t to_len;\n} quiche_recv_info;\n\n// Processes QUIC packets received from the peer.\nssize_t quiche_conn_recv(quiche_conn *conn, uint8_t *buf, size_t buf_len,\n                         const quiche_recv_info *info);\n\ntypedef struct {\n    // The local address the packet should be sent from.\n    struct sockaddr_storage from;\n    socklen_t from_len;\n\n    // The remote address the packet should be sent to.\n    struct sockaddr_storage to;\n    socklen_t to_len;\n\n    // The time to send the packet out.\n    struct timespec at;\n} quiche_send_info;\n\n// Writes a single QUIC packet to be sent to the peer.\nssize_t quiche_conn_send(quiche_conn *conn, uint8_t *out, size_t out_len,\n                         quiche_send_info *out_info);\n\n// Returns the size of the send quantum, in bytes.\nsize_t quiche_conn_send_quantum(const quiche_conn *conn);\n\n// Writes a single QUIC packet to be sent to the peer from the specified\n// local address \"from\" to the destination address \"to\".\nssize_t quiche_conn_send_on_path(quiche_conn *conn, uint8_t *out, size_t out_len,\n                                 const struct sockaddr *from, socklen_t from_len,\n                                 const struct sockaddr *to, socklen_t to_len,\n                                 quiche_send_info *out_info);\n\n// Returns the size of the send quantum over the given 4-tuple, in bytes.\nsize_t quiche_conn_send_quantum_on_path(const quiche_conn *conn,\n                                        const struct sockaddr *local_addr, socklen_t local_len,\n                                        const struct sockaddr *peer_addr, socklen_t peer_len);\n\n\n// Reads contiguous data from a stream.\n// out_error_code is only set when STREAM_STOPPED or STREAM_RESET are returned.\n// Set to the reported error code associated with STOP_SENDING or STREAM_RESET.\nssize_t quiche_conn_stream_recv(quiche_conn *conn, uint64_t stream_id,\n                                uint8_t *out, size_t buf_len, bool *fin,\n                                uint64_t *out_error_code);\n\n// Writes data to a stream.\n// out_error_code is only set when STREAM_STOPPED or STREAM_RESET are returned.\n// Set to the reported error code associated with STOP_SENDING or STREAM_RESET.\nssize_t quiche_conn_stream_send(quiche_conn *conn, uint64_t stream_id,\n                                const uint8_t *buf, size_t buf_len, bool fin,\n                                uint64_t *out_error_code);\n\n// The side of the stream to be shut down.\nenum quiche_shutdown {\n    QUICHE_SHUTDOWN_READ = 0,\n    QUICHE_SHUTDOWN_WRITE = 1,\n};\n\n// Sets the priority for a stream.\nint quiche_conn_stream_priority(quiche_conn *conn, uint64_t stream_id,\n                                uint8_t urgency, bool incremental);\n\n// Shuts down reading or writing from/to the specified stream.\nint quiche_conn_stream_shutdown(quiche_conn *conn, uint64_t stream_id,\n                                enum quiche_shutdown direction, uint64_t err);\n\n// Returns the stream's send capacity in bytes.\nssize_t quiche_conn_stream_capacity(quiche_conn *conn, uint64_t stream_id);\n\n// Returns true if the stream has data that can be read.\nbool quiche_conn_stream_readable(const quiche_conn *conn, uint64_t stream_id);\n\n// Returns the next stream that has data to read, or -1 if no such stream is\n// available.\nint64_t quiche_conn_stream_readable_next(quiche_conn *conn);\n\n// Returns true if the stream has enough send capacity.\n//\n// On error a value lower than 0 is returned.\nint quiche_conn_stream_writable(quiche_conn *conn, uint64_t stream_id, size_t len);\n\n// Returns the next stream that can be written to, or -1 if no such stream is\n// available.\nint64_t quiche_conn_stream_writable_next(quiche_conn *conn);\n\n// Returns true if all the data has been read from the specified stream.\nbool quiche_conn_stream_finished(const quiche_conn *conn, uint64_t stream_id);\n\ntypedef struct quiche_stream_iter quiche_stream_iter;\n\n// Returns an iterator over streams that have outstanding data to read.\nquiche_stream_iter *quiche_conn_readable(const quiche_conn *conn);\n\n// Returns an iterator over streams that can be written to.\nquiche_stream_iter *quiche_conn_writable(const quiche_conn *conn);\n\n// Returns the maximum possible size of egress UDP payloads.\nsize_t quiche_conn_max_send_udp_payload_size(const quiche_conn *conn);\n\n// Returns the amount of time until the next timeout event, in nanoseconds.\nuint64_t quiche_conn_timeout_as_nanos(const quiche_conn *conn);\n\n// Returns the amount of time until the next timeout event, in milliseconds.\nuint64_t quiche_conn_timeout_as_millis(const quiche_conn *conn);\n\n// Processes a timeout event.\nvoid quiche_conn_on_timeout(quiche_conn *conn);\n\n// Closes the connection with the given error and reason.\nint quiche_conn_close(quiche_conn *conn, bool app, uint64_t err,\n                      const uint8_t *reason, size_t reason_len);\n\n// Returns a string uniquely representing the connection.\nvoid quiche_conn_trace_id(const quiche_conn *conn, const uint8_t **out, size_t *out_len);\n\n// Returns the source connection ID.\nvoid quiche_conn_source_id(const quiche_conn *conn, const uint8_t **out, size_t *out_len);\n\ntypedef struct quiche_connection_id_iter quiche_connection_id_iter;\n\n// Returns all active source connection IDs.\nquiche_connection_id_iter *quiche_conn_source_ids(quiche_conn *conn);\n\n// Fetches the next id from the given iterator. Returns false if there are\n// no more elements in the iterator.\nbool quiche_connection_id_iter_next(quiche_connection_id_iter *iter,  const uint8_t **out, size_t *out_len);\n\n// Frees the given path iterator object.\nvoid quiche_connection_id_iter_free(quiche_connection_id_iter *iter);\n\n// Returns the destination connection ID.\nvoid quiche_conn_destination_id(const quiche_conn *conn, const uint8_t **out, size_t *out_len);\n\n// Returns the negotiated ALPN protocol.\nvoid quiche_conn_application_proto(const quiche_conn *conn, const uint8_t **out,\n                                   size_t *out_len);\n\n// Returns the peer's leaf certificate (if any) as a DER-encoded buffer.\nvoid quiche_conn_peer_cert(const quiche_conn *conn, const uint8_t **out, size_t *out_len);\n\n// Returns the serialized cryptographic session for the connection.\nvoid quiche_conn_session(const quiche_conn *conn, const uint8_t **out, size_t *out_len);\n\n// Returns the server name requested by the client.\nvoid quiche_conn_server_name(const quiche_conn *conn, const uint8_t **out, size_t *out_len);\n\n// Returns true if the connection handshake is complete.\nbool quiche_conn_is_established(const quiche_conn *conn);\n\n// Returns true if the connection is resumed.\nbool quiche_conn_is_resumed(const quiche_conn *conn);\n\n// Returns true if the connection has a pending handshake that has progressed\n// enough to send or receive early data.\nbool quiche_conn_is_in_early_data(const quiche_conn *conn);\n\n// Returns whether there is stream or DATAGRAM data available to read.\nbool quiche_conn_is_readable(const quiche_conn *conn);\n\n// Returns true if the connection is draining.\nbool quiche_conn_is_draining(const quiche_conn *conn);\n\n// Returns the number of bidirectional streams that can be created\n// before the peer's stream count limit is reached.\nuint64_t quiche_conn_peer_streams_left_bidi(const quiche_conn *conn);\n\n// Returns the number of unidirectional streams that can be created\n// before the peer's stream count limit is reached.\nuint64_t quiche_conn_peer_streams_left_uni(const quiche_conn *conn);\n\n// Returns true if the connection is closed.\nbool quiche_conn_is_closed(const quiche_conn *conn);\n\n// Returns true if the connection was closed due to the idle timeout.\nbool quiche_conn_is_timed_out(const quiche_conn *conn);\n\n// Returns true if a connection error was received, and updates the provided\n// parameters accordingly.\nbool quiche_conn_peer_error(const quiche_conn *conn,\n                            bool *is_app,\n                            uint64_t *error_code,\n                            const uint8_t **reason,\n                            size_t *reason_len);\n\n// Returns true if a connection error was queued or sent, and updates the provided\n// parameters accordingly.\nbool quiche_conn_local_error(const quiche_conn *conn,\n                             bool *is_app,\n                             uint64_t *error_code,\n                             const uint8_t **reason,\n                             size_t *reason_len);\n\n// Fetches the next stream from the given iterator. Returns false if there are\n// no more elements in the iterator.\nbool quiche_stream_iter_next(quiche_stream_iter *iter, uint64_t *stream_id);\n\n// Frees the given stream iterator object.\nvoid quiche_stream_iter_free(quiche_stream_iter *iter);\n\ntypedef struct {\n    // The number of QUIC packets received on this connection.\n    size_t recv;\n\n    // The number of QUIC packets sent on this connection.\n    size_t sent;\n\n    // The number of QUIC packets that were lost.\n    size_t lost;\n\n    // The number of QUIC packets that were marked as lost but later acked.\n    size_t spurious_lost;\n\n    // The number of sent QUIC packets with retransmitted data.\n    size_t retrans;\n\n    // The number of sent bytes.\n    uint64_t sent_bytes;\n\n    // The number of received bytes.\n    uint64_t recv_bytes;\n\n    // The number of bytes acked.\n    uint64_t acked_bytes;\n\n    // The number of bytes lost.\n    uint64_t lost_bytes;\n\n    // The number of stream bytes retransmitted.\n    uint64_t stream_retrans_bytes;\n\n    // The number of DATAGRAM frames received.\n    size_t dgram_recv;\n\n    // The number of DATAGRAM frames sent.\n    size_t dgram_sent;\n\n    // The number of known paths for the connection.\n    size_t paths_count;\n\n    // The number of streams reset by local.\n    uint64_t reset_stream_count_local;\n\n    // The number of streams stopped by local.\n    uint64_t stopped_stream_count_local;\n\n    // The number of streams reset by remote.\n    uint64_t reset_stream_count_remote;\n\n    // The number of streams stopped by remote.\n    uint64_t stopped_stream_count_remote;\n\n    // The number of DATA_BLOCKED frames sent due to hitting the connection\n    // flow control limit.\n    uint64_t data_blocked_sent_count;\n\n    // The number of STREAM_DATA_BLOCKED frames sent due to a stream hitting\n    // the stream flow control limit.\n    uint64_t stream_data_blocked_sent_count;\n\n    // The number of DATA_BLOCKED frames received from the remote.\n    uint64_t data_blocked_recv_count;\n\n    // The number of STREAM_DATA_BLOCKED frames received from the remote.\n    uint64_t stream_data_blocked_recv_count;\n\n    // The number of STREAMS_BLOCKED frames for bidirectional streams received\n    // from the remote, indicating the peer is blocked on opening new\n    // bidirectional streams.\n    uint64_t streams_blocked_bidi_recv_count;\n\n    // The number of STREAMS_BLOCKED frames for unidirectional streams received\n    // from the remote, indicating the peer is blocked on opening new\n    // unidirectional streams.\n    uint64_t streams_blocked_uni_recv_count;\n\n    // The total number of PATH_CHALLENGE frames that were received.\n    uint64_t path_challenge_rx_count;\n\n    // Total duration during which this side of the connection was\n    // actively sending bytes or waiting for those bytes to be acked.\n    uint64_t bytes_in_flight_duration_msec;\n\n    // True if the send buffer is in an inconsistent state, which could lead to\n    // connection stalls  or excess buffering.\n    bool tx_buffered_inconsistent;\n} quiche_stats;\n\n// Collects and returns statistics about the connection.\nvoid quiche_conn_stats(const quiche_conn *conn, quiche_stats *out);\n\ntypedef struct {\n    // The maximum idle timeout.\n    uint64_t peer_max_idle_timeout;\n\n    // The maximum UDP payload size.\n    uint64_t peer_max_udp_payload_size;\n\n    // The initial flow control maximum data for the connection.\n    uint64_t peer_initial_max_data;\n\n    // The initial flow control maximum data for local bidirectional streams.\n    uint64_t peer_initial_max_stream_data_bidi_local;\n\n    // The initial flow control maximum data for remote bidirectional streams.\n    uint64_t peer_initial_max_stream_data_bidi_remote;\n\n    // The initial flow control maximum data for unidirectional streams.\n    uint64_t peer_initial_max_stream_data_uni;\n\n    // The initial maximum bidirectional streams.\n    uint64_t peer_initial_max_streams_bidi;\n\n    // The initial maximum unidirectional streams.\n    uint64_t peer_initial_max_streams_uni;\n\n    // The ACK delay exponent.\n    uint64_t peer_ack_delay_exponent;\n\n    // The max ACK delay.\n    uint64_t peer_max_ack_delay;\n\n    // Whether active migration is disabled.\n    bool peer_disable_active_migration;\n\n    // The active connection ID limit.\n    uint64_t peer_active_conn_id_limit;\n\n    // DATAGRAM frame extension parameter, if any.\n    ssize_t peer_max_datagram_frame_size;\n} quiche_transport_params;\n\n// Returns the peer's transport parameters in |out|. Returns false if we have\n// not yet processed the peer's transport parameters.\nbool quiche_conn_peer_transport_params(const quiche_conn *conn, quiche_transport_params *out);\n\ntypedef struct {\n    // The local address used by this path.\n    struct sockaddr_storage local_addr;\n    socklen_t local_addr_len;\n\n    // The peer address seen by this path.\n    struct sockaddr_storage peer_addr;\n    socklen_t peer_addr_len;\n\n    // The validation state of the path.\n    ssize_t validation_state;\n\n    // Whether this path is active.\n    bool active;\n\n    // The number of QUIC packets received on this path.\n    size_t recv;\n\n    // The number of QUIC packets sent on this path.\n    size_t sent;\n\n    // The number of QUIC packets that were lost on this path.\n    size_t lost;\n\n    // The number of sent QUIC packets with retransmitted data on this path.\n    size_t retrans;\n\n    // The number of times PTO (probe timeout) fired.\n    //\n    // Loss usually happens in a burst so the number of packets lost will\n    // depend on the volume of inflight packets at the time of loss (which\n    // can be arbitrary). PTO count measures the number of loss events and\n    // provides a normalized loss metric.\n    size_t total_pto_count;\n\n    /// The number of DATAGRAM frames received.\n    size_t dgram_recv;\n\n    /// The number of DATAGRAM frames sent.\n    size_t dgram_sent;\n\n    // The estimated round-trip time of the path (in nanoseconds).\n    uint64_t rtt;\n\n    // The minimum round-trip time observed (in nanoseconds).\n    uint64_t min_rtt;\n\n    // The maximum round-trip time observed (in nanoseconds).\n    uint64_t max_rtt;\n\n    // The estimated round-trip time variation (in nanoseconds).\n    uint64_t rttvar;\n\n    // The size of the path's congestion window in bytes.\n    size_t cwnd;\n\n    // The number of sent bytes on this path.\n    uint64_t sent_bytes;\n\n    // The number of received bytes on this path.\n    uint64_t recv_bytes;\n\n    // The number of bytes lost on this path.\n    uint64_t lost_bytes;\n\n    // The number of stream bytes retransmitted on this path.\n    uint64_t stream_retrans_bytes;\n\n    // The current PMTU for the path.\n    size_t pmtu;\n\n    // The most recent data delivery rate estimate in bytes/s.\n    uint64_t delivery_rate;\n\n    /// The maximum bandwidth estimate for the connection in bytes/s.\n    uint64_t max_bandwidth;\n\n    // The congestion window in bytes at the end of the startup or slow start,\n    // or 0 if the connection is still in startup.\n    uint64_t startup_exit_cwnd;\n} quiche_path_stats;\n\n\n// Collects and returns statistics about the specified path for the connection.\n//\n// The `idx` argument represent the path's index (also see the `paths_count`\n// field of `quiche_stats`).\nint quiche_conn_path_stats(const quiche_conn *conn, size_t idx, quiche_path_stats *out);\n\n// Returns whether or not this is a server-side connection.\nbool quiche_conn_is_server(const quiche_conn *conn);\n\n// Returns the maximum DATAGRAM payload that can be sent.\nssize_t quiche_conn_dgram_max_writable_len(const quiche_conn *conn);\n\n// Returns the length of the first stored DATAGRAM.\nssize_t quiche_conn_dgram_recv_front_len(const quiche_conn *conn);\n\n// Returns the number of items in the DATAGRAM receive queue.\nssize_t quiche_conn_dgram_recv_queue_len(const quiche_conn *conn);\n\n// Returns the total size of all items in the DATAGRAM receive queue.\nssize_t quiche_conn_dgram_recv_queue_byte_size(const quiche_conn *conn);\n\n// Returns the number of items in the DATAGRAM send queue.\nssize_t quiche_conn_dgram_send_queue_len(const quiche_conn *conn);\n\n// Returns the total size of all items in the DATAGRAM send queue.\nssize_t quiche_conn_dgram_send_queue_byte_size(const quiche_conn *conn);\n\n// Reads the first received DATAGRAM.\nssize_t quiche_conn_dgram_recv(quiche_conn *conn, uint8_t *buf,\n                               size_t buf_len);\n\n// Sends data in a DATAGRAM frame.\nssize_t quiche_conn_dgram_send(quiche_conn *conn, const uint8_t *buf,\n                               size_t buf_len);\n\n// Purges queued outgoing DATAGRAMs matching the predicate.\nvoid quiche_conn_dgram_purge_outgoing(quiche_conn *conn,\n                                      bool (*f)(uint8_t *, size_t));\n\n// Returns whether or not the DATAGRAM send queue is full.\nbool quiche_conn_is_dgram_send_queue_full(const quiche_conn *conn);\n\n// Returns whether or not the DATAGRAM recv queue is full.\nbool quiche_conn_is_dgram_recv_queue_full(const quiche_conn *conn);\n\n// Schedule an ack-eliciting packet on the active path.\nssize_t quiche_conn_send_ack_eliciting(quiche_conn *conn);\n\n// Schedule an ack-eliciting packet on the specified path.\nssize_t quiche_conn_send_ack_eliciting_on_path(quiche_conn *conn,\n                           const struct sockaddr *local, socklen_t local_len,\n                           const struct sockaddr *peer, socklen_t peer_len);\n\n// Returns true if there are retired source connection ids and fill the parameters\nbool quiche_conn_retired_scid_next(const quiche_conn *conn, const uint8_t **out, size_t *out_len);\n\n// Returns the number of source Connection IDs that are retired.\nsize_t quiche_conn_retired_scids(const quiche_conn *conn);\n\n// Returns the number of spare Destination Connection IDs, i.e.,\n// Destination Connection IDs that are still unused.\nsize_t quiche_conn_available_dcids(const quiche_conn *conn);\n\n// Returns the number of source Connection IDs that should be provided\n// to the peer without exceeding the limit it advertised.\nsize_t quiche_conn_scids_left(quiche_conn *conn);\n\n// Returns the number of source Connection IDs that are active. This is\n// only meaningful if the host uses non-zero length Source Connection IDs.\nsize_t quiche_conn_active_scids(quiche_conn *conn);\n\n// Provides additional source Connection IDs that the peer can use to reach\n// this host. Writes the sequence number to \"scid_seq\" and returns 0.\nint quiche_conn_new_scid(quiche_conn *conn,\n                           const uint8_t *scid, size_t scid_len,\n                           const uint8_t *reset_token, bool retire_if_needed, uint64_t *scid_seq);\n\n// Requests the stack to perform path validation of the proposed 4-tuple.\nint quiche_conn_probe_path(quiche_conn *conn,\n                                const struct sockaddr *local, socklen_t local_len,\n                                const struct sockaddr *peer, socklen_t peer_len, uint64_t *seq);\n\n// Migrates the connection to a new local address.\nint quiche_conn_migrate_source(quiche_conn *conn, const struct sockaddr *local, socklen_t local_len, uint64_t *seq);\n\n// Migrates the connection over the given network path between \"local\"\n// and \"peer\".\nint quiche_conn_migrate(quiche_conn *conn,\n                             const struct sockaddr *local, socklen_t local_len,\n                             const struct sockaddr *peer, socklen_t peer_len,\n                             uint64_t *seq);\n\nenum quiche_path_event_type {\n    QUICHE_PATH_EVENT_NEW,\n    QUICHE_PATH_EVENT_VALIDATED,\n    QUICHE_PATH_EVENT_FAILED_VALIDATION,\n    QUICHE_PATH_EVENT_CLOSED,\n    QUICHE_PATH_EVENT_REUSED_SOURCE_CONNECTION_ID,\n    QUICHE_PATH_EVENT_PEER_MIGRATED,\n};\n\ntypedef struct quiche_path_event quiche_path_event;\n\n// Retrieves the next event. Returns NULL if there is no event to process.\nquiche_path_event *quiche_conn_path_event_next(quiche_conn *conn);\n\n// Returns the type of the event.\nenum quiche_path_event_type quiche_path_event_type(const quiche_path_event *ev);\n\n// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_NEW.\nvoid quiche_path_event_new(const quiche_path_event *ev,\n                           struct sockaddr_storage *local, socklen_t *local_len, struct sockaddr_storage *peer, socklen_t *peer_len);\n\n// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_VALIDATED.\nvoid quiche_path_event_validated(const quiche_path_event *ev,\n                           struct sockaddr_storage *local, socklen_t *local_len, struct sockaddr_storage *peer, socklen_t *peer_len);\n\n// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_FAILED_VALIDATION.\nvoid quiche_path_event_failed_validation(const quiche_path_event *ev,\n                           struct sockaddr_storage *local, socklen_t *local_len, struct sockaddr_storage *peer, socklen_t *peer_len);\n\n// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_CLOSED.\nvoid quiche_path_event_closed(const quiche_path_event *ev,\n                           struct sockaddr_storage *local, socklen_t *local_len, struct sockaddr_storage *peer, socklen_t *peer_len);\n\n// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_REUSED_SOURCE_CONNECTION_ID.\nvoid quiche_path_event_reused_source_connection_id(const quiche_path_event *ev, uint64_t *id,\n                           struct sockaddr_storage *old_local, socklen_t *old_local_len,\n                           struct sockaddr_storage *old_peer, socklen_t *old_peer_len,\n                           struct sockaddr_storage *local, socklen_t *local_len,\n                           struct sockaddr_storage *peer, socklen_t *peer_len);\n\n// Should be called if the quiche_path_event_type(...) returns QUICHE_PATH_EVENT_PEER_MIGRATED.\nvoid quiche_path_event_peer_migrated(const quiche_path_event *ev,\n                           struct sockaddr_storage *local, socklen_t *local_len,\n                           struct sockaddr_storage *peer, socklen_t *peer_len);\n\n// Frees the path event object.\nvoid quiche_path_event_free(quiche_path_event *ev);\n\n// Requests the retirement of the destination Connection ID used by the\n// host to reach its peer.\nint quiche_conn_retire_dcid(quiche_conn *conn, uint64_t dcid_seq);\n\ntypedef struct quiche_socket_addr_iter quiche_socket_addr_iter;\n\n// Returns an iterator over destination `SockAddr`s whose association\n// with \"from\" forms a known QUIC path on which packets can be sent to.\nquiche_socket_addr_iter *quiche_conn_paths_iter(quiche_conn *conn, const struct sockaddr *from, size_t from_len);\n\n// Fetches the next peer from the given iterator. Returns false if there are\n// no more elements in the iterator.\nbool quiche_socket_addr_iter_next(quiche_socket_addr_iter *iter, struct sockaddr_storage *peer, size_t *peer_len);\n\n// Frees the given path iterator object.\nvoid quiche_socket_addr_iter_free(quiche_socket_addr_iter *iter);\n\n// Returns whether the network path with local address \"from and remote address \"to\" has been validated.\n// If the 4-tuple does not exist over the connection, returns an InvalidState.\nint quiche_conn_is_path_validated(const quiche_conn *conn, const struct sockaddr *from, size_t from_len, const struct sockaddr *to, size_t to_len);\n\n// Frees the connection object.\nvoid quiche_conn_free(quiche_conn *conn);\n\n// Writes an unsigned variable-length integer in network byte-order into\n// the provided buffer.\nint quiche_put_varint(uint8_t *buf, size_t buf_len,\n                      uint64_t val);\n\n// Reads an unsigned variable-length integer in network byte-order from\n// the provided buffer and returns the wire length.\nssize_t quiche_get_varint(const uint8_t *buf, size_t buf_len,\n                          uint64_t *val);\n\n// HTTP/3 API\n//\n\n// List of ALPN tokens of supported HTTP/3 versions.\n#define QUICHE_H3_APPLICATION_PROTOCOL \"\\x02h3\"\n\nenum quiche_h3_error {\n    // There is no error or no work to do\n    QUICHE_H3_ERR_DONE = -1,\n\n    // The provided buffer is too short.\n    QUICHE_H3_ERR_BUFFER_TOO_SHORT = -2,\n\n    // Internal error in the HTTP/3 stack.\n    QUICHE_H3_ERR_INTERNAL_ERROR = -3,\n\n    // Endpoint detected that the peer is exhibiting behavior that causes.\n    // excessive load.\n    QUICHE_H3_ERR_EXCESSIVE_LOAD = -4,\n\n    // Stream ID or Push ID greater that current maximum was\n    // used incorrectly, such as exceeding a limit, reducing a limit,\n    // or being reused.\n    QUICHE_H3_ERR_ID_ERROR= -5,\n\n    // The endpoint detected that its peer created a stream that it will not\n    // accept.\n    QUICHE_H3_ERR_STREAM_CREATION_ERROR = -6,\n\n    // A required critical stream was closed.\n    QUICHE_H3_ERR_CLOSED_CRITICAL_STREAM = -7,\n\n    // No SETTINGS frame at beginning of control stream.\n    QUICHE_H3_ERR_MISSING_SETTINGS = -8,\n\n    // A frame was received which is not permitted in the current state.\n    QUICHE_H3_ERR_FRAME_UNEXPECTED = -9,\n\n    // Frame violated layout or size rules.\n    QUICHE_H3_ERR_FRAME_ERROR = -10,\n\n    // QPACK Header block decompression failure.\n    QUICHE_H3_ERR_QPACK_DECOMPRESSION_FAILED = -11,\n\n    // -12 was previously used for TransportError, skip it\n\n    // The underlying QUIC stream (or connection) doesn't have enough capacity\n    // for the operation to complete. The application should retry later on.\n    QUICHE_H3_ERR_STREAM_BLOCKED = -13,\n\n    // Error in the payload of a SETTINGS frame.\n    QUICHE_H3_ERR_SETTINGS_ERROR = -14,\n\n    // Server rejected request.\n    QUICHE_H3_ERR_REQUEST_REJECTED = -15,\n\n    // Request or its response cancelled.\n    QUICHE_H3_ERR_REQUEST_CANCELLED = -16,\n\n    // Client's request stream terminated without containing a full-formed\n    // request.\n    QUICHE_H3_ERR_REQUEST_INCOMPLETE = -17,\n\n    // An HTTP message was malformed and cannot be processed.\n    QUICHE_H3_ERR_MESSAGE_ERROR = -18,\n\n    // The TCP connection established in response to a CONNECT request was\n    // reset or abnormally closed.\n    QUICHE_H3_ERR_CONNECT_ERROR = -19,\n\n    // The requested operation cannot be served over HTTP/3. Peer should retry\n    // over HTTP/1.1.\n    QUICHE_H3_ERR_VERSION_FALLBACK = -20,\n\n    // The following QUICHE_H3_TRANSPORT_ERR_* errors are propagated\n    // from the QUIC transport layer.\n\n    // See QUICHE_ERR_DONE.\n    QUICHE_H3_TRANSPORT_ERR_DONE = QUICHE_ERR_DONE - 1000,\n\n    // See QUICHE_ERR_BUFFER_TOO_SHORT.\n    QUICHE_H3_TRANSPORT_ERR_BUFFER_TOO_SHORT = QUICHE_ERR_BUFFER_TOO_SHORT - 1000,\n\n    // See QUICHE_ERR_UNKNOWN_VERSION.\n    QUICHE_H3_TRANSPORT_ERR_UNKNOWN_VERSION = QUICHE_ERR_UNKNOWN_VERSION - 1000,\n\n    // See QUICHE_ERR_INVALID_FRAME.\n    QUICHE_H3_TRANSPORT_ERR_INVALID_FRAME = QUICHE_ERR_INVALID_FRAME - 1000,\n\n    // See QUICHE_ERR_INVALID_PACKET.\n    QUICHE_H3_TRANSPORT_ERR_INVALID_PACKET = QUICHE_ERR_INVALID_PACKET - 1000,\n\n    // See QUICHE_ERR_INVALID_STATE.\n    QUICHE_H3_TRANSPORT_ERR_INVALID_STATE = QUICHE_ERR_INVALID_STATE - 1000,\n\n    // See QUICHE_ERR_INVALID_STREAM_STATE.\n    QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE = QUICHE_ERR_INVALID_STREAM_STATE - 1000,\n\n    // See QUICHE_ERR_INVALID_TRANSPORT_PARAM.\n    QUICHE_H3_TRANSPORT_ERR_INVALID_TRANSPORT_PARAM = QUICHE_ERR_INVALID_TRANSPORT_PARAM - 1000,\n\n    // See QUICHE_ERR_CRYPTO_FAIL.\n    QUICHE_H3_TRANSPORT_ERR_CRYPTO_FAIL = QUICHE_ERR_CRYPTO_FAIL - 1000,\n\n    // See QUICHE_ERR_TLS_FAIL.\n    QUICHE_H3_TRANSPORT_ERR_TLS_FAIL = QUICHE_ERR_TLS_FAIL - 1000,\n\n    // See QUICHE_ERR_FLOW_CONTROL.\n    QUICHE_H3_TRANSPORT_ERR_FLOW_CONTROL = QUICHE_ERR_FLOW_CONTROL - 1000,\n\n    // See QUICHE_ERR_STREAM_LIMIT.\n    QUICHE_H3_TRANSPORT_ERR_STREAM_LIMIT = QUICHE_ERR_STREAM_LIMIT - 1000,\n\n    // See QUICHE_ERR_STREAM_STOPPED.\n    QUICHE_H3_TRANSPORT_ERR_STREAM_STOPPED = QUICHE_ERR_STREAM_STOPPED - 1000,\n\n    // See QUICHE_ERR_STREAM_RESET.\n    QUICHE_H3_TRANSPORT_ERR_STREAM_RESET = QUICHE_ERR_STREAM_RESET - 1000,\n\n    // See QUICHE_ERR_FINAL_SIZE.\n    QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE = QUICHE_ERR_FINAL_SIZE - 1000,\n\n    // See QUICHE_ERR_CONGESTION_CONTROL.\n    QUICHE_H3_TRANSPORT_ERR_CONGESTION_CONTROL = QUICHE_ERR_CONGESTION_CONTROL - 1000,\n\n    // See QUICHE_ERR_ID_LIMIT.\n    QUICHE_H3_TRANSPORT_ERR_ID_LIMIT = QUICHE_ERR_ID_LIMIT - 1000,\n\n    // See QUICHE_ERR_OUT_OF_IDENTIFIERS.\n    QUICHE_H3_TRANSPORT_ERR_OUT_OF_IDENTIFIERS = QUICHE_ERR_OUT_OF_IDENTIFIERS - 1000,\n\n    // See QUICHE_ERR_KEY_UPDATE.\n    QUICHE_H3_TRANSPORT_ERR_KEY_UPDATE = QUICHE_ERR_KEY_UPDATE - 1000,\n\n    // See QUICHE_ERR_CRYPTO_BUFFER_EXCEEDED.\n    QUICHE_H3_TRANSPORT_ERR_CRYPTO_BUFFER_EXCEEDED = QUICHE_ERR_CRYPTO_BUFFER_EXCEEDED - 1000,\n\n    // See QUICHE_ERR_INVALID_ACK_RANGE.\n    QUICHE_H3_TRANSPORT_ERR_INVALID_ACK_RANGE = QUICHE_ERR_INVALID_ACK_RANGE - 1000,\n\n    // See QUICHE_ERR_OPTIMISTIC_ACK_DETECTED.\n    QUICHE_H3_TRANSPORT_ERR_OPTIMISTIC_ACK_DETECTED = QUICHE_ERR_OPTIMISTIC_ACK_DETECTED - 1000,\n};\n\n// Stores configuration shared between multiple connections.\ntypedef struct quiche_h3_config quiche_h3_config;\n\n// Creates an HTTP/3 config object with default settings values.\nquiche_h3_config *quiche_h3_config_new(void);\n\n// Sets the `SETTINGS_MAX_FIELD_SECTION_SIZE` setting.\nvoid quiche_h3_config_set_max_field_section_size(quiche_h3_config *config, uint64_t v);\n\n// Sets the `SETTINGS_QPACK_MAX_TABLE_CAPACITY` setting.\nvoid quiche_h3_config_set_qpack_max_table_capacity(quiche_h3_config *config, uint64_t v);\n\n// Sets the `SETTINGS_QPACK_BLOCKED_STREAMS` setting.\nvoid quiche_h3_config_set_qpack_blocked_streams(quiche_h3_config *config, uint64_t v);\n\n// Sets the `SETTINGS_ENABLE_CONNECT_PROTOCOL` setting.\nvoid quiche_h3_config_enable_extended_connect(quiche_h3_config *config, bool enabled);\n\n// Frees the HTTP/3 config object.\nvoid quiche_h3_config_free(quiche_h3_config *config);\n\n// An HTTP/3 connection.\ntypedef struct quiche_h3_conn quiche_h3_conn;\n\n// Creates a new HTTP/3 connection using the provided QUIC connection.\nquiche_h3_conn *quiche_h3_conn_new_with_transport(quiche_conn *quiche_conn,\n                                                  quiche_h3_config *config);\n\nenum quiche_h3_event_type {\n    QUICHE_H3_EVENT_HEADERS,\n    QUICHE_H3_EVENT_DATA,\n    QUICHE_H3_EVENT_FINISHED,\n    QUICHE_H3_EVENT_GOAWAY,\n    QUICHE_H3_EVENT_RESET,\n    QUICHE_H3_EVENT_PRIORITY_UPDATE,\n};\n\ntypedef struct quiche_h3_event quiche_h3_event;\n\n// Processes HTTP/3 data received from the peer.\nint64_t quiche_h3_conn_poll(quiche_h3_conn *conn, quiche_conn *quic_conn,\n                            quiche_h3_event **ev);\n\n// Returns the type of the event.\nenum quiche_h3_event_type quiche_h3_event_type(quiche_h3_event *ev);\n\n// Iterates over the headers in the event.\n//\n// The `cb` callback will be called for each header in `ev`. `cb` should check\n// the validity of pseudo-headers and headers. If `cb` returns any value other\n// than `0`, processing will be interrupted and the value is returned to the\n// caller.\nint quiche_h3_event_for_each_header(quiche_h3_event *ev,\n                                    int (*cb)(uint8_t *name, size_t name_len,\n                                              uint8_t *value, size_t value_len,\n                                              void *argp),\n                                    void *argp);\n\n// Iterates over the peer's HTTP/3 settings.\n//\n// The `cb` callback will be called for each setting in `conn`.\n// If `cb` returns any value other than `0`, processing will be interrupted and\n// the value is returned to the caller.\nint quiche_h3_for_each_setting(quiche_h3_conn *conn,\n                               int (*cb)(uint64_t identifier,\n                                         uint64_t value, void *argp),\n                               void *argp);\n\n// Check whether more frames will follow the headers on the stream.\nbool quiche_h3_event_headers_has_more_frames(quiche_h3_event *ev);\n\n// Check whether or not extended connection is enabled by the peer\nbool quiche_h3_extended_connect_enabled_by_peer(quiche_h3_conn *conn);\n\n// Frees the HTTP/3 event object.\nvoid quiche_h3_event_free(quiche_h3_event *ev);\n\ntypedef struct {\n    const uint8_t *name;\n    size_t name_len;\n\n    const uint8_t *value;\n    size_t value_len;\n} quiche_h3_header;\n\n// Extensible Priorities parameters.\ntypedef struct {\n    uint8_t urgency;\n    bool incremental;\n} quiche_h3_priority;\n\n// Sends an HTTP/3 request.\nint64_t quiche_h3_send_request(quiche_h3_conn *conn, quiche_conn *quic_conn,\n                               const quiche_h3_header *headers, size_t headers_len,\n                               bool fin);\n\n// Sends an HTTP/3 response on the specified stream with default priority.\nint quiche_h3_send_response(quiche_h3_conn *conn, quiche_conn *quic_conn,\n                            uint64_t stream_id, const quiche_h3_header *headers,\n                            size_t headers_len, bool fin);\n\n// Sends an HTTP/3 response on the specified stream with specified priority.\nint quiche_h3_send_response_with_priority(quiche_h3_conn *conn,\n                            quiche_conn *quic_conn, uint64_t stream_id,\n                            const quiche_h3_header *headers, size_t headers_len,\n                            quiche_h3_priority *priority, bool fin);\n\n// Sends additional HTTP/3 headers on the specified stream.\nint quiche_h3_send_additional_headers(quiche_h3_conn *conn,\n                            quiche_conn *quic_conn, uint64_t stream_id,\n                            quiche_h3_header *headers, size_t headers_len,\n                            bool is_trailer_section, bool fin);\n\n// Sends an HTTP/3 body chunk on the given stream.\nssize_t quiche_h3_send_body(quiche_h3_conn *conn, quiche_conn *quic_conn,\n                            uint64_t stream_id, const uint8_t *body, size_t body_len,\n                            bool fin);\n\n// Reads request or response body data into the provided buffer.\nssize_t quiche_h3_recv_body(quiche_h3_conn *conn, quiche_conn *quic_conn,\n                            uint64_t stream_id, uint8_t *out, size_t out_len);\n\n// Sends a GOAWAY frame to initiate graceful connection closure.\nint quiche_h3_send_goaway(quiche_h3_conn *conn, quiche_conn *quic_conn,\n                          uint64_t id);\n\n// Try to parse an Extensible Priority field value.\nint quiche_h3_parse_extensible_priority(uint8_t *priority,\n                                        size_t priority_len,\n                                        quiche_h3_priority *parsed);\n\n/// Sends a PRIORITY_UPDATE frame on the control stream with specified\n/// request stream ID and priority.\nint quiche_h3_send_priority_update_for_request(quiche_h3_conn *conn,\n                                               quiche_conn *quic_conn,\n                                               uint64_t stream_id,\n                                               quiche_h3_priority *priority);\n\n// Take the last received PRIORITY_UPDATE frame for a stream.\n//\n// The `cb` callback will be called once. `cb` should check the validity of\n// priority field value contents. If `cb` returns any value other than `0`,\n// processing will be interrupted and the value is returned to the caller.\nint quiche_h3_take_last_priority_update(quiche_h3_conn *conn,\n                                        uint64_t prioritized_element_id,\n                                        int (*cb)(uint8_t  *priority_field_value,\n                                                  uint64_t priority_field_value_len,\n                                                  void *argp),\n                                        void *argp);\n\n// Returns whether the peer enabled HTTP/3 DATAGRAM frame support.\nbool quiche_h3_dgram_enabled_by_peer(quiche_h3_conn *conn,\n                                     quiche_conn *quic_conn);\n\ntypedef struct {\n    // The number of bytes received on the QPACK encoder stream.\n    uint64_t qpack_encoder_stream_recv_bytes;\n\n    // The number of bytes received on the QPACK decoder stream.\n    uint64_t qpack_decoder_stream_recv_bytes;\n} quiche_h3_stats;\n\n// Collects and returns statistics about the connection.\nvoid quiche_h3_conn_stats(const quiche_h3_conn *conn, quiche_h3_stats *out);\n\n// Frees the HTTP/3 connection object.\nvoid quiche_h3_conn_free(quiche_h3_conn *conn);\n\n#if defined(__cplusplus)\n}  // extern C\n#endif\n\n#endif // QUICHE_H\n"
  },
  {
    "path": "quiche/src/buffers.rs",
    "content": "// Copyright (C) 2026, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::fmt::Debug;\nuse std::sync::Arc;\n\n/// A trait for providing internal storage buffers for streams,\n/// enabling zero-copy operations.\n/// The associated type `Buf` can be any type that dereferences to\n/// a slice, but should be fast to clone, eg. by wrapping it with an\n/// [`Arc`].\npub trait BufFactory: Clone + Default + Debug {\n    /// The type of the generated buffer. The clone operation should be cheap,\n    /// e.g., by using an [`Arc`].\n    type Buf: Clone + Debug + AsRef<[u8]>;\n\n    /// Generate a new buffer from a given slice, the buffer must contain the\n    /// same data as the original slice.\n    fn buf_from_slice(buf: &[u8]) -> Self::Buf;\n}\n\n/// A trait that enables zero-copy sends to quiche. When buffers produced\n/// by the `BufFactory` implement this trait, quiche and h3 can supply the\n/// raw buffers to be sent, instead of slices that must be copied first.\npub trait BufSplit {\n    /// Split the buffer at a given point, after the split the old buffer\n    /// must only contain the first `at` bytes, while the newly produced\n    /// buffer must containt the remaining bytes.\n    fn split_at(&mut self, at: usize) -> Self;\n\n    /// Try to prepend a prefix to the buffer, return true if succeeded.\n    fn try_add_prefix(&mut self, _prefix: &[u8]) -> bool {\n        false\n    }\n}\n\n/// The default [`BufFactory`] allocates buffers on the heap on demand.\n#[derive(Debug, Clone, Default)]\npub struct DefaultBufFactory;\n\n/// The default [`BufFactory::Buf`] is a boxed slice wrapped in an [`Arc`].\n#[derive(Debug, Clone, Default)]\npub struct DefaultBuf(Arc<Box<[u8]>>);\n\nimpl BufFactory for DefaultBufFactory {\n    type Buf = DefaultBuf;\n\n    fn buf_from_slice(buf: &[u8]) -> Self::Buf {\n        DefaultBuf(Arc::new(buf.into()))\n    }\n}\n\nimpl AsRef<[u8]> for DefaultBuf {\n    fn as_ref(&self) -> &[u8] {\n        &self.0[..]\n    }\n}\n"
  },
  {
    "path": "quiche/src/build.rs",
    "content": "// Additional parameters for Android build of BoringSSL.\n//\n// Requires Android NDK >= 19.\nconst CMAKE_PARAMS_ANDROID_NDK: &[(&str, &[(&str, &str)])] = &[\n    (\"aarch64\", &[(\"ANDROID_ABI\", \"arm64-v8a\")]),\n    (\"arm\", &[(\"ANDROID_ABI\", \"armeabi-v7a\")]),\n    (\"x86\", &[(\"ANDROID_ABI\", \"x86\")]),\n    (\"x86_64\", &[(\"ANDROID_ABI\", \"x86_64\")]),\n];\n\n// iOS.\nconst CMAKE_PARAMS_IOS: &[(&str, &[(&str, &str)])] = &[\n    (\"aarch64\", &[\n        (\"CMAKE_OSX_ARCHITECTURES\", \"arm64\"),\n        (\"CMAKE_OSX_SYSROOT\", \"iphoneos\"),\n    ]),\n    (\"x86_64\", &[\n        (\"CMAKE_OSX_ARCHITECTURES\", \"x86_64\"),\n        (\"CMAKE_OSX_SYSROOT\", \"iphonesimulator\"),\n    ]),\n];\n\n// ARM Linux.\nconst CMAKE_PARAMS_ARM_LINUX: &[(&str, &[(&str, &str)])] = &[\n    (\"aarch64\", &[(\"CMAKE_SYSTEM_PROCESSOR\", \"aarch64\")]),\n    (\"arm\", &[(\"CMAKE_SYSTEM_PROCESSOR\", \"arm\")]),\n];\n\n/// Returns the platform-specific output path for lib.\n///\n/// MSVC generator on Windows place static libs in a target sub-folder,\n/// so adjust library location based on platform and build target.\n/// See issue: https://github.com/alexcrichton/cmake-rs/issues/18\nfn get_boringssl_platform_output_path() -> String {\n    if cfg!(target_env = \"msvc\") {\n        // Code under this branch should match the logic in cmake-rs\n        let debug_env_var =\n            std::env::var(\"DEBUG\").expect(\"DEBUG variable not defined in env\");\n\n        let deb_info = match &debug_env_var[..] {\n            \"false\" => false,\n            \"true\" => true,\n            unknown => panic!(\"Unknown DEBUG={unknown} env var.\"),\n        };\n\n        let opt_env_var = std::env::var(\"OPT_LEVEL\")\n            .expect(\"OPT_LEVEL variable not defined in env\");\n\n        let subdir = match &opt_env_var[..] {\n            \"0\" => \"Debug\",\n            \"1\" | \"2\" | \"3\" =>\n                if deb_info {\n                    \"RelWithDebInfo\"\n                } else {\n                    \"Release\"\n                },\n            \"s\" | \"z\" => \"MinSizeRel\",\n            unknown => panic!(\"Unknown OPT_LEVEL={unknown} env var.\"),\n        };\n\n        subdir.to_string()\n    } else {\n        \"\".to_string()\n    }\n}\n\n/// Returns a new cmake::Config for building BoringSSL.\n///\n/// It will add platform-specific parameters if needed.\nfn get_boringssl_cmake_config() -> cmake::Config {\n    let arch = std::env::var(\"CARGO_CFG_TARGET_ARCH\").unwrap();\n    let os = std::env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n    let pwd = std::env::current_dir().unwrap();\n\n    let mut boringssl_cmake = cmake::Config::new(\"deps/boringssl\");\n\n    // Add platform-specific parameters.\n    match os.as_ref() {\n        \"android\" => {\n            // We need ANDROID_NDK_HOME to be set properly.\n            let android_ndk_home = std::env::var(\"ANDROID_NDK_HOME\")\n                .expect(\"Please set ANDROID_NDK_HOME for Android build\");\n            let android_ndk_home = std::path::Path::new(&android_ndk_home);\n            for (android_arch, params) in CMAKE_PARAMS_ANDROID_NDK {\n                if *android_arch == arch {\n                    for (name, value) in *params {\n                        boringssl_cmake.define(name, value);\n                    }\n                }\n            }\n            let toolchain_file =\n                android_ndk_home.join(\"build/cmake/android.toolchain.cmake\");\n            let toolchain_file = toolchain_file.to_str().unwrap();\n            boringssl_cmake.define(\"CMAKE_TOOLCHAIN_FILE\", toolchain_file);\n\n            // 21 is the minimum level tested. You can give higher value.\n            boringssl_cmake.define(\"ANDROID_NATIVE_API_LEVEL\", \"21\");\n            boringssl_cmake.define(\"ANDROID_STL\", \"c++_shared\");\n\n            boringssl_cmake\n        },\n\n        \"ios\" => {\n            for (ios_arch, params) in CMAKE_PARAMS_IOS {\n                if *ios_arch == arch {\n                    for (name, value) in *params {\n                        boringssl_cmake.define(name, value);\n                    }\n                }\n            }\n\n            // Bitcode is always on.\n            let bitcode_cflag = \"-fembed-bitcode\";\n\n            // Hack for Xcode 10.1.\n            let target_cflag = if arch == \"x86_64\" {\n                \"-target x86_64-apple-ios-simulator\"\n            } else {\n                \"\"\n            };\n\n            let cflag = format!(\"{bitcode_cflag} {target_cflag}\");\n\n            boringssl_cmake.define(\"CMAKE_ASM_FLAGS\", &cflag);\n            boringssl_cmake.cflag(&cflag);\n\n            boringssl_cmake\n        },\n\n        \"linux\" => match arch.as_ref() {\n            \"aarch64\" | \"arm\" => {\n                for (arm_arch, params) in CMAKE_PARAMS_ARM_LINUX {\n                    if *arm_arch == arch {\n                        for (name, value) in *params {\n                            boringssl_cmake.define(name, value);\n                        }\n                    }\n                }\n                boringssl_cmake.define(\"CMAKE_SYSTEM_NAME\", \"Linux\");\n                boringssl_cmake.define(\"CMAKE_SYSTEM_VERSION\", \"1\");\n\n                boringssl_cmake\n            },\n\n            \"x86\" => {\n                boringssl_cmake.define(\n                    \"CMAKE_TOOLCHAIN_FILE\",\n                    pwd.join(\"deps/boringssl/src/util/32-bit-toolchain.cmake\")\n                        .as_os_str(),\n                );\n\n                boringssl_cmake\n            },\n\n            _ => boringssl_cmake,\n        },\n\n        _ => {\n            // Configure BoringSSL for building on 32-bit non-windows platforms.\n            if arch == \"x86\" && os != \"windows\" {\n                boringssl_cmake.define(\n                    \"CMAKE_TOOLCHAIN_FILE\",\n                    pwd.join(\"deps/boringssl/src/util/32-bit-toolchain.cmake\")\n                        .as_os_str(),\n                );\n            }\n\n            boringssl_cmake\n        },\n    }\n}\n\nfn write_pkg_config() {\n    use std::io::prelude::*;\n\n    let manifest_dir = std::env::var(\"CARGO_MANIFEST_DIR\").unwrap();\n    let target_dir = target_dir_path();\n\n    let out_path = target_dir.as_path().join(\"quiche.pc\");\n    let mut out_file = std::fs::File::create(out_path).unwrap();\n\n    let include_dir = format!(\"{manifest_dir}/include\");\n\n    let version = std::env::var(\"CARGO_PKG_VERSION\").unwrap();\n\n    let output = format!(\n        \"# quiche\n\nincludedir={include_dir}\nlibdir={}\n\nName: quiche\nDescription: quiche library\nURL: https://github.com/cloudflare/quiche\nVersion: {version}\nLibs: -Wl,-rpath,${{libdir}} -L${{libdir}} -lquiche\nCflags: -I${{includedir}}\n\",\n        target_dir.to_str().unwrap(),\n    );\n\n    out_file.write_all(output.as_bytes()).unwrap();\n}\n\nfn target_dir_path() -> std::path::PathBuf {\n    let out_dir = std::env::var(\"OUT_DIR\").unwrap();\n    let out_dir = std::path::Path::new(&out_dir);\n\n    for p in out_dir.ancestors() {\n        if p.ends_with(\"build\") {\n            return p.parent().unwrap().to_path_buf();\n        }\n    }\n\n    unreachable!();\n}\n\nfn main() {\n    if cfg!(feature = \"boringssl-vendored\") &&\n        !cfg!(feature = \"boringssl-boring-crate\") &&\n        !cfg!(feature = \"openssl\")\n    {\n        let bssl_dir = std::env::var(\"QUICHE_BSSL_PATH\").unwrap_or_else(|_| {\n            let mut cfg = get_boringssl_cmake_config();\n\n            if cfg!(feature = \"fuzzing\") {\n                cfg.cxxflag(\"-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE\")\n                    .cxxflag(\"-DBORINGSSL_UNSAFE_FUZZER_MODE\");\n                cfg.cflag(\"-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE\")\n                    .cflag(\"-DBORINGSSL_UNSAFE_FUZZER_MODE\");\n            }\n\n            cfg.build_target(\"ssl\").build();\n            cfg.build_target(\"crypto\").build().display().to_string()\n        });\n\n        println!(\"cargo:rustc-link-arg=-Wl,-rpath,{bssl_dir}\");\n\n        let build_path = get_boringssl_platform_output_path();\n        let mut build_dir = format!(\"{bssl_dir}/build/{build_path}\");\n\n        // If build directory doesn't exist, use the specified path as is.\n        if !std::path::Path::new(&build_dir).is_dir() {\n            build_dir = bssl_dir;\n        }\n\n        println!(\"cargo:rustc-link-search=native={build_dir}\");\n\n        let bssl_link_kind = std::env::var(\"QUICHE_BSSL_LINK_KIND\")\n            .unwrap_or(\"static\".to_string());\n        println!(\"cargo:rustc-link-lib={bssl_link_kind}=ssl\");\n        println!(\"cargo:rustc-link-lib={bssl_link_kind}=crypto\");\n    }\n\n    if cfg!(feature = \"boringssl-boring-crate\") {\n        println!(\"cargo:rustc-link-lib=static=ssl\");\n        println!(\"cargo:rustc-link-lib=static=crypto\");\n    }\n\n    // MacOS: Allow cdylib to link with undefined symbols\n    let target_os = std::env::var(\"CARGO_CFG_TARGET_OS\").unwrap();\n    if target_os == \"macos\" {\n        println!(\"cargo:rustc-cdylib-link-arg=-Wl,-undefined,dynamic_lookup\");\n    }\n\n    #[cfg(feature = \"openssl\")]\n    {\n        let pkgcfg = pkg_config::Config::new();\n\n        if pkgcfg.probe(\"libcrypto\").is_err() {\n            panic!(\"no libcrypto found\");\n        }\n\n        if pkgcfg.probe(\"libssl\").is_err() {\n            panic!(\"no libssl found\");\n        }\n    }\n\n    if cfg!(feature = \"pkg-config-meta\") {\n        write_pkg_config();\n    }\n\n    #[cfg(feature = \"ffi\")]\n    if target_os != \"windows\" {\n        cdylib_link_lines::metabuild();\n    }\n}\n"
  },
  {
    "path": "quiche/src/cid.rs",
    "content": "// Copyright (C) 2022, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::Error;\nuse crate::Result;\nuse std::cmp;\n\nuse crate::frame;\n\nuse crate::packet::ConnectionId;\n\nuse std::collections::HashSet;\nuse std::collections::VecDeque;\n\nuse smallvec::SmallVec;\n\n/// Used to calculate the cap for the queue of retired connection IDs for which\n/// a RETIRED_CONNECTION_ID frame have not been sent, as a multiple of\n/// `active_conn_id_limit` (see RFC 9000, section 5.1.2).\nconst RETIRED_CONN_ID_LIMIT_MULTIPLIER: u64 = 3;\n\n#[derive(Default)]\nstruct BoundedConnectionIdSeqSet {\n    /// The inner set.\n    inner: HashSet<u64>,\n\n    /// The maximum number of elements that the set can have.\n    capacity: usize,\n}\n\nimpl BoundedConnectionIdSeqSet {\n    /// Creates a set bounded by `capacity`.\n    fn new(capacity: usize) -> Self {\n        Self {\n            inner: HashSet::new(),\n            capacity,\n        }\n    }\n\n    fn insert(&mut self, e: u64) -> Result<bool> {\n        if self.inner.len() >= self.capacity {\n            return Err(Error::IdLimit);\n        }\n\n        Ok(self.inner.insert(e))\n    }\n\n    fn remove(&mut self, e: &u64) -> bool {\n        self.inner.remove(e)\n    }\n\n    fn is_empty(&self) -> bool {\n        self.inner.is_empty()\n    }\n}\n\n/// A structure holding a `ConnectionId` and all its related metadata.\n#[derive(Debug, Default)]\npub struct ConnectionIdEntry {\n    /// The Connection ID.\n    pub cid: ConnectionId<'static>,\n\n    /// Its associated sequence number.\n    pub seq: u64,\n\n    /// Its associated reset token. Initial CIDs may not have any reset token.\n    pub reset_token: Option<u128>,\n\n    /// The path identifier using this CID, if any.\n    pub path_id: Option<usize>,\n}\n\n#[derive(Default)]\nstruct BoundedNonEmptyConnectionIdVecDeque {\n    /// The inner `VecDeque`.\n    inner: VecDeque<ConnectionIdEntry>,\n\n    /// The maximum number of elements that the `VecDeque` can have.\n    capacity: usize,\n}\n\nimpl BoundedNonEmptyConnectionIdVecDeque {\n    /// Creates a `VecDeque` bounded by `capacity` and inserts\n    /// `initial_entry` in it.\n    fn new(capacity: usize, initial_entry: ConnectionIdEntry) -> Self {\n        let mut inner = VecDeque::with_capacity(1);\n        inner.push_back(initial_entry);\n        Self { inner, capacity }\n    }\n\n    /// Updates the maximum capacity of the inner `VecDeque` to `new_capacity`.\n    /// Does nothing if `new_capacity` is lower or equal to the current\n    /// `capacity`.\n    fn resize(&mut self, new_capacity: usize) {\n        if new_capacity > self.capacity {\n            self.capacity = new_capacity;\n        }\n    }\n\n    /// Returns the oldest inserted entry still present in the `VecDeque`.\n    fn get_oldest(&self) -> &ConnectionIdEntry {\n        self.inner.front().expect(\"vecdeque is empty\")\n    }\n\n    /// Gets a immutable reference to the entry having the provided `seq`.\n    fn get(&self, seq: u64) -> Option<&ConnectionIdEntry> {\n        // We need to iterate over the whole map to find the key.\n        self.inner.iter().find(|e| e.seq == seq)\n    }\n\n    /// Gets a mutable reference to the entry having the provided `seq`.\n    fn get_mut(&mut self, seq: u64) -> Option<&mut ConnectionIdEntry> {\n        // We need to iterate over the whole map to find the key.\n        self.inner.iter_mut().find(|e| e.seq == seq)\n    }\n\n    /// Returns an iterator over the entries in the `VecDeque`.\n    fn iter(&self) -> impl Iterator<Item = &ConnectionIdEntry> {\n        self.inner.iter()\n    }\n\n    /// Returns the number of elements in the `VecDeque`.\n    fn len(&self) -> usize {\n        self.inner.len()\n    }\n\n    /// Inserts the provided entry in the `VecDeque`.\n    ///\n    /// This method ensures the unicity of the `seq` associated to an entry. If\n    /// an entry has the same `seq` than `e`, this method updates the entry in\n    /// the `VecDeque` and the number of stored elements remains unchanged.\n    ///\n    /// If inserting a new element would exceed the collection's capacity, this\n    /// method raises an [`IdLimit`].\n    ///\n    /// [`IdLimit`]: enum.Error.html#IdLimit\n    fn insert(&mut self, e: ConnectionIdEntry) -> Result<()> {\n        // Ensure we don't have duplicates.\n        match self.get_mut(e.seq) {\n            Some(oe) => *oe = e,\n            None => {\n                if self.inner.len() >= self.capacity {\n                    return Err(Error::IdLimit);\n                }\n                self.inner.push_back(e);\n            },\n        };\n        Ok(())\n    }\n\n    /// Removes all the elements in the collection and inserts the provided one.\n    fn clear_and_insert(&mut self, e: ConnectionIdEntry) {\n        self.inner.clear();\n        self.inner.push_back(e);\n    }\n\n    /// Removes the element in the collection having the provided `seq`.\n    ///\n    /// If this method is called when there remains a single element in the\n    /// collection, this method raises an [`OutOfIdentifiers`].\n    ///\n    /// Returns `Some` if the element was in the collection and removed, or\n    /// `None` if it was not and nothing was modified.\n    ///\n    /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers\n    fn remove(&mut self, seq: u64) -> Result<Option<ConnectionIdEntry>> {\n        if self.inner.len() <= 1 {\n            return Err(Error::OutOfIdentifiers);\n        }\n\n        Ok(self\n            .inner\n            .iter()\n            .position(|e| e.seq == seq)\n            .and_then(|index| self.inner.remove(index)))\n    }\n}\n\n#[derive(Default)]\npub struct ConnectionIdentifiers {\n    /// All the Destination Connection IDs provided by our peer.\n    dcids: BoundedNonEmptyConnectionIdVecDeque,\n\n    /// All the Source Connection IDs we provide to our peer.\n    scids: BoundedNonEmptyConnectionIdVecDeque,\n\n    /// Source Connection IDs that should be announced to the peer.\n    advertise_new_scid_seqs: VecDeque<u64>,\n\n    /// Retired Destination Connection IDs that should be announced to the peer.\n    retire_dcid_seqs: BoundedConnectionIdSeqSet,\n\n    /// Retired Source Connection IDs that should be notified to the\n    /// application.\n    retired_scids: VecDeque<ConnectionId<'static>>,\n\n    /// Largest \"Retire Prior To\" we received from the peer.\n    largest_peer_retire_prior_to: u64,\n\n    /// Largest sequence number we received from the peer.\n    largest_destination_seq: u64,\n\n    /// Next sequence number to use.\n    next_scid_seq: u64,\n\n    /// \"Retire Prior To\" value to advertise to the peer.\n    retire_prior_to: u64,\n\n    /// The maximum number of source Connection IDs our peer allows us.\n    source_conn_id_limit: usize,\n\n    /// Does the host use zero-length source Connection ID.\n    zero_length_scid: bool,\n\n    /// Does the host use zero-length destination Connection ID.\n    zero_length_dcid: bool,\n}\n\nimpl ConnectionIdentifiers {\n    /// Creates a new `ConnectionIdentifiers` with the specified destination\n    /// connection ID limit and initial source Connection ID. The destination\n    /// Connection ID is set to the empty one.\n    pub fn new(\n        mut destination_conn_id_limit: usize, initial_scid: &ConnectionId,\n        initial_path_id: usize, reset_token: Option<u128>,\n    ) -> ConnectionIdentifiers {\n        // It must be at least 2.\n        if destination_conn_id_limit < 2 {\n            destination_conn_id_limit = 2;\n        }\n\n        // Initially, the limit of active source connection IDs is 2.\n        let source_conn_id_limit = 2;\n\n        // Record the zero-length SCID status.\n        let zero_length_scid = initial_scid.is_empty();\n\n        let initial_scid =\n            ConnectionId::from_ref(initial_scid.as_ref()).into_owned();\n\n        // We need to track up to (2 * source_conn_id_limit - 1) source\n        // Connection IDs when the host wants to force their renewal.\n        let scids = BoundedNonEmptyConnectionIdVecDeque::new(\n            2 * source_conn_id_limit - 1,\n            ConnectionIdEntry {\n                cid: initial_scid,\n                seq: 0,\n                reset_token,\n                path_id: Some(initial_path_id),\n            },\n        );\n\n        let dcids = BoundedNonEmptyConnectionIdVecDeque::new(\n            destination_conn_id_limit,\n            ConnectionIdEntry {\n                cid: ConnectionId::default(),\n                seq: 0,\n                reset_token: None,\n                path_id: Some(initial_path_id),\n            },\n        );\n\n        // Guard against overflow.\n        let value =\n            (destination_conn_id_limit as u64) * RETIRED_CONN_ID_LIMIT_MULTIPLIER;\n        let size = cmp::min(usize::MAX as u64, value) as usize;\n        // Because we already inserted the initial SCID.\n        let next_scid_seq = 1;\n        ConnectionIdentifiers {\n            scids,\n            dcids,\n            retire_dcid_seqs: BoundedConnectionIdSeqSet::new(size),\n            next_scid_seq,\n            source_conn_id_limit,\n            zero_length_scid,\n            ..Default::default()\n        }\n    }\n\n    /// Sets the maximum number of source connection IDs our peer allows us.\n    pub fn set_source_conn_id_limit(&mut self, v: u64) {\n        // Bound conn id limit so our scids queue sizing is valid.\n        let v = cmp::min(v, (usize::MAX / 2) as u64) as usize;\n\n        // It must be at least 2.\n        if v >= 2 {\n            self.source_conn_id_limit = v;\n            // We need to track up to (2 * source_conn_id_limit - 1) source\n            // Connection IDs when the host wants to force their renewal.\n            self.scids.resize(2 * v - 1);\n        }\n    }\n\n    /// Gets the destination Connection ID associated with the provided sequence\n    /// number.\n    #[inline]\n    pub fn get_dcid(&self, seq_num: u64) -> Result<&ConnectionIdEntry> {\n        self.dcids.get(seq_num).ok_or(Error::InvalidState)\n    }\n\n    /// Gets the source Connection ID associated with the provided sequence\n    /// number.\n    #[inline]\n    pub fn get_scid(&self, seq_num: u64) -> Result<&ConnectionIdEntry> {\n        self.scids.get(seq_num).ok_or(Error::InvalidState)\n    }\n\n    /// Adds a new source identifier, and indicates whether it should be\n    /// advertised through a `NEW_CONNECTION_ID` frame or not.\n    ///\n    /// At any time, the peer cannot have more Destination Connection IDs than\n    /// the maximum number of active Connection IDs it negotiated. In such case\n    /// (i.e., when [`active_source_cids()`] - `peer_active_conn_id_limit` = 0,\n    /// if the caller agrees to request the removal of previous connection IDs,\n    /// it sets the `retire_if_needed` parameter. Otherwise, an [`IdLimit`] is\n    /// returned.\n    ///\n    /// Note that setting `retire_if_needed` does not prevent this function from\n    /// returning an [`IdLimit`] in the case the caller wants to retire still\n    /// unannounced Connection IDs.\n    ///\n    /// When setting the initial Source Connection ID, the `reset_token` may be\n    /// `None`. However, other Source CIDs must have an associated\n    /// `reset_token`. Providing `None` as the `reset_token` for non-initial\n    /// SCIDs raises an [`InvalidState`].\n    ///\n    /// In the case the provided `cid` is already present, it does not add it.\n    /// If the provided `reset_token` differs from the one already registered,\n    /// returns an `InvalidState`.\n    ///\n    /// Returns the sequence number associated to that new source identifier.\n    ///\n    /// [`active_source_cids()`]:  struct.ConnectionIdentifiers.html#method.active_source_cids\n    /// [`InvalidState`]: enum.Error.html#InvalidState\n    /// [`IdLimit`]: enum.Error.html#IdLimit\n    pub fn new_scid(\n        &mut self, cid: ConnectionId<'static>, reset_token: Option<u128>,\n        advertise: bool, path_id: Option<usize>, retire_if_needed: bool,\n    ) -> Result<u64> {\n        if self.zero_length_scid {\n            return Err(Error::InvalidState);\n        }\n\n        // Check whether the number of source Connection IDs does not exceed the\n        // limit. If the host agrees to retire old CIDs, it can store up to\n        // (2 * source_active_conn_id - 1) source CIDs. This limit is enforced\n        // when calling `self.scids.insert()`.\n        if self.scids.len() >= self.source_conn_id_limit {\n            if !retire_if_needed {\n                return Err(Error::IdLimit);\n            }\n\n            // We need to retire the lowest one.\n            self.retire_prior_to = self.lowest_usable_scid_seq()? + 1;\n        }\n\n        let seq = self.next_scid_seq;\n\n        if reset_token.is_none() && seq != 0 {\n            return Err(Error::InvalidState);\n        }\n\n        // Check first that the SCID has not been inserted before.\n        if let Some(e) = self.scids.iter().find(|e| e.cid == cid) {\n            if e.reset_token != reset_token {\n                return Err(Error::InvalidState);\n            }\n            return Ok(e.seq);\n        }\n\n        self.scids.insert(ConnectionIdEntry {\n            cid,\n            seq,\n            reset_token,\n            path_id,\n        })?;\n        self.next_scid_seq += 1;\n\n        self.mark_advertise_new_scid_seq(seq, advertise);\n\n        Ok(seq)\n    }\n\n    /// Sets the initial destination identifier.\n    pub fn set_initial_dcid(\n        &mut self, cid: ConnectionId<'static>, reset_token: Option<u128>,\n        path_id: Option<usize>,\n    ) {\n        // Record the zero-length DCID status.\n        self.zero_length_dcid = cid.is_empty();\n        self.dcids.clear_and_insert(ConnectionIdEntry {\n            cid,\n            seq: 0,\n            reset_token,\n            path_id,\n        });\n    }\n\n    /// Adds a new Destination Connection ID (originating from a\n    /// NEW_CONNECTION_ID frame) and process all its related metadata.\n    ///\n    /// Returns an error if the provided Connection ID or its metadata are\n    /// invalid.\n    ///\n    /// Returns a list of tuples (DCID sequence number, Path ID), containing the\n    /// sequence number of retired DCIDs that were linked to their respective\n    /// Path ID.\n    pub fn new_dcid(\n        &mut self, cid: ConnectionId<'static>, seq: u64, reset_token: u128,\n        retire_prior_to: u64, retired_path_ids: &mut SmallVec<[(u64, usize); 1]>,\n    ) -> Result<()> {\n        if self.zero_length_dcid {\n            return Err(Error::InvalidState);\n        }\n\n        // If an endpoint receives a NEW_CONNECTION_ID frame that repeats a\n        // previously issued connection ID with a different Stateless Reset\n        // Token field value or a different Sequence Number field value, or if a\n        // sequence number is used for different connection IDs, the endpoint\n        // MAY treat that receipt as a connection error of type\n        // PROTOCOL_VIOLATION.\n        if let Some(e) = self.dcids.iter().find(|e| e.cid == cid || e.seq == seq)\n        {\n            if e.cid != cid || e.seq != seq || e.reset_token != Some(reset_token)\n            {\n                return Err(Error::InvalidFrame);\n            }\n            // The identifier is already there, nothing to do.\n            return Ok(());\n        }\n\n        // The value in the Retire Prior To field MUST be less than or equal to\n        // the value in the Sequence Number field. Receiving a value in the\n        // Retire Prior To field that is greater than that in the Sequence\n        // Number field MUST be treated as a connection error of type\n        // FRAME_ENCODING_ERROR.\n        if retire_prior_to > seq {\n            return Err(Error::InvalidFrame);\n        }\n\n        // An endpoint that receives a NEW_CONNECTION_ID frame with a sequence\n        // number smaller than the Retire Prior To field of a previously\n        // received NEW_CONNECTION_ID frame MUST send a corresponding\n        // RETIRE_CONNECTION_ID frame that retires the newly received connection\n        // ID, unless it has already done so for that sequence number.\n        if seq < self.largest_peer_retire_prior_to {\n            self.mark_retire_dcid_seq(seq, true)?;\n            return Ok(());\n        }\n\n        if seq > self.largest_destination_seq {\n            self.largest_destination_seq = seq;\n        }\n\n        let new_entry = ConnectionIdEntry {\n            cid: cid.clone(),\n            seq,\n            reset_token: Some(reset_token),\n            path_id: None,\n        };\n\n        let mut retired_dcid_queue_err = None;\n\n        // A receiver MUST ignore any Retire Prior To fields that do not\n        // increase the largest received Retire Prior To value.\n        //\n        // After processing a NEW_CONNECTION_ID frame and adding and retiring\n        // active connection IDs, if the number of active connection IDs exceeds\n        // the value advertised in its active_connection_id_limit transport\n        // parameter, an endpoint MUST close the connection with an error of type\n        // CONNECTION_ID_LIMIT_ERROR.\n        if retire_prior_to > self.largest_peer_retire_prior_to {\n            let retired = &mut self.retire_dcid_seqs;\n\n            // The insert entry MUST have a sequence higher or equal to the ones\n            // being retired.\n            if new_entry.seq < retire_prior_to {\n                return Err(Error::OutOfIdentifiers);\n            }\n\n            // To avoid exceeding the capacity of the inner `VecDeque`, we first\n            // remove the elements and then insert the new one.\n            let index = self\n                .dcids\n                .inner\n                .partition_point(|e| e.seq < retire_prior_to);\n\n            for e in self.dcids.inner.drain(..index) {\n                if let Some(pid) = e.path_id {\n                    retired_path_ids.push((e.seq, pid));\n                }\n\n                if let Err(e) = retired.insert(e.seq) {\n                    // Delay propagating the error as we need to try to insert\n                    // the new DCID first.\n                    retired_dcid_queue_err = Some(e);\n                    break;\n                }\n            }\n\n            self.largest_peer_retire_prior_to = retire_prior_to;\n        }\n\n        // Note that if no element has been retired and the `VecDeque` reaches\n        // its capacity limit, this will raise an `IdLimit`.\n        self.dcids.insert(new_entry)?;\n\n        // Propagate the error triggered when inserting a retired DCID seq to\n        // the queue.\n        if let Some(e) = retired_dcid_queue_err {\n            return Err(e);\n        }\n\n        Ok(())\n    }\n\n    /// Retires the Source Connection ID having the provided sequence number.\n    ///\n    /// In case the retired Connection ID is the same as the one used by the\n    /// packet requesting the retiring, or if the retired sequence number is\n    /// greater than any previously advertised sequence numbers, it returns an\n    /// [`InvalidState`].\n    ///\n    /// Returns the path ID that was associated to the retired CID, if any.\n    ///\n    /// [`InvalidState`]: enum.Error.html#InvalidState\n    pub fn retire_scid(\n        &mut self, seq: u64, pkt_dcid: &ConnectionId,\n    ) -> Result<Option<usize>> {\n        if seq >= self.next_scid_seq {\n            return Err(Error::InvalidState);\n        }\n\n        let pid = if let Some(e) = self.scids.remove(seq)? {\n            if e.cid == *pkt_dcid {\n                return Err(Error::InvalidState);\n            }\n\n            // Notifies the application.\n            self.retired_scids.push_back(e.cid);\n\n            // Retiring this SCID may increase the retire prior to.\n            let lowest_scid_seq = self.lowest_usable_scid_seq()?;\n            self.retire_prior_to = lowest_scid_seq;\n\n            e.path_id\n        } else {\n            None\n        };\n\n        Ok(pid)\n    }\n\n    /// Retires the Destination Connection ID having the provided sequence\n    /// number.\n    ///\n    /// If the caller tries to retire the last destination Connection ID, this\n    /// method triggers an [`OutOfIdentifiers`].\n    ///\n    /// If the caller tries to retire a non-existing Destination Connection\n    /// ID sequence number, this method returns an [`InvalidState`].\n    ///\n    /// Returns the path ID that was associated to the retired CID, if any.\n    ///\n    /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers\n    /// [`InvalidState`]: enum.Error.html#InvalidState\n    pub fn retire_dcid(&mut self, seq: u64) -> Result<Option<usize>> {\n        if self.zero_length_dcid {\n            return Err(Error::InvalidState);\n        }\n\n        let e = self.dcids.remove(seq)?.ok_or(Error::InvalidState)?;\n\n        self.mark_retire_dcid_seq(seq, true)?;\n\n        Ok(e.path_id)\n    }\n\n    /// Returns an iterator over the source connection IDs.\n    pub fn scids_iter(&self) -> impl Iterator<Item = &ConnectionId<'_>> {\n        self.scids.iter().map(|e| &e.cid)\n    }\n\n    /// Updates the Source Connection ID entry with the provided sequence number\n    /// to indicate that it is now linked to the provided path ID.\n    pub fn link_scid_to_path_id(\n        &mut self, dcid_seq: u64, path_id: usize,\n    ) -> Result<()> {\n        let e = self.scids.get_mut(dcid_seq).ok_or(Error::InvalidState)?;\n        e.path_id = Some(path_id);\n        Ok(())\n    }\n\n    /// Updates the Destination Connection ID entry with the provided sequence\n    /// number to indicate that it is now linked to the provided path ID.\n    pub fn link_dcid_to_path_id(\n        &mut self, dcid_seq: u64, path_id: usize,\n    ) -> Result<()> {\n        let e = self.dcids.get_mut(dcid_seq).ok_or(Error::InvalidState)?;\n        e.path_id = Some(path_id);\n        Ok(())\n    }\n\n    /// Gets the minimum Source Connection ID sequence number whose removal has\n    /// not been requested yet.\n    #[inline]\n    pub fn lowest_usable_scid_seq(&self) -> Result<u64> {\n        self.scids\n            .iter()\n            .filter_map(|e| {\n                if e.seq >= self.retire_prior_to {\n                    Some(e.seq)\n                } else {\n                    None\n                }\n            })\n            .min()\n            .ok_or(Error::InvalidState)\n    }\n\n    /// Gets the lowest Destination Connection ID sequence number that is not\n    /// associated to a path.\n    #[inline]\n    pub fn lowest_available_dcid_seq(&self) -> Option<u64> {\n        self.dcids\n            .iter()\n            .filter_map(|e| {\n                if e.path_id.is_none() {\n                    Some(e.seq)\n                } else {\n                    None\n                }\n            })\n            .min()\n    }\n\n    /// Finds the sequence number of the Source Connection ID having the\n    /// provided value and the identifier of the path using it, if any.\n    #[inline]\n    pub fn find_scid_seq(\n        &self, scid: &ConnectionId,\n    ) -> Option<(u64, Option<usize>)> {\n        self.scids.iter().find_map(|e| {\n            if e.cid == *scid {\n                Some((e.seq, e.path_id))\n            } else {\n                None\n            }\n        })\n    }\n\n    /// Returns the number of Source Connection IDs that have not been\n    /// assigned to a path yet.\n    ///\n    /// Note that this function is only meaningful if the host uses non-zero\n    /// length Source Connection IDs.\n    #[inline]\n    pub fn available_scids(&self) -> usize {\n        self.scids.iter().filter(|e| e.path_id.is_none()).count()\n    }\n\n    /// Returns the number of Destination Connection IDs that have not been\n    /// assigned to a path yet.\n    ///\n    /// Note that this function returns 0 if the host uses zero length\n    /// Destination Connection IDs.\n    #[inline]\n    pub fn available_dcids(&self) -> usize {\n        if self.zero_length_dcid() {\n            return 0;\n        }\n        self.dcids.iter().filter(|e| e.path_id.is_none()).count()\n    }\n\n    /// Returns the oldest active source Connection ID of this connection.\n    #[inline]\n    pub fn oldest_scid(&self) -> &ConnectionIdEntry {\n        self.scids.get_oldest()\n    }\n\n    /// Returns the oldest known active destination Connection ID of this\n    /// connection.\n    ///\n    /// Note that due to e.g., reordering at reception side, the oldest known\n    /// active destination Connection ID is not necessarily the one having the\n    /// lowest sequence.\n    #[inline]\n    pub fn oldest_dcid(&self) -> &ConnectionIdEntry {\n        self.dcids.get_oldest()\n    }\n\n    /// Adds or remove the source Connection ID sequence number from the\n    /// source Connection ID set that need to be advertised to the peer through\n    /// NEW_CONNECTION_ID frames.\n    #[inline]\n    pub fn mark_advertise_new_scid_seq(\n        &mut self, scid_seq: u64, advertise: bool,\n    ) {\n        if advertise {\n            self.advertise_new_scid_seqs.push_back(scid_seq);\n        } else if let Some(index) = self\n            .advertise_new_scid_seqs\n            .iter()\n            .position(|s| *s == scid_seq)\n        {\n            self.advertise_new_scid_seqs.remove(index);\n        }\n    }\n\n    /// Adds or remove the destination Connection ID sequence number from the\n    /// retired destination Connection ID set that need to be advertised to the\n    /// peer through RETIRE_CONNECTION_ID frames.\n    #[inline]\n    pub fn mark_retire_dcid_seq(\n        &mut self, dcid_seq: u64, retire: bool,\n    ) -> Result<()> {\n        if retire {\n            self.retire_dcid_seqs.insert(dcid_seq)?;\n        } else {\n            self.retire_dcid_seqs.remove(&dcid_seq);\n        }\n\n        Ok(())\n    }\n\n    /// Gets a source Connection ID's sequence number requiring advertising it\n    /// to the peer through NEW_CONNECTION_ID frame, if any.\n    ///\n    /// If `Some`, it always returns the same value until it has been removed\n    /// using `mark_advertise_new_scid_seq`.\n    #[inline]\n    pub fn next_advertise_new_scid_seq(&self) -> Option<u64> {\n        self.advertise_new_scid_seqs.front().copied()\n    }\n\n    /// Returns a copy of the set of destination Connection IDs's sequence\n    /// numbers to send RETIRE_CONNECTION_ID frames.\n    ///\n    /// Note that the set includes sequence numbers at the time the copy was\n    /// created. To account for newly inserted or removed sequence numbers, a\n    /// new copy needs to be created.\n    #[inline]\n    pub fn retire_dcid_seqs(&self) -> HashSet<u64> {\n        self.retire_dcid_seqs.inner.clone()\n    }\n\n    /// Returns true if there are new source Connection IDs to advertise.\n    #[inline]\n    pub fn has_new_scids(&self) -> bool {\n        !self.advertise_new_scid_seqs.is_empty()\n    }\n\n    /// Returns true if there are retired destination Connection IDs to\\\n    /// advertise.\n    #[inline]\n    pub fn has_retire_dcids(&self) -> bool {\n        !self.retire_dcid_seqs.is_empty()\n    }\n\n    /// Returns whether zero-length source CIDs are used.\n    #[inline]\n    pub fn zero_length_scid(&self) -> bool {\n        self.zero_length_scid\n    }\n\n    /// Returns whether zero-length destination CIDs are used.\n    #[inline]\n    pub fn zero_length_dcid(&self) -> bool {\n        self.zero_length_dcid\n    }\n\n    /// Gets the NEW_CONNECTION_ID frame related to the source connection ID\n    /// with sequence `seq_num`.\n    pub fn get_new_connection_id_frame_for(\n        &self, seq_num: u64,\n    ) -> Result<frame::Frame> {\n        let e = self.scids.get(seq_num).ok_or(Error::InvalidState)?;\n        Ok(frame::Frame::NewConnectionId {\n            seq_num,\n            retire_prior_to: self.retire_prior_to,\n            conn_id: e.cid.to_vec(),\n            reset_token: e.reset_token.ok_or(Error::InvalidState)?.to_be_bytes(),\n        })\n    }\n\n    /// Returns the number of source Connection IDs that are active. This is\n    /// only meaningful if the host uses non-zero length Source Connection IDs.\n    #[inline]\n    pub fn active_source_cids(&self) -> usize {\n        self.scids.len()\n    }\n\n    /// Returns the number of source Connection IDs that are retired. This is\n    /// only meaningful if the host uses non-zero length Source Connection IDs.\n    #[inline]\n    pub fn retired_source_cids(&self) -> usize {\n        self.retired_scids.len()\n    }\n\n    pub fn pop_retired_scid(&mut self) -> Option<ConnectionId<'static>> {\n        self.retired_scids.pop_front()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::test_utils::create_cid_and_reset_token;\n\n    #[test]\n    fn ids_new_scids() {\n        let (scid, _) = create_cid_and_reset_token(16);\n        let (dcid, _) = create_cid_and_reset_token(16);\n\n        let mut ids = ConnectionIdentifiers::new(2, &scid, 0, None);\n        ids.set_source_conn_id_limit(3);\n        ids.set_initial_dcid(dcid, None, Some(0));\n\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.available_scids(), 0);\n        assert!(!ids.has_new_scids());\n        assert_eq!(ids.next_advertise_new_scid_seq(), None);\n\n        let (scid2, rt2) = create_cid_and_reset_token(16);\n\n        assert_eq!(ids.new_scid(scid2, Some(rt2), true, None, false), Ok(1));\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.available_scids(), 1);\n        assert!(ids.has_new_scids());\n        assert_eq!(ids.next_advertise_new_scid_seq(), Some(1));\n\n        let (scid3, rt3) = create_cid_and_reset_token(16);\n\n        assert_eq!(ids.new_scid(scid3, Some(rt3), true, None, false), Ok(2));\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.available_scids(), 2);\n        assert!(ids.has_new_scids());\n        assert_eq!(ids.next_advertise_new_scid_seq(), Some(1));\n\n        // If now we give another CID, it reports an error since it exceeds the\n        // limit of active CIDs.\n        let (scid4, rt4) = create_cid_and_reset_token(16);\n\n        assert_eq!(\n            ids.new_scid(scid4, Some(rt4), true, None, false),\n            Err(Error::IdLimit),\n        );\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.available_scids(), 2);\n        assert!(ids.has_new_scids());\n        assert_eq!(ids.next_advertise_new_scid_seq(), Some(1));\n\n        // Assume we sent one of them.\n        ids.mark_advertise_new_scid_seq(1, false);\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.available_scids(), 2);\n        assert!(ids.has_new_scids());\n        assert_eq!(ids.next_advertise_new_scid_seq(), Some(2));\n\n        // Send the other.\n        ids.mark_advertise_new_scid_seq(2, false);\n\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.available_scids(), 2);\n        assert!(!ids.has_new_scids());\n        assert_eq!(ids.next_advertise_new_scid_seq(), None);\n    }\n\n    #[test]\n    fn new_dcid_event() {\n        let (scid, _) = create_cid_and_reset_token(16);\n        let (dcid, _) = create_cid_and_reset_token(16);\n\n        let mut retired_path_ids = SmallVec::new();\n\n        let mut ids = ConnectionIdentifiers::new(2, &scid, 0, None);\n        ids.set_initial_dcid(dcid, None, Some(0));\n\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.dcids.len(), 1);\n\n        let (dcid2, rt2) = create_cid_and_reset_token(16);\n\n        assert_eq!(\n            ids.new_dcid(dcid2, 1, rt2, 0, &mut retired_path_ids),\n            Ok(()),\n        );\n        assert_eq!(retired_path_ids, SmallVec::from_buf([]));\n        assert_eq!(ids.available_dcids(), 1);\n        assert_eq!(ids.dcids.len(), 2);\n\n        // Now we assume that the client wants to advertise more source\n        // Connection IDs than the advertised limit. This is valid if it\n        // requests its peer to retire enough Connection IDs to fit within the\n        // limits.\n        let (dcid3, rt3) = create_cid_and_reset_token(16);\n        assert_eq!(\n            ids.new_dcid(dcid3, 2, rt3, 1, &mut retired_path_ids),\n            Ok(())\n        );\n        assert_eq!(retired_path_ids, SmallVec::from_buf([(0, 0)]));\n        // The CID module does not handle path replacing. Fake it now.\n        ids.link_dcid_to_path_id(1, 0).unwrap();\n        assert_eq!(ids.available_dcids(), 1);\n        assert_eq!(ids.dcids.len(), 2);\n        assert!(ids.has_retire_dcids());\n        assert_eq!(ids.retire_dcid_seqs().iter().next(), Some(&0));\n\n        // Fake RETIRE_CONNECTION_ID sending.\n        let _ = ids.mark_retire_dcid_seq(0, false);\n        assert!(!ids.has_retire_dcids());\n        assert_eq!(ids.retire_dcid_seqs().iter().next(), None);\n\n        // Now tries to experience CID retirement. If the server tries to remove\n        // non-existing DCIDs, it fails.\n        assert_eq!(ids.retire_dcid(0), Err(Error::InvalidState));\n        assert_eq!(ids.retire_dcid(3), Err(Error::InvalidState));\n        assert!(!ids.has_retire_dcids());\n        assert_eq!(ids.dcids.len(), 2);\n\n        // Now it removes DCID with sequence 1.\n        assert_eq!(ids.retire_dcid(1), Ok(Some(0)));\n        // The CID module does not handle path replacing. Fake it now.\n        ids.link_dcid_to_path_id(2, 0).unwrap();\n        assert_eq!(ids.available_dcids(), 0);\n        assert!(ids.has_retire_dcids());\n        assert_eq!(ids.retire_dcid_seqs().iter().next(), Some(&1));\n        assert_eq!(ids.dcids.len(), 1);\n\n        // Fake RETIRE_CONNECTION_ID sending.\n        let _ = ids.mark_retire_dcid_seq(1, false);\n        assert!(!ids.has_retire_dcids());\n        assert_eq!(ids.retire_dcid_seqs().iter().next(), None);\n\n        // Trying to remove the last DCID triggers an error.\n        assert_eq!(ids.retire_dcid(2), Err(Error::OutOfIdentifiers));\n        assert_eq!(ids.available_dcids(), 0);\n        assert!(!ids.has_retire_dcids());\n        assert_eq!(ids.dcids.len(), 1);\n    }\n\n    #[test]\n    fn new_dcid_reordered() {\n        let (scid, _) = create_cid_and_reset_token(16);\n        let (dcid, _) = create_cid_and_reset_token(16);\n\n        let mut retired_path_ids = SmallVec::new();\n\n        let mut ids = ConnectionIdentifiers::new(2, &scid, 0, None);\n        ids.set_initial_dcid(dcid, None, Some(0));\n\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.dcids.len(), 1);\n\n        // Skip DCID #1 (e.g due to packet loss) and insert DCID #2.\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 2, rt, 1, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 1);\n\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 3, rt, 2, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 2);\n\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 4, rt, 3, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 2);\n\n        // Insert DCID #1 (e.g due to packet reordering).\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 1, rt, 0, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 2);\n\n        // Try inserting DCID #1 again (e.g. due to retransmission).\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 1, rt, 0, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 2);\n    }\n\n    #[test]\n    fn new_dcid_partial_retire_prior_to() {\n        let (scid, _) = create_cid_and_reset_token(16);\n        let (dcid, _) = create_cid_and_reset_token(16);\n\n        let mut retired_path_ids = SmallVec::new();\n\n        let mut ids = ConnectionIdentifiers::new(5, &scid, 0, None);\n        ids.set_initial_dcid(dcid, None, Some(0));\n\n        assert_eq!(ids.available_dcids(), 0);\n        assert_eq!(ids.dcids.len(), 1);\n\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 1, rt, 0, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 2);\n\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 2, rt, 0, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 3);\n\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 3, rt, 0, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 4);\n\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 4, rt, 0, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 5);\n\n        // Retire a DCID from the middle of the list\n        assert!(ids.retire_dcid(3).is_ok());\n\n        // Retire prior to DCID that was just retired.\n        //\n        // This is largely to test that the `partition_point()` call above\n        // returns a meaningful value even if the actual sequence that is\n        // searched isn't present in the list.\n        let (dcid, rt) = create_cid_and_reset_token(16);\n        assert!(ids.new_dcid(dcid, 5, rt, 3, &mut retired_path_ids).is_ok());\n        assert_eq!(ids.dcids.len(), 2);\n    }\n\n    #[test]\n    fn retire_scids() {\n        let (scid, _) = create_cid_and_reset_token(16);\n        let (dcid, _) = create_cid_and_reset_token(16);\n\n        let mut ids = ConnectionIdentifiers::new(3, &scid, 0, None);\n        ids.set_initial_dcid(dcid, None, Some(0));\n        ids.set_source_conn_id_limit(3);\n\n        let (scid2, rt2) = create_cid_and_reset_token(16);\n        let (scid3, rt3) = create_cid_and_reset_token(16);\n\n        assert_eq!(\n            ids.new_scid(scid2.clone(), Some(rt2), true, None, false),\n            Ok(1),\n        );\n        assert_eq!(ids.scids.len(), 2);\n        assert_eq!(\n            ids.new_scid(scid3.clone(), Some(rt3), true, None, false),\n            Ok(2),\n        );\n        assert_eq!(ids.scids.len(), 3);\n\n        assert_eq!(ids.pop_retired_scid(), None);\n\n        assert_eq!(ids.retire_scid(0, &scid2), Ok(Some(0)));\n\n        assert_eq!(ids.pop_retired_scid(), Some(scid));\n        assert_eq!(ids.pop_retired_scid(), None);\n\n        assert_eq!(ids.retire_scid(1, &scid3), Ok(None));\n\n        assert_eq!(ids.pop_retired_scid(), Some(scid2));\n        assert_eq!(ids.pop_retired_scid(), None);\n    }\n}\n"
  },
  {
    "path": "quiche/src/crypto/boringssl.rs",
    "content": "use super::*;\n\nuse std::convert::TryFrom;\n\nuse std::mem::MaybeUninit;\n\nuse libc::c_int;\nuse libc::c_uint;\nuse libc::c_void;\n\n// NOTE: This structure is copied from <openssl/aead.h> in order to be able to\n// statically allocate it. While it is not often modified upstream, it needs to\n// be kept in sync.\n#[repr(C)]\nstruct EVP_AEAD_CTX {\n    aead: libc::uintptr_t,\n    opaque: [u8; 580],\n    alignment: u64,\n    tag_len: u8,\n}\n\n#[derive(Clone)]\n#[repr(C)]\npub(crate) struct AES_KEY {\n    rd_key: [u32; 4 * (14 + 1)],\n    rounds: c_int,\n}\n\nimpl Algorithm {\n    fn get_evp_aead(self) -> *const EVP_AEAD {\n        match self {\n            Algorithm::AES128_GCM => unsafe { EVP_aead_aes_128_gcm_tls13() },\n            Algorithm::AES256_GCM => unsafe { EVP_aead_aes_256_gcm_tls13() },\n            Algorithm::ChaCha20_Poly1305 => unsafe {\n                EVP_aead_chacha20_poly1305()\n            },\n        }\n    }\n}\n\npub(crate) struct PacketKey {\n    alg: Algorithm,\n\n    ctx: EVP_AEAD_CTX,\n\n    nonce: Vec<u8>,\n}\n\nimpl PacketKey {\n    pub fn new(\n        alg: Algorithm, key: Vec<u8>, iv: Vec<u8>, _enc: u32,\n    ) -> Result<Self> {\n        Ok(Self {\n            alg,\n            ctx: make_aead_ctx(alg, &key)?,\n            nonce: iv,\n        })\n    }\n\n    pub fn from_secret(aead: Algorithm, secret: &[u8], enc: u32) -> Result<Self> {\n        let key_len = aead.key_len();\n        let nonce_len = aead.nonce_len();\n\n        let mut key = vec![0; key_len];\n        let mut iv = vec![0; nonce_len];\n\n        derive_pkt_key(aead, secret, &mut key)?;\n        derive_pkt_iv(aead, secret, &mut iv)?;\n\n        let mut pkt_key = Self::new(aead, key, iv, enc)?;\n\n        // Dummy seal operation to prime the AEAD context with the nonce mask.\n        //\n        // This is needed because BoringCrypto requires the first counter (i.e.\n        // packet number) to be zero, which would not be the case for packet\n        // number spaces after Initial as the same packet number sequence is\n        // shared.\n        let _ = pkt_key.seal_with_u64_counter(0, b\"\", &mut [0_u8; 16], 0, None);\n\n        Ok(pkt_key)\n    }\n\n    pub fn open_with_u64_counter(\n        &self, counter: u64, ad: &[u8], buf: &mut [u8],\n    ) -> Result<usize> {\n        let tag_len = self.alg.tag_len();\n\n        let mut out_len = match buf.len().checked_sub(tag_len) {\n            Some(n) => n,\n            None => return Err(Error::CryptoFail),\n        };\n\n        let max_out_len = out_len;\n\n        let nonce = make_nonce(&self.nonce, counter);\n\n        let rc = unsafe {\n            EVP_AEAD_CTX_open(\n                &self.ctx,          // ctx\n                buf.as_mut_ptr(),   // out\n                &mut out_len,       // out_len\n                max_out_len,        // max_out_len\n                nonce[..].as_ptr(), // nonce\n                nonce.len(),        // nonce_len\n                buf.as_ptr(),       // inp\n                buf.len(),          // in_len\n                ad.as_ptr(),        // ad\n                ad.len(),           // ad_len\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        Ok(out_len)\n    }\n\n    pub fn seal_with_u64_counter(\n        &mut self, counter: u64, ad: &[u8], buf: &mut [u8], in_len: usize,\n        extra_in: Option<&[u8]>,\n    ) -> Result<usize> {\n        let tag_len = self.alg.tag_len();\n\n        let mut out_tag_len = tag_len;\n\n        let (extra_in_ptr, extra_in_len) = match extra_in {\n            Some(v) => (v.as_ptr(), v.len()),\n\n            None => (std::ptr::null(), 0),\n        };\n\n        // Make sure all the outputs combined fit in the buffer.\n        if in_len + tag_len + extra_in_len > buf.len() {\n            return Err(Error::CryptoFail);\n        }\n\n        let nonce = make_nonce(&self.nonce, counter);\n\n        let rc = unsafe {\n            EVP_AEAD_CTX_seal_scatter(\n                &mut self.ctx,              // ctx\n                buf.as_mut_ptr(),           // out\n                buf[in_len..].as_mut_ptr(), // out_tag\n                &mut out_tag_len,           // out_tag_len\n                tag_len + extra_in_len,     // max_out_tag_len\n                nonce[..].as_ptr(),         // nonce\n                nonce.len(),                // nonce_len\n                buf.as_ptr(),               // inp\n                in_len,                     // in_len\n                extra_in_ptr,               // extra_in\n                extra_in_len,               // extra_in_len\n                ad.as_ptr(),                // ad\n                ad.len(),                   // ad_len\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        Ok(in_len + out_tag_len)\n    }\n}\n\n#[derive(Clone)]\n#[allow(clippy::large_enum_variant)]\npub(crate) enum HeaderProtectionKey {\n    Aes(AES_KEY),\n\n    ChaCha(Vec<u8>),\n}\n\nimpl HeaderProtectionKey {\n    pub fn new(alg: Algorithm, hp_key: Vec<u8>) -> Result<Self> {\n        match alg {\n            Algorithm::AES128_GCM | Algorithm::AES256_GCM => unsafe {\n                let key_len_bits = alg.key_len() as u32 * 8;\n\n                let mut aes_key = MaybeUninit::<AES_KEY>::uninit();\n\n                let rc = AES_set_encrypt_key(\n                    hp_key.as_ptr(),\n                    key_len_bits,\n                    aes_key.as_mut_ptr(),\n                );\n\n                if rc != 0 {\n                    return Err(Error::CryptoFail);\n                }\n\n                let aes_key = aes_key.assume_init();\n                Ok(Self::Aes(aes_key))\n            },\n\n            Algorithm::ChaCha20_Poly1305 => Ok(Self::ChaCha(hp_key)),\n        }\n    }\n\n    pub fn new_mask(&self, sample: &[u8]) -> Result<HeaderProtectionMask> {\n        match self {\n            Self::Aes(aes_key) => {\n                let mut block = [0_u8; 16];\n\n                unsafe {\n                    AES_ecb_encrypt(\n                        sample.as_ptr(),\n                        block.as_mut_ptr(),\n                        aes_key as _,\n                        1,\n                    )\n                };\n\n                // Downsize the encrypted block to the size of the header\n                // protection mask.\n                //\n                // The length of the slice will always match the size of\n                // `HeaderProtectionMask` so the `unwrap()` is safe.\n                let new_mask =\n                    HeaderProtectionMask::try_from(&block[..HP_MASK_LEN])\n                        .unwrap();\n                Ok(new_mask)\n            },\n\n            Self::ChaCha(key) => {\n                const PLAINTEXT: &[u8; HP_MASK_LEN] = &[0_u8; HP_MASK_LEN];\n\n                let mut new_mask = HeaderProtectionMask::default();\n\n                let counter = u32::from_le_bytes([\n                    sample[0], sample[1], sample[2], sample[3],\n                ]);\n\n                unsafe {\n                    CRYPTO_chacha_20(\n                        new_mask.as_mut_ptr(),\n                        PLAINTEXT.as_ptr(),\n                        PLAINTEXT.len(),\n                        key.as_ptr(),\n                        sample[size_of::<u32>()..].as_ptr(),\n                        counter,\n                    );\n                };\n\n                Ok(new_mask)\n            },\n        }\n    }\n}\n\nfn make_aead_ctx(alg: Algorithm, key: &[u8]) -> Result<EVP_AEAD_CTX> {\n    let mut ctx = MaybeUninit::uninit();\n\n    let ctx = unsafe {\n        let aead = alg.get_evp_aead();\n\n        let rc = EVP_AEAD_CTX_init(\n            ctx.as_mut_ptr(),\n            aead,\n            key.as_ptr(),\n            alg.key_len(),\n            alg.tag_len(),\n            std::ptr::null_mut(),\n        );\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        ctx.assume_init()\n    };\n\n    Ok(ctx)\n}\n\npub(crate) fn hkdf_extract(\n    alg: Algorithm, out: &mut [u8], secret: &[u8], salt: &[u8],\n) -> Result<()> {\n    let mut out_len = out.len();\n\n    let rc = unsafe {\n        HKDF_extract(\n            out.as_mut_ptr(),\n            &mut out_len,\n            alg.get_evp_digest(),\n            secret.as_ptr(),\n            secret.len(),\n            salt.as_ptr(),\n            salt.len(),\n        )\n    };\n\n    if rc != 1 {\n        return Err(Error::CryptoFail);\n    }\n\n    Ok(())\n}\n\npub(crate) fn hkdf_expand(\n    alg: Algorithm, out: &mut [u8], secret: &[u8], info: &[u8],\n) -> Result<()> {\n    let rc = unsafe {\n        HKDF_expand(\n            out.as_mut_ptr(),\n            out.len(),\n            alg.get_evp_digest(),\n            secret.as_ptr(),\n            secret.len(),\n            info.as_ptr(),\n            info.len(),\n        )\n    };\n\n    if rc != 1 {\n        return Err(Error::CryptoFail);\n    }\n\n    Ok(())\n}\n\nextern \"C\" {\n    fn EVP_aead_aes_128_gcm_tls13() -> *const EVP_AEAD;\n\n    fn EVP_aead_aes_256_gcm_tls13() -> *const EVP_AEAD;\n\n    fn EVP_aead_chacha20_poly1305() -> *const EVP_AEAD;\n\n    // HKDF\n    fn HKDF_extract(\n        out_key: *mut u8, out_len: *mut usize, digest: *const EVP_MD,\n        secret: *const u8, secret_len: usize, salt: *const u8, salt_len: usize,\n    ) -> c_int;\n\n    fn HKDF_expand(\n        out_key: *mut u8, out_len: usize, digest: *const EVP_MD, prk: *const u8,\n        prk_len: usize, info: *const u8, info_len: usize,\n    ) -> c_int;\n\n    // EVP_AEAD_CTX\n    fn EVP_AEAD_CTX_init(\n        ctx: *mut EVP_AEAD_CTX, aead: *const EVP_AEAD, key: *const u8,\n        key_len: usize, tag_len: usize, engine: *mut c_void,\n    ) -> c_int;\n\n    fn EVP_AEAD_CTX_open(\n        ctx: *const EVP_AEAD_CTX, out: *mut u8, out_len: *mut usize,\n        max_out_len: usize, nonce: *const u8, nonce_len: usize, inp: *const u8,\n        in_len: usize, ad: *const u8, ad_len: usize,\n    ) -> c_int;\n\n    fn EVP_AEAD_CTX_seal_scatter(\n        ctx: *mut EVP_AEAD_CTX, out: *mut u8, out_tag: *mut u8,\n        out_tag_len: *mut usize, max_out_tag_len: usize, nonce: *const u8,\n        nonce_len: usize, inp: *const u8, in_len: usize, extra_in: *const u8,\n        extra_in_len: usize, ad: *const u8, ad_len: usize,\n    ) -> c_int;\n\n    // AES\n    fn AES_set_encrypt_key(\n        key: *const u8, bits: c_uint, aeskey: *mut AES_KEY,\n    ) -> c_int;\n\n    fn AES_ecb_encrypt(\n        inp: *const u8, out: *mut u8, key: *const AES_KEY, enc: c_int,\n    ) -> c_void;\n\n    // ChaCha20\n    fn CRYPTO_chacha_20(\n        out: *mut u8, inp: *const u8, in_len: usize, key: *const u8,\n        nonce: *const u8, counter: u32,\n    ) -> c_void;\n}\n"
  },
  {
    "path": "quiche/src/crypto/mod.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse libc::c_int;\nuse libc::c_void;\n\nuse crate::Error;\nuse crate::Result;\n\nuse crate::packet;\n\n// All the AEAD algorithms we support use 96-bit nonces.\npub const MAX_NONCE_LEN: usize = 12;\n\n// Length of header protection mask.\npub const HP_MASK_LEN: usize = 5;\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum Level {\n    Initial   = 0,\n    ZeroRTT   = 1,\n    Handshake = 2,\n    OneRTT    = 3,\n}\n\nimpl Level {\n    pub fn from_epoch(e: packet::Epoch) -> Level {\n        match e {\n            packet::Epoch::Initial => Level::Initial,\n\n            packet::Epoch::Handshake => Level::Handshake,\n\n            packet::Epoch::Application => Level::OneRTT,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum Algorithm {\n    #[allow(non_camel_case_types)]\n    AES128_GCM,\n\n    #[allow(non_camel_case_types)]\n    AES256_GCM,\n\n    #[allow(non_camel_case_types)]\n    ChaCha20_Poly1305,\n}\n\n// Note: some vendor-specific methods are implemented by each vendor's submodule\n// (openssl-quictls / boringssl).\nimpl Algorithm {\n    fn get_evp_digest(self) -> *const EVP_MD {\n        match self {\n            Algorithm::AES128_GCM => unsafe { EVP_sha256() },\n            Algorithm::AES256_GCM => unsafe { EVP_sha384() },\n            Algorithm::ChaCha20_Poly1305 => unsafe { EVP_sha256() },\n        }\n    }\n\n    pub const fn key_len(self) -> usize {\n        match self {\n            Algorithm::AES128_GCM => 16,\n            Algorithm::AES256_GCM => 32,\n            Algorithm::ChaCha20_Poly1305 => 32,\n        }\n    }\n\n    pub const fn tag_len(self) -> usize {\n        if cfg!(feature = \"fuzzing\") {\n            return 16;\n        }\n\n        match self {\n            Algorithm::AES128_GCM => 16,\n            Algorithm::AES256_GCM => 16,\n            Algorithm::ChaCha20_Poly1305 => 16,\n        }\n    }\n\n    pub const fn nonce_len(self) -> usize {\n        match self {\n            Algorithm::AES128_GCM => 12,\n            Algorithm::AES256_GCM => 12,\n            Algorithm::ChaCha20_Poly1305 => 12,\n        }\n    }\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\npub struct EVP_AEAD {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct EVP_MD {\n    _unused: c_void,\n}\n\ntype HeaderProtectionMask = [u8; HP_MASK_LEN];\n\npub struct Open {\n    alg: Algorithm,\n\n    secret: Vec<u8>,\n\n    header: HeaderProtectionKey,\n\n    packet: PacketKey,\n}\n\nimpl Open {\n    // Note: some vendor-specific methods are implemented by each vendor's\n    // submodule (openssl-quictls / boringssl).\n\n    pub const DECRYPT: u32 = 0;\n\n    pub fn new(\n        alg: Algorithm, key: Vec<u8>, iv: Vec<u8>, hp_key: Vec<u8>,\n        secret: Vec<u8>,\n    ) -> Result<Open> {\n        Ok(Open {\n            alg,\n\n            secret,\n\n            header: HeaderProtectionKey::new(alg, hp_key)?,\n\n            packet: PacketKey::new(alg, key, iv, Self::DECRYPT)?,\n        })\n    }\n\n    pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result<Open> {\n        Ok(Open {\n            alg: aead,\n\n            secret: secret.to_vec(),\n\n            header: HeaderProtectionKey::from_secret(aead, secret)?,\n\n            packet: PacketKey::from_secret(aead, secret, Self::DECRYPT)?,\n        })\n    }\n\n    pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> {\n        if cfg!(feature = \"fuzzing\") {\n            return Ok(<[u8; 5]>::default());\n        }\n\n        self.header.new_mask(sample)\n    }\n\n    pub fn alg(&self) -> Algorithm {\n        self.alg\n    }\n\n    pub fn derive_next_packet_key(&self) -> Result<Open> {\n        let next_secret = derive_next_secret(self.alg, &self.secret)?;\n\n        let next_packet_key =\n            PacketKey::from_secret(self.alg, &next_secret, Self::DECRYPT)?;\n\n        Ok(Open {\n            alg: self.alg,\n\n            secret: next_secret,\n\n            header: self.header.clone(),\n\n            packet: next_packet_key,\n        })\n    }\n\n    pub fn open_with_u64_counter(\n        &self, counter: u64, ad: &[u8], buf: &mut [u8],\n    ) -> Result<usize> {\n        if cfg!(feature = \"fuzzing\") {\n            let tag_len = self.alg.tag_len();\n            let out_len = match buf.len().checked_sub(tag_len) {\n                Some(n) => n,\n                None => return Err(Error::CryptoFail),\n            };\n            if ad.len() > tag_len && buf[out_len..] == ad[..tag_len] {\n                return Err(Error::CryptoFail);\n            }\n            return Ok(out_len);\n        }\n\n        self.packet.open_with_u64_counter(counter, ad, buf)\n    }\n}\n\npub struct Seal {\n    alg: Algorithm,\n\n    secret: Vec<u8>,\n\n    header: HeaderProtectionKey,\n\n    packet: PacketKey,\n}\n\nimpl Seal {\n    // Note: some vendor-specific methods are implemented by each vendor's\n    // submodule (openssl-quictls / boringssl).\n\n    pub const ENCRYPT: u32 = 1;\n\n    pub fn new(\n        alg: Algorithm, key: Vec<u8>, iv: Vec<u8>, hp_key: Vec<u8>,\n        secret: Vec<u8>,\n    ) -> Result<Seal> {\n        Ok(Seal {\n            alg,\n\n            secret,\n\n            header: HeaderProtectionKey::new(alg, hp_key)?,\n\n            packet: PacketKey::new(alg, key, iv, Self::ENCRYPT)?,\n        })\n    }\n\n    pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result<Seal> {\n        Ok(Seal {\n            alg: aead,\n\n            secret: secret.to_vec(),\n\n            header: HeaderProtectionKey::from_secret(aead, secret)?,\n\n            packet: PacketKey::from_secret(aead, secret, Self::ENCRYPT)?,\n        })\n    }\n\n    pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5]> {\n        if cfg!(feature = \"fuzzing\") {\n            return Ok(<[u8; 5]>::default());\n        }\n\n        self.header.new_mask(sample)\n    }\n\n    pub fn alg(&self) -> Algorithm {\n        self.alg\n    }\n\n    pub fn derive_next_packet_key(&self) -> Result<Seal> {\n        let next_secret = derive_next_secret(self.alg, &self.secret)?;\n\n        let next_packet_key =\n            PacketKey::from_secret(self.alg, &next_secret, Self::ENCRYPT)?;\n\n        Ok(Seal {\n            alg: self.alg,\n\n            secret: next_secret,\n\n            header: self.header.clone(),\n\n            packet: next_packet_key,\n        })\n    }\n\n    pub fn seal_with_u64_counter(\n        &mut self, counter: u64, ad: &[u8], buf: &mut [u8], in_len: usize,\n        extra_in: Option<&[u8]>,\n    ) -> Result<usize> {\n        if cfg!(feature = \"fuzzing\") {\n            let tag_len = self.alg.tag_len();\n\n            if let Some(extra) = extra_in {\n                if in_len + tag_len + extra.len() > buf.len() {\n                    return Err(Error::CryptoFail);\n                }\n                buf[in_len..in_len + extra.len()].copy_from_slice(extra);\n                return Ok(in_len + extra.len());\n            }\n            if in_len + tag_len > buf.len() {\n                return Err(Error::CryptoFail);\n            }\n\n            return Ok(in_len + tag_len);\n        }\n\n        self.packet\n            .seal_with_u64_counter(counter, ad, buf, in_len, extra_in)\n    }\n}\n\nimpl HeaderProtectionKey {\n    pub fn from_secret(aead: Algorithm, secret: &[u8]) -> Result<Self> {\n        let key_len = aead.key_len();\n\n        let mut hp_key = vec![0; key_len];\n\n        derive_hdr_key(aead, secret, &mut hp_key)?;\n\n        Self::new(aead, hp_key)\n    }\n}\n\npub fn derive_initial_key_material(\n    cid: &[u8], version: u32, is_server: bool, did_reset: bool,\n) -> Result<(Open, Seal)> {\n    let mut initial_secret = [0; 32];\n    let mut client_secret = vec![0; 32];\n    let mut server_secret = vec![0; 32];\n\n    let aead = Algorithm::AES128_GCM;\n\n    let key_len = aead.key_len();\n    let nonce_len = aead.nonce_len();\n\n    derive_initial_secret(cid, version, &mut initial_secret)?;\n\n    derive_client_initial_secret(aead, &initial_secret, &mut client_secret)?;\n\n    derive_server_initial_secret(aead, &initial_secret, &mut server_secret)?;\n\n    // When the initial key material has been reset (e.g. due to retry or\n    // version negotiation), we need to prime the AEAD context as well, as the\n    // following packet will not start from 0 again. This is done through the\n    // `Open/Seal::from_secret()` path, rather than `Open/Seal::new()`.\n    if did_reset {\n        let (open, seal) = if is_server {\n            (\n                Open::from_secret(aead, &client_secret)?,\n                Seal::from_secret(aead, &server_secret)?,\n            )\n        } else {\n            (\n                Open::from_secret(aead, &server_secret)?,\n                Seal::from_secret(aead, &client_secret)?,\n            )\n        };\n\n        return Ok((open, seal));\n    }\n\n    // Client.\n    let mut client_key = vec![0; key_len];\n    let mut client_iv = vec![0; nonce_len];\n    let mut client_hp_key = vec![0; key_len];\n\n    derive_pkt_key(aead, &client_secret, &mut client_key)?;\n    derive_pkt_iv(aead, &client_secret, &mut client_iv)?;\n    derive_hdr_key(aead, &client_secret, &mut client_hp_key)?;\n\n    // Server.\n    let mut server_key = vec![0; key_len];\n    let mut server_iv = vec![0; nonce_len];\n    let mut server_hp_key = vec![0; key_len];\n\n    derive_pkt_key(aead, &server_secret, &mut server_key)?;\n    derive_pkt_iv(aead, &server_secret, &mut server_iv)?;\n    derive_hdr_key(aead, &server_secret, &mut server_hp_key)?;\n\n    let (open, seal) = if is_server {\n        (\n            Open::new(aead, client_key, client_iv, client_hp_key, client_secret)?,\n            Seal::new(aead, server_key, server_iv, server_hp_key, server_secret)?,\n        )\n    } else {\n        (\n            Open::new(aead, server_key, server_iv, server_hp_key, server_secret)?,\n            Seal::new(aead, client_key, client_iv, client_hp_key, client_secret)?,\n        )\n    };\n\n    Ok((open, seal))\n}\n\nfn derive_initial_secret(\n    secret: &[u8], version: u32, out_prk: &mut [u8],\n) -> Result<()> {\n    const INITIAL_SALT_V1: [u8; 20] = [\n        0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6,\n        0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a,\n    ];\n\n    let salt = match version {\n        crate::PROTOCOL_VERSION_V1 => &INITIAL_SALT_V1,\n\n        _ => &INITIAL_SALT_V1,\n    };\n\n    hkdf_extract(Algorithm::AES128_GCM, out_prk, secret, salt)\n}\n\nfn derive_client_initial_secret(\n    aead: Algorithm, prk: &[u8], out: &mut [u8],\n) -> Result<()> {\n    const LABEL: &[u8] = b\"client in\";\n    hkdf_expand_label(aead, prk, LABEL, out)\n}\n\nfn derive_server_initial_secret(\n    aead: Algorithm, prk: &[u8], out: &mut [u8],\n) -> Result<()> {\n    const LABEL: &[u8] = b\"server in\";\n    hkdf_expand_label(aead, prk, LABEL, out)\n}\n\nfn derive_next_secret(aead: Algorithm, secret: &[u8]) -> Result<Vec<u8>> {\n    const LABEL: &[u8] = b\"quic ku\";\n\n    let mut next_secret = vec![0u8; secret.len()];\n\n    hkdf_expand_label(aead, secret, LABEL, &mut next_secret)?;\n\n    Ok(next_secret)\n}\n\npub fn derive_hdr_key(\n    aead: Algorithm, secret: &[u8], out: &mut [u8],\n) -> Result<()> {\n    const LABEL: &[u8] = b\"quic hp\";\n\n    let key_len = aead.key_len();\n\n    if key_len > out.len() {\n        return Err(Error::CryptoFail);\n    }\n\n    hkdf_expand_label(aead, secret, LABEL, &mut out[..key_len])\n}\n\npub fn derive_pkt_key(aead: Algorithm, prk: &[u8], out: &mut [u8]) -> Result<()> {\n    const LABEL: &[u8] = b\"quic key\";\n\n    let key_len: usize = aead.key_len();\n\n    if key_len > out.len() {\n        return Err(Error::CryptoFail);\n    }\n\n    hkdf_expand_label(aead, prk, LABEL, &mut out[..key_len])\n}\n\npub fn derive_pkt_iv(aead: Algorithm, prk: &[u8], out: &mut [u8]) -> Result<()> {\n    const LABEL: &[u8] = b\"quic iv\";\n\n    let nonce_len = aead.nonce_len();\n\n    if nonce_len > out.len() {\n        return Err(Error::CryptoFail);\n    }\n\n    hkdf_expand_label(aead, prk, LABEL, &mut out[..nonce_len])\n}\n\nfn hkdf_expand_label(\n    alg: Algorithm, prk: &[u8], label: &[u8], out: &mut [u8],\n) -> Result<()> {\n    const LABEL_PREFIX: &[u8] = b\"tls13 \";\n\n    let out_len = (out.len() as u16).to_be_bytes();\n    let label_len = (LABEL_PREFIX.len() + label.len()) as u8;\n\n    let info = [&out_len, &[label_len][..], LABEL_PREFIX, label, &[0][..]];\n    let info = info.concat();\n\n    hkdf_expand(alg, out, prk, &info)?;\n\n    Ok(())\n}\n\nfn make_nonce(iv: &[u8], counter: u64) -> [u8; MAX_NONCE_LEN] {\n    let mut nonce = [0; MAX_NONCE_LEN];\n    nonce.copy_from_slice(iv);\n\n    // XOR the last bytes of the IV with the counter. This is equivalent to\n    // left-padding the counter with zero bytes.\n    for (a, b) in nonce[4..].iter_mut().zip(counter.to_be_bytes().iter()) {\n        *a ^= b;\n    }\n\n    nonce\n}\n\npub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()> {\n    if a.len() != b.len() {\n        return Err(Error::CryptoFail);\n    }\n\n    let rc = unsafe { CRYPTO_memcmp(a.as_ptr(), b.as_ptr(), a.len()) };\n\n    if rc == 0 {\n        return Ok(());\n    }\n\n    Err(Error::CryptoFail)\n}\n\nextern \"C\" {\n    fn EVP_sha256() -> *const EVP_MD;\n\n    fn EVP_sha384() -> *const EVP_MD;\n\n    // CRYPTO\n    fn CRYPTO_memcmp(a: *const u8, b: *const u8, len: usize) -> c_int;\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn derive_initial_secrets_v1() {\n        let dcid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];\n\n        let mut initial_secret = [0; 32];\n\n        let mut secret = [0; 32];\n        let mut pkt_key = [0; 16];\n        let mut pkt_iv = [0; 12];\n        let mut hdr_key = [0; 16];\n\n        let aead = Algorithm::AES128_GCM;\n\n        assert!(derive_initial_secret(\n            &dcid,\n            crate::PROTOCOL_VERSION_V1,\n            &mut initial_secret,\n        )\n        .is_ok());\n\n        // Client.\n        assert!(\n            derive_client_initial_secret(aead, &initial_secret, &mut secret)\n                .is_ok()\n        );\n        let expected_client_initial_secret = [\n            0xc0, 0x0c, 0xf1, 0x51, 0xca, 0x5b, 0xe0, 0x75, 0xed, 0x0e, 0xbf,\n            0xb5, 0xc8, 0x03, 0x23, 0xc4, 0x2d, 0x6b, 0x7d, 0xb6, 0x78, 0x81,\n            0x28, 0x9a, 0xf4, 0x00, 0x8f, 0x1f, 0x6c, 0x35, 0x7a, 0xea,\n        ];\n        assert_eq!(&secret, &expected_client_initial_secret);\n\n        assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok());\n        let expected_client_pkt_key = [\n            0x1f, 0x36, 0x96, 0x13, 0xdd, 0x76, 0xd5, 0x46, 0x77, 0x30, 0xef,\n            0xcb, 0xe3, 0xb1, 0xa2, 0x2d,\n        ];\n        assert_eq!(&pkt_key, &expected_client_pkt_key);\n\n        assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok());\n        let expected_client_pkt_iv = [\n            0xfa, 0x04, 0x4b, 0x2f, 0x42, 0xa3, 0xfd, 0x3b, 0x46, 0xfb, 0x25,\n            0x5c,\n        ];\n        assert_eq!(&pkt_iv, &expected_client_pkt_iv);\n\n        assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok());\n        let expected_client_hdr_key = [\n            0x9f, 0x50, 0x44, 0x9e, 0x04, 0xa0, 0xe8, 0x10, 0x28, 0x3a, 0x1e,\n            0x99, 0x33, 0xad, 0xed, 0xd2,\n        ];\n        assert_eq!(&hdr_key, &expected_client_hdr_key);\n\n        // Server.\n        assert!(\n            derive_server_initial_secret(aead, &initial_secret, &mut secret)\n                .is_ok()\n        );\n\n        let expected_server_initial_secret = [\n            0x3c, 0x19, 0x98, 0x28, 0xfd, 0x13, 0x9e, 0xfd, 0x21, 0x6c, 0x15,\n            0x5a, 0xd8, 0x44, 0xcc, 0x81, 0xfb, 0x82, 0xfa, 0x8d, 0x74, 0x46,\n            0xfa, 0x7d, 0x78, 0xbe, 0x80, 0x3a, 0xcd, 0xda, 0x95, 0x1b,\n        ];\n        assert_eq!(&secret, &expected_server_initial_secret);\n\n        assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok());\n        let expected_server_pkt_key = [\n            0xcf, 0x3a, 0x53, 0x31, 0x65, 0x3c, 0x36, 0x4c, 0x88, 0xf0, 0xf3,\n            0x79, 0xb6, 0x06, 0x7e, 0x37,\n        ];\n        assert_eq!(&pkt_key, &expected_server_pkt_key);\n\n        assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok());\n        let expected_server_pkt_iv = [\n            0x0a, 0xc1, 0x49, 0x3c, 0xa1, 0x90, 0x58, 0x53, 0xb0, 0xbb, 0xa0,\n            0x3e,\n        ];\n        assert_eq!(&pkt_iv, &expected_server_pkt_iv);\n\n        assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok());\n        let expected_server_hdr_key = [\n            0xc2, 0x06, 0xb8, 0xd9, 0xb9, 0xf0, 0xf3, 0x76, 0x44, 0x43, 0x0b,\n            0x49, 0x0e, 0xea, 0xa3, 0x14,\n        ];\n        assert_eq!(&hdr_key, &expected_server_hdr_key);\n    }\n\n    #[test]\n    fn derive_chacha20_secrets() {\n        let secret = [\n            0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42,\n            0x27, 0x48, 0xad, 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0,\n            0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3, 0x0f, 0x21, 0x63, 0x2b,\n        ];\n\n        let aead = Algorithm::ChaCha20_Poly1305;\n\n        let mut pkt_key = [0; 32];\n        let mut pkt_iv = [0; 12];\n        let mut hdr_key = [0; 32];\n\n        assert!(derive_pkt_key(aead, &secret, &mut pkt_key).is_ok());\n        let expected_pkt_key = [\n            0xc6, 0xd9, 0x8f, 0xf3, 0x44, 0x1c, 0x3f, 0xe1, 0xb2, 0x18, 0x20,\n            0x94, 0xf6, 0x9c, 0xaa, 0x2e, 0xd4, 0xb7, 0x16, 0xb6, 0x54, 0x88,\n            0x96, 0x0a, 0x7a, 0x98, 0x49, 0x79, 0xfb, 0x23, 0xe1, 0xc8,\n        ];\n        assert_eq!(&pkt_key, &expected_pkt_key);\n\n        assert!(derive_pkt_iv(aead, &secret, &mut pkt_iv).is_ok());\n        let expected_pkt_iv = [\n            0xe0, 0x45, 0x9b, 0x34, 0x74, 0xbd, 0xd0, 0xe4, 0x4a, 0x41, 0xc1,\n            0x44,\n        ];\n        assert_eq!(&pkt_iv, &expected_pkt_iv);\n\n        assert!(derive_hdr_key(aead, &secret, &mut hdr_key).is_ok());\n        let expected_hdr_key = [\n            0x25, 0xa2, 0x82, 0xb9, 0xe8, 0x2f, 0x06, 0xf2, 0x1f, 0x48, 0x89,\n            0x17, 0xa4, 0xfc, 0x8f, 0x1b, 0x73, 0x57, 0x36, 0x85, 0x60, 0x85,\n            0x97, 0xd0, 0xef, 0xcb, 0x07, 0x6b, 0x0a, 0xb7, 0xa7, 0xa4,\n        ];\n        assert_eq!(&hdr_key, &expected_hdr_key);\n\n        let next_secret = derive_next_secret(aead, &secret).unwrap();\n        let expected_secret = [\n            0x12, 0x23, 0x50, 0x47, 0x55, 0x03, 0x6d, 0x55, 0x63, 0x42, 0xee,\n            0x93, 0x61, 0xd2, 0x53, 0x42, 0x1a, 0x82, 0x6c, 0x9e, 0xcd, 0xf3,\n            0xc7, 0x14, 0x86, 0x84, 0xb3, 0x6b, 0x71, 0x48, 0x81, 0xf9,\n        ];\n        assert_eq!(&next_secret, &expected_secret);\n    }\n}\n\n#[cfg(not(feature = \"openssl\"))]\nmod boringssl;\n#[cfg(not(feature = \"openssl\"))]\npub(crate) use boringssl::*;\n\n#[cfg(feature = \"openssl\")]\nmod openssl_quictls;\n#[cfg(feature = \"openssl\")]\npub(crate) use openssl_quictls::*;\n"
  },
  {
    "path": "quiche/src/crypto/openssl_quictls.rs",
    "content": "use super::*;\n\nuse libc::c_int;\nuse libc::c_uchar;\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct EVP_CIPHER_CTX {\n    _unused: *mut EVP_CIPHER_CTX,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct EVP_PKEY_CTX {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct OSSL_PARAM {\n    _unused: c_void,\n}\n\nimpl Algorithm {\n    pub fn get_evp(self) -> *const EVP_AEAD {\n        match self {\n            Algorithm::AES128_GCM => unsafe { EVP_aes_128_ctr() },\n            Algorithm::AES256_GCM => unsafe { EVP_aes_256_ctr() },\n            Algorithm::ChaCha20_Poly1305 => unsafe { EVP_chacha20() },\n        }\n    }\n\n    pub fn get_evp_aead(self) -> *const EVP_AEAD {\n        match self {\n            Algorithm::AES128_GCM => unsafe { EVP_aes_128_gcm() },\n            Algorithm::AES256_GCM => unsafe { EVP_aes_256_gcm() },\n            Algorithm::ChaCha20_Poly1305 => unsafe { EVP_chacha20_poly1305() },\n        }\n    }\n}\n\npub(crate) struct PacketKey {\n    alg: Algorithm,\n\n    ctx: *mut EVP_CIPHER_CTX,\n\n    nonce: Vec<u8>,\n\n    // Note: We'd need the key for later use as it is needed by the openssl API.\n    // TODO: check if we can avoid this and get the key when needed and not\n    // have it stored here.\n    key: Vec<u8>,\n}\n\nimpl PacketKey {\n    pub fn new(\n        alg: Algorithm, key: Vec<u8>, iv: Vec<u8>, enc: u32,\n    ) -> Result<Self> {\n        Ok(Self {\n            alg,\n            ctx: make_evp_cipher_ctx_basic(alg, true, enc)?,\n            nonce: iv,\n            key,\n        })\n    }\n\n    pub fn from_secret(aead: Algorithm, secret: &[u8], enc: u32) -> Result<Self> {\n        let key_len = aead.key_len();\n        let nonce_len = aead.nonce_len();\n\n        let mut key = vec![0; key_len];\n        let mut iv = vec![0; nonce_len];\n\n        derive_pkt_key(aead, secret, &mut key)?;\n        derive_pkt_iv(aead, secret, &mut iv)?;\n\n        Self::new(aead, key, iv, enc)\n    }\n\n    pub fn open_with_u64_counter(\n        &self, counter: u64, ad: &[u8], buf: &mut [u8],\n    ) -> Result<usize> {\n        let tag_len = self.alg.tag_len();\n\n        let in_buf = buf.to_owned(); // very inefficient\n\n        let mut cipher_len = buf.len();\n\n        let nonce = make_nonce(&self.nonce, counter);\n\n        // Set the IV len.\n        const EVP_CTRL_AEAD_SET_IVLEN: i32 = 0x9;\n        let mut rc = unsafe {\n            EVP_CIPHER_CTX_ctrl(\n                self.ctx,\n                EVP_CTRL_AEAD_SET_IVLEN,\n                nonce.len() as i32,\n                std::ptr::null_mut(),\n            )\n        };\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        rc = unsafe {\n            EVP_CipherInit_ex2(\n                self.ctx,\n                std::ptr::null_mut(), // already set\n                self.key.as_ptr(),\n                nonce[..].as_ptr(),\n                Open::DECRYPT as i32,\n                std::ptr::null(),\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        let mut olen: i32 = 0;\n\n        if !ad.is_empty() {\n            rc = unsafe {\n                EVP_CipherUpdate(\n                    self.ctx,\n                    std::ptr::null_mut(),\n                    &mut olen,\n                    ad.as_ptr(),\n                    ad.len() as i32,\n                )\n            };\n\n            if rc != 1 {\n                return Err(Error::CryptoFail);\n            }\n        }\n\n        if cipher_len < tag_len {\n            return Err(Error::CryptoFail);\n        }\n\n        cipher_len -= tag_len;\n\n        rc = unsafe {\n            EVP_CipherUpdate(\n                self.ctx,\n                buf.as_mut_ptr(),\n                &mut olen,\n                in_buf.as_ptr(),\n                cipher_len as i32,\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        let plaintext_len = olen as usize;\n\n        const EVP_CTRL_AEAD_SET_TAG: i32 = 0x11;\n        rc = unsafe {\n            EVP_CIPHER_CTX_ctrl(\n                self.ctx,\n                EVP_CTRL_AEAD_SET_TAG,\n                tag_len as i32,\n                buf[cipher_len..].as_mut_ptr() as *mut c_void,\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        rc = unsafe {\n            EVP_CipherFinal_ex(\n                self.ctx,\n                buf[plaintext_len..].as_mut_ptr(),\n                &mut olen,\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        Ok(plaintext_len + olen as usize)\n    }\n\n    pub fn seal_with_u64_counter(\n        &mut self, counter: u64, ad: &[u8], buf: &mut [u8], in_len: usize,\n        _extra_in: Option<&[u8]>,\n    ) -> Result<usize> {\n        let tag_len = self.alg.tag_len();\n\n        // TODO: replace this with something more efficient.\n        let in_buf = buf.to_owned();\n\n        let nonce = make_nonce(&self.nonce, counter);\n\n        // Set the IV len.\n        const EVP_CTRL_AEAD_SET_IVLEN: i32 = 0x9;\n        let mut rc = unsafe {\n            EVP_CIPHER_CTX_ctrl(\n                self.ctx,\n                EVP_CTRL_AEAD_SET_IVLEN,\n                nonce.len() as i32,\n                std::ptr::null_mut(),\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        rc = unsafe {\n            EVP_CipherInit_ex2(\n                self.ctx,\n                std::ptr::null_mut(), // already set\n                self.key.as_ptr(),\n                nonce[..].as_ptr(),\n                Seal::ENCRYPT as i32,\n                std::ptr::null(),\n            )\n        };\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        let mut olen: i32 = 0;\n        let mut rc;\n\n        if !ad.is_empty() {\n            rc = unsafe {\n                EVP_CipherUpdate(\n                    self.ctx,\n                    std::ptr::null_mut(),\n                    &mut olen,\n                    ad.as_ptr(),\n                    ad.len() as i32,\n                )\n            };\n\n            if rc != 1 {\n                // We had AD but we couldn't set it.\n                return Err(Error::CryptoFail);\n            }\n        }\n\n        let mut ciphertext_len: usize = 0;\n\n        rc = unsafe {\n            EVP_CipherUpdate(\n                self.ctx,\n                buf.as_mut_ptr(),\n                &mut olen,\n                in_buf.as_ptr(),\n                in_len as i32,\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        };\n\n        ciphertext_len += olen as usize;\n\n        let len = olen as usize;\n        rc = unsafe {\n            EVP_CipherFinal_ex(self.ctx, buf[len..].as_mut_ptr(), &mut olen)\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        ciphertext_len += olen as usize;\n\n        const EVP_CTRL_AEAD_GET_TAG: i32 = 0x10;\n        rc = unsafe {\n            EVP_CIPHER_CTX_ctrl(\n                self.ctx,\n                EVP_CTRL_AEAD_GET_TAG,\n                tag_len as i32,\n                buf[ciphertext_len..].as_mut_ptr() as *mut c_void,\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        Ok(in_len + tag_len)\n    }\n}\n\nimpl Drop for PacketKey {\n    fn drop(&mut self) {\n        unsafe { EVP_CIPHER_CTX_free(self.ctx) }\n    }\n}\n\nunsafe impl Send for PacketKey {}\nunsafe impl Sync for PacketKey {}\n\npub(crate) struct HeaderProtectionKey {\n    ctx: *mut EVP_CIPHER_CTX,\n\n    key: Vec<u8>,\n}\n\nimpl HeaderProtectionKey {\n    pub fn new(alg: Algorithm, hp_key: Vec<u8>) -> Result<Self> {\n        Ok(Self {\n            ctx: make_evp_cipher_ctx_basic(alg, false, 1)?,\n            key: hp_key,\n        })\n    }\n\n    pub fn new_mask(&self, sample: &[u8]) -> Result<HeaderProtectionMask> {\n        const PLAINTEXT: &[u8; 5] = &[0_u8; 5];\n\n        let mut new_mask = HeaderProtectionMask::default();\n\n        // Set IV (i.e. the sample).\n        let rc = unsafe {\n            EVP_CipherInit_ex2(\n                self.ctx,\n                std::ptr::null_mut(), // already set\n                self.key.as_ptr(),\n                sample.as_ptr(),\n                -1,\n                std::ptr::null(),\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        let mut out_len: i32 = 0;\n\n        let rc = unsafe {\n            EVP_CipherUpdate(\n                self.ctx,\n                new_mask.as_mut_ptr(),\n                &mut out_len,\n                PLAINTEXT.as_ptr(),\n                PLAINTEXT.len() as i32,\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        };\n\n        let rc = unsafe {\n            EVP_CipherFinal_ex(\n                self.ctx,\n                new_mask[out_len as usize..].as_mut_ptr(),\n                &mut out_len,\n            )\n        };\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        Ok(new_mask)\n    }\n}\n\nimpl Clone for HeaderProtectionKey {\n    fn clone(&self) -> Self {\n        let ctx = unsafe { EVP_CIPHER_CTX_dup(self.ctx) };\n\n        Self {\n            ctx,\n            key: self.key.clone(),\n        }\n    }\n}\n\nimpl Drop for HeaderProtectionKey {\n    fn drop(&mut self) {\n        unsafe { EVP_CIPHER_CTX_free(self.ctx) }\n    }\n}\n\nunsafe impl Send for HeaderProtectionKey {}\nunsafe impl Sync for HeaderProtectionKey {}\n\nfn make_evp_cipher_ctx_basic(\n    alg: Algorithm, aead: bool, enc: u32,\n) -> Result<*mut EVP_CIPHER_CTX> {\n    let ctx: *mut EVP_CIPHER_CTX = unsafe {\n        let cipher: *const EVP_AEAD = if aead {\n            alg.get_evp_aead()\n        } else {\n            alg.get_evp()\n        };\n\n        let ctx = EVP_CIPHER_CTX_new();\n\n        if ctx.is_null() {\n            return Err(Error::CryptoFail);\n        }\n\n        let rc = EVP_CipherInit_ex2(\n            ctx,\n            cipher,\n            std::ptr::null_mut(),\n            std::ptr::null_mut(),\n            enc as c_int, // Following calls can use -1 once this is set.\n            std::ptr::null(),\n        );\n\n        if rc != 1 {\n            return Err(Error::CryptoFail);\n        }\n\n        ctx\n    };\n\n    Ok(ctx)\n}\n\npub(crate) fn hkdf_extract(\n    alg: Algorithm, out: &mut [u8], secret: &[u8], salt: &[u8],\n) -> Result<()> {\n    let mut out_len = out.len();\n\n    unsafe {\n        let prf = alg.get_evp_digest();\n\n        let ctx = EVP_PKEY_CTX_new_id(\n            1036, // EVP_PKEY_HKDF\n            std::ptr::null_mut(),\n        );\n\n        if EVP_PKEY_derive_init(ctx) != 1 ||\n            EVP_PKEY_CTX_set_hkdf_mode(\n                ctx, 1, // EVP_PKEY_HKDF_MODE_EXTRACT_ONLY\n            ) != 1 ||\n            EVP_PKEY_CTX_set_hkdf_md(ctx, prf) != 1 ||\n            EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt.as_ptr(), salt.len()) != 1 ||\n            EVP_PKEY_CTX_set1_hkdf_key(ctx, secret.as_ptr(), secret.len()) != 1 ||\n            EVP_PKEY_derive(ctx, out.as_mut_ptr(), &mut out_len) != 1\n        {\n            EVP_PKEY_CTX_free(ctx);\n            return Err(Error::CryptoFail);\n        }\n\n        EVP_PKEY_CTX_free(ctx);\n    }\n\n    Ok(())\n}\n\npub(crate) fn hkdf_expand(\n    alg: Algorithm, out: &mut [u8], secret: &[u8], info: &[u8],\n) -> Result<()> {\n    let mut out_len = out.len();\n\n    unsafe {\n        let prf = alg.get_evp_digest();\n\n        let ctx = EVP_PKEY_CTX_new_id(\n            1036, // EVP_PKEY_HKDF\n            std::ptr::null_mut(),\n        );\n\n        if EVP_PKEY_derive_init(ctx) != 1 ||\n            EVP_PKEY_CTX_set_hkdf_mode(\n                ctx, 2, // EVP_PKEY_HKDF_MODE_EXPAND_ONLY\n            ) != 1 ||\n            EVP_PKEY_CTX_set_hkdf_md(ctx, prf) != 1 ||\n            EVP_PKEY_CTX_set1_hkdf_key(ctx, secret.as_ptr(), secret.len()) != 1 ||\n            EVP_PKEY_CTX_add1_hkdf_info(ctx, info.as_ptr(), info.len()) != 1 ||\n            EVP_PKEY_derive(ctx, out.as_mut_ptr(), &mut out_len) != 1\n        {\n            EVP_PKEY_CTX_free(ctx);\n            return Err(Error::CryptoFail);\n        }\n\n        EVP_PKEY_CTX_free(ctx);\n    }\n\n    Ok(())\n}\n\nextern \"C\" {\n    // EVP\n    fn EVP_aes_128_ctr() -> *const EVP_AEAD;\n    fn EVP_aes_128_gcm() -> *const EVP_AEAD;\n\n    fn EVP_aes_256_ctr() -> *const EVP_AEAD;\n    fn EVP_aes_256_gcm() -> *const EVP_AEAD;\n\n    fn EVP_chacha20() -> *const EVP_AEAD;\n    fn EVP_chacha20_poly1305() -> *const EVP_AEAD;\n\n    // EVP_CIPHER_CTX\n    fn EVP_CIPHER_CTX_new() -> *mut EVP_CIPHER_CTX;\n\n    fn EVP_CIPHER_CTX_dup(ctx: *const EVP_CIPHER_CTX) -> *mut EVP_CIPHER_CTX;\n\n    fn EVP_CIPHER_CTX_free(ctx: *mut EVP_CIPHER_CTX);\n\n    fn EVP_CipherInit_ex2(\n        ctx: *mut EVP_CIPHER_CTX, cipher: *const EVP_AEAD, key: *const c_uchar,\n        iv: *const c_uchar, enc: c_int, params: *const OSSL_PARAM,\n    ) -> c_int;\n\n    fn EVP_CIPHER_CTX_ctrl(\n        ctx: *mut EVP_CIPHER_CTX, type_: i32, arg: i32, ptr: *mut c_void,\n    ) -> c_int;\n\n    fn EVP_CipherUpdate(\n        ctx: *mut EVP_CIPHER_CTX, out: *mut c_uchar, outl: *mut c_int,\n        in_: *const c_uchar, inl: i32,\n    ) -> c_int;\n\n    fn EVP_CipherFinal_ex(\n        ctx: *mut EVP_CIPHER_CTX, out: *mut c_uchar, outl: *mut c_int,\n    ) -> c_int;\n\n    // EVP_PKEY\n    fn EVP_PKEY_CTX_new_id(id: c_int, e: *mut c_void) -> *mut EVP_PKEY_CTX;\n\n    fn EVP_PKEY_CTX_set_hkdf_mode(ctx: *mut EVP_PKEY_CTX, mode: c_int) -> c_int;\n    fn EVP_PKEY_CTX_set_hkdf_md(\n        ctx: *mut EVP_PKEY_CTX, md: *const EVP_MD,\n    ) -> c_int;\n    fn EVP_PKEY_CTX_set1_hkdf_salt(\n        ctx: *mut EVP_PKEY_CTX, salt: *const u8, salt_len: usize,\n    ) -> c_int;\n    fn EVP_PKEY_CTX_set1_hkdf_key(\n        ctx: *mut EVP_PKEY_CTX, key: *const u8, key_len: usize,\n    ) -> c_int;\n    fn EVP_PKEY_CTX_add1_hkdf_info(\n        ctx: *mut EVP_PKEY_CTX, info: *const u8, info_len: usize,\n    ) -> c_int;\n\n    fn EVP_PKEY_derive_init(ctx: *mut EVP_PKEY_CTX) -> c_int;\n\n    fn EVP_PKEY_derive(\n        ctx: *mut EVP_PKEY_CTX, key: *mut u8, key_len: *mut usize,\n    ) -> c_int;\n\n    fn EVP_PKEY_CTX_free(ctx: *mut EVP_PKEY_CTX);\n}\n"
  },
  {
    "path": "quiche/src/dgram.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::Error;\nuse crate::Result;\n\nuse std::collections::VecDeque;\n\n/// Keeps track of DATAGRAM frames.\n#[derive(Default)]\npub struct DatagramQueue {\n    queue: Option<VecDeque<Vec<u8>>>,\n    queue_max_len: usize,\n    queue_bytes_size: usize,\n}\n\nimpl DatagramQueue {\n    pub fn new(queue_max_len: usize) -> Self {\n        DatagramQueue {\n            queue: None,\n            queue_bytes_size: 0,\n            queue_max_len,\n        }\n    }\n\n    pub fn push(&mut self, data: Vec<u8>) -> Result<()> {\n        if self.is_full() {\n            return Err(Error::Done);\n        }\n\n        self.queue_bytes_size += data.len();\n        self.queue\n            .get_or_insert_with(Default::default)\n            .push_back(data);\n\n        Ok(())\n    }\n\n    pub fn peek_front_len(&self) -> Option<usize> {\n        self.queue.as_ref().and_then(|q| q.front().map(|d| d.len()))\n    }\n\n    pub fn peek_front_bytes(&self, buf: &mut [u8], len: usize) -> Result<usize> {\n        match self.queue.as_ref().and_then(|q| q.front()) {\n            Some(d) => {\n                let len = std::cmp::min(len, d.len());\n                if buf.len() < len {\n                    return Err(Error::BufferTooShort);\n                }\n\n                buf[..len].copy_from_slice(&d[..len]);\n                Ok(len)\n            },\n\n            None => Err(Error::Done),\n        }\n    }\n\n    pub fn pop(&mut self) -> Option<Vec<u8>> {\n        if let Some(d) = self.queue.as_mut().and_then(|q| q.pop_front()) {\n            self.queue_bytes_size = self.queue_bytes_size.saturating_sub(d.len());\n            return Some(d);\n        }\n\n        None\n    }\n\n    pub fn has_pending(&self) -> bool {\n        !self.queue.as_ref().map(|q| q.is_empty()).unwrap_or(true)\n    }\n\n    pub fn purge<F: Fn(&[u8]) -> bool>(&mut self, f: F) {\n        if let Some(q) = self.queue.as_mut() {\n            q.retain(|d| !f(d));\n            self.queue_bytes_size = q.iter().fold(0, |total, d| total + d.len());\n        }\n    }\n\n    pub fn is_full(&self) -> bool {\n        self.len() == self.queue_max_len\n    }\n\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    pub fn len(&self) -> usize {\n        self.queue.as_ref().map(|q| q.len()).unwrap_or(0)\n    }\n\n    pub fn byte_size(&self) -> usize {\n        self.queue_bytes_size\n    }\n}\n"
  },
  {
    "path": "quiche/src/error.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n/// A specialized [`Result`] type for quiche operations.\n///\n/// This type is used throughout quiche's public API for any operation that\n/// can produce an error.\n///\n/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html\npub type Result<T> = std::result::Result<T, Error>;\n\n/// A QUIC error.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum Error {\n    /// There is no more work to do.\n    Done,\n\n    /// The provided buffer is too short.\n    BufferTooShort,\n\n    /// The provided packet cannot be parsed because its version is unknown.\n    UnknownVersion,\n\n    /// The provided packet cannot be parsed because it contains an invalid\n    /// frame.\n    InvalidFrame,\n\n    /// The provided packet cannot be parsed.\n    InvalidPacket,\n\n    /// The operation cannot be completed because the connection is in an\n    /// invalid state.\n    InvalidState,\n\n    /// The operation cannot be completed because the stream is in an\n    /// invalid state.\n    ///\n    /// The stream ID is provided as associated data.\n    InvalidStreamState(u64),\n\n    /// The peer's transport params cannot be parsed.\n    InvalidTransportParam,\n\n    /// A cryptographic operation failed.\n    CryptoFail,\n\n    /// The TLS handshake failed.\n    TlsFail,\n\n    /// The peer violated the local flow control limits.\n    FlowControl,\n\n    /// The peer violated the local stream limits.\n    StreamLimit,\n\n    /// The specified stream was stopped by the peer.\n    ///\n    /// The error code sent as part of the `STOP_SENDING` frame is provided as\n    /// associated data.\n    StreamStopped(u64),\n\n    /// The specified stream was reset by the peer.\n    ///\n    /// The error code sent as part of the `RESET_STREAM` frame is provided as\n    /// associated data.\n    StreamReset(u64),\n\n    /// The received data exceeds the stream's final size.\n    FinalSize,\n\n    /// Error in congestion control.\n    CongestionControl,\n\n    /// Too many identifiers were provided.\n    IdLimit,\n\n    /// Not enough available identifiers.\n    OutOfIdentifiers,\n\n    /// Error in key update.\n    KeyUpdate,\n\n    /// The peer sent more data in CRYPTO frames than we can buffer.\n    CryptoBufferExceeded,\n\n    /// The peer sent an ACK frame with an invalid range.\n    InvalidAckRange,\n\n    /// The peer send an ACK frame for a skipped packet used for Optimistic ACK\n    /// mitigation.\n    OptimisticAckDetected,\n\n    /// An invalid DCID was used when connecting to a remote peer.\n    InvalidDcidInitialization,\n}\n\n/// QUIC error codes sent on the wire.\n///\n/// As defined in [RFC9000](https://www.rfc-editor.org/rfc/rfc9000.html#name-error-codes).\n#[derive(Copy, Clone, Debug, Eq, PartialEq)]\npub enum WireErrorCode {\n    /// An endpoint uses this with CONNECTION_CLOSE to signal that the\n    /// connection is being closed abruptly in the absence of any error.\n    NoError              = 0x0,\n    /// The endpoint encountered an internal error and cannot continue with the\n    /// connection.\n    InternalError        = 0x1,\n    /// The server refused to accept a new connection.\n    ConnectionRefused    = 0x2,\n    /// An endpoint received more data than it permitted in its advertised data\n    /// limits; see Section 4.\n    FlowControlError     = 0x3,\n    /// An endpoint received a frame for a stream identifier that exceeded its\n    /// advertised stream limit for the corresponding stream type.\n    StreamLimitError     = 0x4,\n    /// An endpoint received a frame for a stream that was not in a state that\n    /// permitted that frame.\n    StreamStateError     = 0x5,\n    /// (1) An endpoint received a STREAM frame containing data that exceeded\n    /// the previously established final size, (2) an endpoint received a\n    /// STREAM frame or a RESET_STREAM frame containing a final size that\n    /// was lower than the size of stream data that was already received, or\n    /// (3) an endpoint received a STREAM frame or a RESET_STREAM frame\n    /// containing a different final size to the one already established.\n    FinalSizeError       = 0x6,\n    /// An endpoint received a frame that was badly formatted -- for instance, a\n    /// frame of an unknown type or an ACK frame that has more\n    /// acknowledgment ranges than the remainder of the packet could carry.\n    FrameEncodingError   = 0x7,\n    /// An endpoint received transport parameters that were badly formatted,\n    /// included an invalid value, omitted a mandatory transport parameter,\n    /// included a forbidden transport parameter, or were otherwise in\n    /// error.\n    TransportParameterError = 0x8,\n    /// The number of connection IDs provided by the peer exceeds the advertised\n    /// active_connection_id_limit.\n    ConnectionIdLimitError = 0x9,\n    /// An endpoint detected an error with protocol compliance that was not\n    /// covered by more specific error codes.\n    ProtocolViolation    = 0xa,\n    /// A server received a client Initial that contained an invalid Token\n    /// field.\n    InvalidToken         = 0xb,\n    /// The application or application protocol caused the connection to be\n    /// closed.\n    ApplicationError     = 0xc,\n    /// An endpoint has received more data in CRYPTO frames than it can buffer.\n    CryptoBufferExceeded = 0xd,\n    /// An endpoint detected errors in performing key updates.\n    KeyUpdateError       = 0xe,\n    /// An endpoint has reached the confidentiality or integrity limit for the\n    /// AEAD algorithm used by the given connection.\n    AeadLimitReached     = 0xf,\n    /// An endpoint has determined that the network path is incapable of\n    /// supporting QUIC. An endpoint is unlikely to receive a\n    /// CONNECTION_CLOSE frame carrying this code except when the path does\n    /// not support a large enough MTU.\n    NoViablePath         = 0x10,\n}\n\nimpl Error {\n    pub(crate) fn to_wire(self) -> u64 {\n        match self {\n            Error::Done => WireErrorCode::NoError as u64,\n            Error::InvalidFrame => WireErrorCode::FrameEncodingError as u64,\n            Error::InvalidStreamState(..) =>\n                WireErrorCode::StreamStateError as u64,\n            Error::InvalidTransportParam =>\n                WireErrorCode::TransportParameterError as u64,\n            Error::FlowControl => WireErrorCode::FlowControlError as u64,\n            Error::StreamLimit => WireErrorCode::StreamLimitError as u64,\n            Error::IdLimit => WireErrorCode::ConnectionIdLimitError as u64,\n            Error::FinalSize => WireErrorCode::FinalSizeError as u64,\n            Error::CryptoBufferExceeded =>\n                WireErrorCode::CryptoBufferExceeded as u64,\n            Error::KeyUpdate => WireErrorCode::KeyUpdateError as u64,\n            _ => WireErrorCode::ProtocolViolation as u64,\n        }\n    }\n\n    #[cfg(feature = \"ffi\")]\n    pub(crate) fn to_c(self) -> libc::ssize_t {\n        match self {\n            Error::Done => -1,\n            Error::BufferTooShort => -2,\n            Error::UnknownVersion => -3,\n            Error::InvalidFrame => -4,\n            Error::InvalidPacket => -5,\n            Error::InvalidState => -6,\n            Error::InvalidStreamState(_) => -7,\n            Error::InvalidTransportParam => -8,\n            Error::CryptoFail => -9,\n            Error::TlsFail => -10,\n            Error::FlowControl => -11,\n            Error::StreamLimit => -12,\n            Error::FinalSize => -13,\n            Error::CongestionControl => -14,\n            Error::StreamStopped { .. } => -15,\n            Error::StreamReset { .. } => -16,\n            Error::IdLimit => -17,\n            Error::OutOfIdentifiers => -18,\n            Error::KeyUpdate => -19,\n            Error::CryptoBufferExceeded => -20,\n            Error::InvalidAckRange => -21,\n            Error::OptimisticAckDetected => -22,\n            Error::InvalidDcidInitialization => -23,\n        }\n    }\n}\n\nimpl std::fmt::Display for Error {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl std::error::Error for Error {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        None\n    }\n}\n\nimpl From<octets::BufferTooShortError> for Error {\n    fn from(_err: octets::BufferTooShortError) -> Self {\n        Error::BufferTooShort\n    }\n}\n\n/// Represents information carried by `CONNECTION_CLOSE` frames.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct ConnectionError {\n    /// Whether the error came from the application or the transport layer.\n    pub is_app: bool,\n\n    /// The error code carried by the `CONNECTION_CLOSE` frame.\n    pub error_code: u64,\n\n    /// The reason carried by the `CONNECTION_CLOSE` frame.\n    pub reason: Vec<u8>,\n}\n"
  },
  {
    "path": "quiche/src/ffi.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::ffi;\nuse std::ptr;\nuse std::slice;\nuse std::sync::atomic;\n\nuse std::net::Ipv4Addr;\nuse std::net::Ipv6Addr;\nuse std::net::SocketAddrV4;\nuse std::net::SocketAddrV6;\n\n#[cfg(unix)]\nuse std::os::unix::io::FromRawFd;\n\nuse libc::c_char;\nuse libc::c_int;\nuse libc::c_void;\nuse libc::size_t;\nuse libc::sockaddr;\nuse libc::ssize_t;\nuse libc::timespec;\n\n#[cfg(not(windows))]\nuse libc::AF_INET;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::AF_INET;\n\n#[cfg(not(windows))]\nuse libc::AF_INET6;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::AF_INET6;\n\n#[cfg(not(windows))]\nuse libc::in_addr;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::IN_ADDR as in_addr;\n\n#[cfg(not(windows))]\nuse libc::in6_addr;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::IN6_ADDR as in6_addr;\n\n#[cfg(not(windows))]\nuse libc::sa_family_t;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::ADDRESS_FAMILY as sa_family_t;\n\n#[cfg(not(windows))]\nuse libc::sockaddr_in;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::SOCKADDR_IN as sockaddr_in;\n\n#[cfg(not(windows))]\nuse libc::sockaddr_in6;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::SOCKADDR_IN6 as sockaddr_in6;\n\n#[cfg(not(windows))]\nuse libc::sockaddr_storage;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::SOCKADDR_STORAGE as sockaddr_storage;\n\n#[cfg(windows)]\nuse libc::c_int as socklen_t;\n#[cfg(not(windows))]\nuse libc::socklen_t;\n\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::IN6_ADDR_0;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::IN_ADDR_0;\n#[cfg(windows)]\nuse windows_sys::Win32::Networking::WinSock::SOCKADDR_IN6_0;\n\nuse crate::*;\n\n#[no_mangle]\npub extern \"C\" fn quiche_version() -> *const u8 {\n    static VERSION: &str = concat!(env!(\"CARGO_PKG_VERSION\"), \"\\0\");\n    VERSION.as_ptr()\n}\n\nstruct Logger {\n    cb: extern \"C\" fn(line: *const u8, argp: *mut c_void),\n    argp: atomic::AtomicPtr<c_void>,\n}\n\nimpl log::Log for Logger {\n    fn enabled(&self, _metadata: &log::Metadata) -> bool {\n        true\n    }\n\n    fn log(&self, record: &log::Record) {\n        let line = format!(\"{}: {}\\0\", record.target(), record.args());\n        (self.cb)(line.as_ptr(), self.argp.load(atomic::Ordering::Relaxed));\n    }\n\n    fn flush(&self) {}\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_enable_debug_logging(\n    cb: extern \"C\" fn(line: *const u8, argp: *mut c_void), argp: *mut c_void,\n) -> c_int {\n    let argp = atomic::AtomicPtr::new(argp);\n    let logger = Box::new(Logger { cb, argp });\n\n    if log::set_boxed_logger(logger).is_err() {\n        return -1;\n    }\n\n    log::set_max_level(log::LevelFilter::Trace);\n\n    0\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_new(version: u32) -> *mut Config {\n    match Config::new(version) {\n        Ok(c) => Box::into_raw(Box::new(c)),\n\n        Err(_) => ptr::null_mut(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_load_cert_chain_from_pem_file(\n    config: &mut Config, path: *const c_char,\n) -> c_int {\n    let path = unsafe { ffi::CStr::from_ptr(path).to_str().unwrap() };\n\n    match config.load_cert_chain_from_pem_file(path) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_load_priv_key_from_pem_file(\n    config: &mut Config, path: *const c_char,\n) -> c_int {\n    let path = unsafe { ffi::CStr::from_ptr(path).to_str().unwrap() };\n\n    match config.load_priv_key_from_pem_file(path) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_load_verify_locations_from_file(\n    config: &mut Config, path: *const c_char,\n) -> c_int {\n    let path = unsafe { ffi::CStr::from_ptr(path).to_str().unwrap() };\n\n    match config.load_verify_locations_from_file(path) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_load_verify_locations_from_directory(\n    config: &mut Config, path: *const c_char,\n) -> c_int {\n    let path = unsafe { ffi::CStr::from_ptr(path).to_str().unwrap() };\n\n    match config.load_verify_locations_from_directory(path) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_verify_peer(config: &mut Config, v: bool) {\n    config.verify_peer(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_grease(config: &mut Config, v: bool) {\n    config.grease(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_discover_pmtu(config: &mut Config, v: bool) {\n    config.discover_pmtu(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_pmtud_max_probes(\n    config: &mut Config, max_probes: u8,\n) {\n    config.set_pmtud_max_probes(max_probes);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_log_keys(config: &mut Config) {\n    config.log_keys();\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_enable_early_data(config: &mut Config) {\n    config.enable_early_data();\n}\n\n#[no_mangle]\n/// Corresponds to the `Config::set_application_protos_wire_format` Rust\n/// function.\npub extern \"C\" fn quiche_config_set_application_protos(\n    config: &mut Config, protos: *const u8, protos_len: size_t,\n) -> c_int {\n    let protos = unsafe { slice::from_raw_parts(protos, protos_len) };\n\n    match config.set_application_protos_wire_format(protos) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_max_amplification_factor(\n    config: &mut Config, v: usize,\n) {\n    config.set_max_amplification_factor(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_max_idle_timeout(\n    config: &mut Config, v: u64,\n) {\n    config.set_max_idle_timeout(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_max_recv_udp_payload_size(\n    config: &mut Config, v: size_t,\n) {\n    config.set_max_recv_udp_payload_size(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_initial_max_data(\n    config: &mut Config, v: u64,\n) {\n    config.set_initial_max_data(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_initial_max_stream_data_bidi_local(\n    config: &mut Config, v: u64,\n) {\n    config.set_initial_max_stream_data_bidi_local(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_initial_max_stream_data_bidi_remote(\n    config: &mut Config, v: u64,\n) {\n    config.set_initial_max_stream_data_bidi_remote(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_initial_max_stream_data_uni(\n    config: &mut Config, v: u64,\n) {\n    config.set_initial_max_stream_data_uni(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_initial_max_streams_bidi(\n    config: &mut Config, v: u64,\n) {\n    config.set_initial_max_streams_bidi(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_initial_max_streams_uni(\n    config: &mut Config, v: u64,\n) {\n    config.set_initial_max_streams_uni(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_ack_delay_exponent(\n    config: &mut Config, v: u64,\n) {\n    config.set_ack_delay_exponent(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_max_ack_delay(config: &mut Config, v: u64) {\n    config.set_max_ack_delay(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_disable_active_migration(\n    config: &mut Config, v: bool,\n) {\n    config.set_disable_active_migration(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_cc_algorithm_name(\n    config: &mut Config, name: *const c_char,\n) -> c_int {\n    let name = unsafe { ffi::CStr::from_ptr(name).to_str().unwrap() };\n    match config.set_cc_algorithm_name(name) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_cc_algorithm(\n    config: &mut Config, algo: CongestionControlAlgorithm,\n) {\n    config.set_cc_algorithm(algo);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_initial_congestion_window_packets(\n    config: &mut Config, packets: size_t,\n) {\n    config.set_initial_congestion_window_packets(packets);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_enable_hystart(config: &mut Config, v: bool) {\n    config.enable_hystart(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_enable_pacing(config: &mut Config, v: bool) {\n    config.enable_pacing(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_enable_cubic_idle_restart_fix(\n    config: &mut Config, v: bool,\n) {\n    config.set_enable_cubic_idle_restart_fix(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_max_pacing_rate(config: &mut Config, v: u64) {\n    config.set_max_pacing_rate(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_enable_dgram(\n    config: &mut Config, enabled: bool, recv_queue_len: size_t,\n    send_queue_len: size_t,\n) {\n    config.enable_dgram(enabled, recv_queue_len, send_queue_len);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_max_send_udp_payload_size(\n    config: &mut Config, v: size_t,\n) {\n    config.set_max_send_udp_payload_size(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_max_connection_window(\n    config: &mut Config, v: u64,\n) {\n    config.set_max_connection_window(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_max_stream_window(\n    config: &mut Config, v: u64,\n) {\n    config.set_max_stream_window(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_active_connection_id_limit(\n    config: &mut Config, v: u64,\n) {\n    config.set_active_connection_id_limit(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_stateless_reset_token(\n    config: &mut Config, v: *const u8,\n) {\n    let reset_token = unsafe { slice::from_raw_parts(v, 16) };\n    let reset_token = match reset_token.try_into() {\n        Ok(rt) => rt,\n        Err(_) => unreachable!(),\n    };\n    let reset_token = u128::from_be_bytes(reset_token);\n    config.set_stateless_reset_token(Some(reset_token));\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_disable_dcid_reuse(\n    config: &mut Config, v: bool,\n) {\n    config.set_disable_dcid_reuse(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_set_ticket_key(\n    config: &mut Config, key: *const u8, key_len: size_t,\n) -> c_int {\n    let key = unsafe { slice::from_raw_parts(key, key_len) };\n\n    match config.set_ticket_key(key) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_config_free(config: *mut Config) {\n    drop(unsafe { Box::from_raw(config) });\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_header_info(\n    buf: *mut u8, buf_len: size_t, dcil: size_t, version: *mut u32, ty: *mut u8,\n    scid: *mut u8, scid_len: *mut size_t, dcid: *mut u8, dcid_len: *mut size_t,\n    token: *mut u8, token_len: *mut size_t,\n) -> c_int {\n    let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };\n    let hdr = match Header::from_slice(buf, dcil) {\n        Ok(v) => v,\n\n        Err(e) => return e.to_c() as c_int,\n    };\n\n    unsafe {\n        *version = hdr.version;\n\n        *ty = match hdr.ty {\n            Type::Initial => 1,\n            Type::Retry => 2,\n            Type::Handshake => 3,\n            Type::ZeroRTT => 4,\n            Type::Short => 5,\n            Type::VersionNegotiation => 6,\n        };\n\n        if *scid_len < hdr.scid.len() {\n            return -1;\n        }\n\n        let scid = slice::from_raw_parts_mut(scid, *scid_len);\n        let scid = &mut scid[..hdr.scid.len()];\n        scid.copy_from_slice(&hdr.scid);\n\n        *scid_len = hdr.scid.len();\n\n        if *dcid_len < hdr.dcid.len() {\n            return -1;\n        }\n\n        let dcid = slice::from_raw_parts_mut(dcid, *dcid_len);\n        let dcid = &mut dcid[..hdr.dcid.len()];\n        dcid.copy_from_slice(&hdr.dcid);\n\n        *dcid_len = hdr.dcid.len();\n\n        match hdr.token {\n            Some(tok) => {\n                if *token_len < tok.len() {\n                    return -1;\n                }\n\n                let token = slice::from_raw_parts_mut(token, *token_len);\n                let token = &mut token[..tok.len()];\n                token.copy_from_slice(&tok);\n\n                *token_len = tok.len();\n            },\n\n            None => *token_len = 0,\n        }\n    }\n\n    0\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_accept(\n    scid: *const u8, scid_len: size_t, odcid: *const u8, odcid_len: size_t,\n    local: &sockaddr, local_len: socklen_t, peer: &sockaddr, peer_len: socklen_t,\n    config: &mut Config,\n) -> *mut Connection {\n    let scid = unsafe { slice::from_raw_parts(scid, scid_len) };\n    let scid = ConnectionId::from_ref(scid);\n\n    let odcid = if !odcid.is_null() && odcid_len > 0 {\n        Some(ConnectionId::from_ref(unsafe {\n            slice::from_raw_parts(odcid, odcid_len)\n        }))\n    } else {\n        None\n    };\n\n    let local = std_addr_from_c(local, local_len);\n    let peer = std_addr_from_c(peer, peer_len);\n\n    match accept(&scid, odcid.as_ref(), local, peer, config) {\n        Ok(c) => Box::into_raw(Box::new(c)),\n\n        Err(_) => ptr::null_mut(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_connect(\n    server_name: *const c_char, scid: *const u8, scid_len: size_t,\n    local: &sockaddr, local_len: socklen_t, peer: &sockaddr, peer_len: socklen_t,\n    config: &mut Config,\n) -> *mut Connection {\n    let server_name = if server_name.is_null() {\n        None\n    } else {\n        Some(unsafe { ffi::CStr::from_ptr(server_name).to_str().unwrap() })\n    };\n\n    let scid = unsafe { slice::from_raw_parts(scid, scid_len) };\n    let scid = ConnectionId::from_ref(scid);\n\n    let local = std_addr_from_c(local, local_len);\n    let peer = std_addr_from_c(peer, peer_len);\n\n    match connect(server_name, &scid, local, peer, config) {\n        Ok(c) => Box::into_raw(Box::new(c)),\n\n        Err(_) => ptr::null_mut(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_negotiate_version(\n    scid: *const u8, scid_len: size_t, dcid: *const u8, dcid_len: size_t,\n    out: *mut u8, out_len: size_t,\n) -> ssize_t {\n    let scid = unsafe { slice::from_raw_parts(scid, scid_len) };\n    let scid = ConnectionId::from_ref(scid);\n\n    let dcid = unsafe { slice::from_raw_parts(dcid, dcid_len) };\n    let dcid = ConnectionId::from_ref(dcid);\n\n    let out = unsafe { slice::from_raw_parts_mut(out, out_len) };\n\n    match negotiate_version(&scid, &dcid, out) {\n        Ok(v) => v as ssize_t,\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_version_is_supported(version: u32) -> bool {\n    version_is_supported(version)\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_retry(\n    scid: *const u8, scid_len: size_t, dcid: *const u8, dcid_len: size_t,\n    new_scid: *const u8, new_scid_len: size_t, token: *const u8,\n    token_len: size_t, version: u32, out: *mut u8, out_len: size_t,\n) -> ssize_t {\n    let scid = unsafe { slice::from_raw_parts(scid, scid_len) };\n    let scid = ConnectionId::from_ref(scid);\n\n    let dcid = unsafe { slice::from_raw_parts(dcid, dcid_len) };\n    let dcid = ConnectionId::from_ref(dcid);\n\n    let new_scid = unsafe { slice::from_raw_parts(new_scid, new_scid_len) };\n    let new_scid = ConnectionId::from_ref(new_scid);\n\n    let token = unsafe { slice::from_raw_parts(token, token_len) };\n    let out = unsafe { slice::from_raw_parts_mut(out, out_len) };\n\n    match retry(&scid, &dcid, &new_scid, token, version, out) {\n        Ok(v) => v as ssize_t,\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\n#[cfg(feature = \"custom-client-dcid\")]\npub extern \"C\" fn quiche_conn_new_with_tls_and_client_dcid(\n    scid: *const u8, scid_len: size_t, dcid: *const u8, dcid_len: size_t,\n    local: &sockaddr, local_len: socklen_t, peer: &sockaddr, peer_len: socklen_t,\n    config: &Config, ssl: *mut c_void,\n) -> *mut Connection {\n    {\n        let scid = unsafe { slice::from_raw_parts(scid, scid_len) };\n        let scid = ConnectionId::from_ref(scid);\n\n        let dcid = if !dcid.is_null() && dcid_len > 0 {\n            Some(ConnectionId::from_ref(unsafe {\n                slice::from_raw_parts(dcid, dcid_len)\n            }))\n        } else {\n            None\n        };\n\n        let local = std_addr_from_c(local, local_len);\n        let peer = std_addr_from_c(peer, peer_len);\n\n        let tls = unsafe { tls::Handshake::from_ptr(ssl) };\n\n        match Connection::with_tls(\n            &scid,\n            None, // retry_cids\n            dcid.as_ref(),\n            local,\n            peer,\n            config,\n            tls,\n            false,\n        ) {\n            Ok(c) => Box::into_raw(Box::new(c)),\n\n            Err(_) => ptr::null_mut(),\n        }\n    }\n}\n\n#[no_mangle]\n#[cfg(not(feature = \"custom-client-dcid\"))]\n#[allow(unused_variables)]\npub extern \"C\" fn quiche_conn_new_with_tls_and_client_dcid(\n    scid: *const u8, scid_len: size_t, dcid: *const u8, dcid_len: size_t,\n    local: &sockaddr, local_len: socklen_t, peer: &sockaddr, peer_len: socklen_t,\n    config: &Config, ssl: *mut c_void,\n) -> *mut Connection {\n    // It's always an error to call this function without the custom-client-dcid\n    // feature enabled.\n    ptr::null_mut()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_new_with_tls(\n    scid: *const u8, scid_len: size_t, odcid: *const u8, odcid_len: size_t,\n    local: &sockaddr, local_len: socklen_t, peer: &sockaddr, peer_len: socklen_t,\n    config: &Config, ssl: *mut c_void, is_server: bool,\n) -> *mut Connection {\n    let scid = unsafe { slice::from_raw_parts(scid, scid_len) };\n    let scid = ConnectionId::from_ref(scid);\n\n    let odcid = if !odcid.is_null() && odcid_len > 0 {\n        Some(ConnectionId::from_ref(unsafe {\n            slice::from_raw_parts(odcid, odcid_len)\n        }))\n    } else {\n        None\n    };\n\n    let retry_cids = odcid.as_ref().map(|odcid| RetryConnectionIds {\n        original_destination_cid: odcid,\n        retry_source_cid: &scid,\n    });\n\n    let local = std_addr_from_c(local, local_len);\n    let peer = std_addr_from_c(peer, peer_len);\n\n    let tls = unsafe { tls::Handshake::from_ptr(ssl) };\n\n    match Connection::with_tls(\n        &scid, retry_cids, None, local, peer, config, tls, is_server,\n    ) {\n        Ok(c) => Box::into_raw(Box::new(c)),\n\n        Err(_) => ptr::null_mut(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_set_keylog_path(\n    conn: &mut Connection, path: *const c_char,\n) -> bool {\n    let filename = unsafe { ffi::CStr::from_ptr(path).to_str().unwrap() };\n\n    let file = std::fs::OpenOptions::new()\n        .create(true)\n        .append(true)\n        .open(filename);\n\n    let writer = match file {\n        Ok(f) => std::io::BufWriter::new(f),\n\n        Err(_) => return false,\n    };\n\n    conn.set_keylog(Box::new(writer));\n\n    true\n}\n\n#[no_mangle]\n#[cfg(unix)]\npub extern \"C\" fn quiche_conn_set_keylog_fd(conn: &mut Connection, fd: c_int) {\n    let f = unsafe { std::fs::File::from_raw_fd(fd) };\n    let writer = std::io::BufWriter::new(f);\n\n    conn.set_keylog(Box::new(writer));\n}\n\n#[no_mangle]\n#[cfg(feature = \"qlog\")]\npub extern \"C\" fn quiche_conn_set_qlog_path(\n    conn: &mut Connection, path: *const c_char, log_title: *const c_char,\n    log_desc: *const c_char,\n) -> bool {\n    let filename = unsafe { ffi::CStr::from_ptr(path).to_str().unwrap() };\n\n    let file = std::fs::OpenOptions::new()\n        .write(true)\n        .create_new(true)\n        .open(filename);\n\n    let writer = match file {\n        Ok(f) => std::io::BufWriter::new(f),\n\n        Err(_) => return false,\n    };\n\n    let title = unsafe { ffi::CStr::from_ptr(log_title).to_str().unwrap() };\n    let description = unsafe { ffi::CStr::from_ptr(log_desc).to_str().unwrap() };\n\n    conn.set_qlog(\n        Box::new(writer),\n        title.to_string(),\n        format!(\"{} id={}\", description, conn.trace_id),\n    );\n\n    true\n}\n\n#[no_mangle]\n#[cfg(all(unix, feature = \"qlog\"))]\npub extern \"C\" fn quiche_conn_set_qlog_fd(\n    conn: &mut Connection, fd: c_int, log_title: *const c_char,\n    log_desc: *const c_char,\n) {\n    let f = unsafe { std::fs::File::from_raw_fd(fd) };\n    let writer = std::io::BufWriter::new(f);\n\n    let title = unsafe { ffi::CStr::from_ptr(log_title).to_str().unwrap() };\n    let description = unsafe { ffi::CStr::from_ptr(log_desc).to_str().unwrap() };\n\n    conn.set_qlog(\n        Box::new(writer),\n        title.to_string(),\n        format!(\"{} id={}\", description, conn.trace_id),\n    );\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_set_session(\n    conn: &mut Connection, buf: *const u8, buf_len: size_t,\n) -> c_int {\n    let buf = unsafe { slice::from_raw_parts(buf, buf_len) };\n\n    match conn.set_session(buf) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_set_max_idle_timeout(\n    conn: &mut Connection, v: u64,\n) -> c_int {\n    match conn.set_max_idle_timeout(v) {\n        Ok(()) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[repr(C)]\npub struct RecvInfo<'a> {\n    from: &'a sockaddr,\n    from_len: socklen_t,\n    to: &'a sockaddr,\n    to_len: socklen_t,\n}\n\nimpl From<&RecvInfo<'_>> for crate::RecvInfo {\n    fn from(info: &RecvInfo) -> crate::RecvInfo {\n        crate::RecvInfo {\n            from: std_addr_from_c(info.from, info.from_len),\n            to: std_addr_from_c(info.to, info.to_len),\n        }\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_recv(\n    conn: &mut Connection, buf: *mut u8, buf_len: size_t, info: &RecvInfo,\n) -> ssize_t {\n    if buf_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };\n\n    match conn.recv(buf, info.into()) {\n        Ok(v) => v as ssize_t,\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[repr(C)]\npub struct SendInfo {\n    from: sockaddr_storage,\n    from_len: socklen_t,\n    to: sockaddr_storage,\n    to_len: socklen_t,\n\n    at: timespec,\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_send(\n    conn: &mut Connection, out: *mut u8, out_len: size_t, out_info: &mut SendInfo,\n) -> ssize_t {\n    if out_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let out = unsafe { slice::from_raw_parts_mut(out, out_len) };\n\n    match conn.send(out) {\n        Ok((v, info)) => {\n            out_info.from_len = std_addr_to_c(&info.from, &mut out_info.from);\n            out_info.to_len = std_addr_to_c(&info.to, &mut out_info.to);\n\n            std_time_to_c(&info.at, &mut out_info.at);\n\n            v as ssize_t\n        },\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_send_on_path(\n    conn: &mut Connection, out: *mut u8, out_len: size_t, from: *const sockaddr,\n    from_len: socklen_t, to: *const sockaddr, to_len: socklen_t,\n    out_info: &mut SendInfo,\n) -> ssize_t {\n    if out_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let from = optional_std_addr_from_c(from, from_len);\n    let to = optional_std_addr_from_c(to, to_len);\n    let out = unsafe { slice::from_raw_parts_mut(out, out_len) };\n\n    match conn.send_on_path(out, from, to) {\n        Ok((v, info)) => {\n            out_info.from_len = std_addr_to_c(&info.from, &mut out_info.from);\n            out_info.to_len = std_addr_to_c(&info.to, &mut out_info.to);\n\n            std_time_to_c(&info.at, &mut out_info.at);\n\n            v as ssize_t\n        },\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_recv(\n    conn: &mut Connection, stream_id: u64, out: *mut u8, out_len: size_t,\n    fin: &mut bool, out_error_code: &mut u64,\n) -> ssize_t {\n    if out_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let out = unsafe { slice::from_raw_parts_mut(out, out_len) };\n\n    let (out_len, out_fin) = match conn.stream_recv(stream_id, out) {\n        Ok(v) => v,\n\n        Err(e) => {\n            match e {\n                Error::StreamReset(error) => *out_error_code = error,\n                Error::StreamStopped(error) => *out_error_code = error,\n                _ => {},\n            }\n            return e.to_c();\n        },\n    };\n\n    *fin = out_fin;\n\n    out_len as ssize_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_send(\n    conn: &mut Connection, stream_id: u64, buf: *const u8, buf_len: size_t,\n    fin: bool, out_error_code: &mut u64,\n) -> ssize_t {\n    if buf_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let buf = if buf.is_null() {\n        assert_eq!(buf_len, 0);\n        &[]\n    } else {\n        unsafe { slice::from_raw_parts(buf, buf_len) }\n    };\n\n    match conn.stream_send(stream_id, buf, fin) {\n        Ok(v) => v as ssize_t,\n\n        Err(e) => {\n            match e {\n                Error::StreamReset(error) => *out_error_code = error,\n                Error::StreamStopped(error) => *out_error_code = error,\n                _ => {},\n            }\n            e.to_c()\n        },\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_priority(\n    conn: &mut Connection, stream_id: u64, urgency: u8, incremental: bool,\n) -> c_int {\n    match conn.stream_priority(stream_id, urgency, incremental) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_shutdown(\n    conn: &mut Connection, stream_id: u64, direction: Shutdown, err: u64,\n) -> c_int {\n    match conn.stream_shutdown(stream_id, direction, err) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_capacity(\n    conn: &mut Connection, stream_id: u64,\n) -> ssize_t {\n    match conn.stream_capacity(stream_id) {\n        Ok(v) => v as ssize_t,\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_readable(\n    conn: &Connection, stream_id: u64,\n) -> bool {\n    conn.stream_readable(stream_id)\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_readable_next(conn: &mut Connection) -> i64 {\n    conn.stream_readable_next().map(|v| v as i64).unwrap_or(-1)\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_writable(\n    conn: &mut Connection, stream_id: u64, len: usize,\n) -> c_int {\n    match conn.stream_writable(stream_id, len) {\n        Ok(true) => 1,\n\n        Ok(false) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_writable_next(conn: &mut Connection) -> i64 {\n    conn.stream_writable_next().map(|v| v as i64).unwrap_or(-1)\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stream_finished(\n    conn: &Connection, stream_id: u64,\n) -> bool {\n    conn.stream_finished(stream_id)\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_readable(conn: &Connection) -> *mut StreamIter {\n    Box::into_raw(Box::new(conn.readable()))\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_writable(conn: &Connection) -> *mut StreamIter {\n    Box::into_raw(Box::new(conn.writable()))\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_max_send_udp_payload_size(\n    conn: &Connection,\n) -> usize {\n    conn.max_send_udp_payload_size()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_readable(conn: &Connection) -> bool {\n    conn.is_readable()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_close(\n    conn: &mut Connection, app: bool, err: u64, reason: *const u8,\n    reason_len: size_t,\n) -> c_int {\n    let reason = if reason.is_null() {\n        assert_eq!(reason_len, 0);\n        &[]\n    } else {\n        unsafe { slice::from_raw_parts(reason, reason_len) }\n    };\n\n    match conn.close(app, err, reason) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_timeout_as_nanos(conn: &Connection) -> u64 {\n    match conn.timeout() {\n        Some(timeout) => timeout.as_nanos() as u64,\n\n        None => u64::MAX,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_timeout_as_millis(conn: &Connection) -> u64 {\n    match conn.timeout() {\n        Some(timeout) => timeout.as_millis() as u64,\n\n        None => u64::MAX,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_on_timeout(conn: &mut Connection) {\n    conn.on_timeout()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_trace_id(\n    conn: &Connection, out: &mut *const u8, out_len: &mut size_t,\n) {\n    let trace_id = conn.trace_id();\n\n    *out = trace_id.as_ptr();\n    *out_len = trace_id.len();\n}\n\n/// An iterator over connection ids.\n#[derive(Default)]\npub struct ConnectionIdIter<'a> {\n    cids: Vec<ConnectionId<'a>>,\n    index: usize,\n}\n\nimpl<'a> Iterator for ConnectionIdIter<'a> {\n    type Item = ConnectionId<'a>;\n\n    #[inline]\n    fn next(&mut self) -> Option<Self::Item> {\n        let v = self.cids.get(self.index)?;\n        self.index += 1;\n        Some(v.clone())\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_source_ids(\n    conn: &Connection,\n) -> *mut ConnectionIdIter<'_> {\n    let vec = conn.source_ids().cloned().collect();\n    Box::into_raw(Box::new(ConnectionIdIter {\n        cids: vec,\n        index: 0,\n    }))\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_connection_id_iter_next(\n    iter: &mut ConnectionIdIter, out: &mut *const u8, out_len: &mut size_t,\n) -> bool {\n    if let Some(conn_id) = iter.next() {\n        let id = conn_id.as_ref();\n        *out = id.as_ptr();\n        *out_len = id.len();\n        return true;\n    }\n\n    false\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_connection_id_iter_free(iter: *mut ConnectionIdIter) {\n    drop(unsafe { Box::from_raw(iter) });\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_source_id(\n    conn: &Connection, out: &mut *const u8, out_len: &mut size_t,\n) {\n    let conn_id = conn.source_id();\n    let id = conn_id.as_ref();\n    *out = id.as_ptr();\n    *out_len = id.len();\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_destination_id(\n    conn: &Connection, out: &mut *const u8, out_len: &mut size_t,\n) {\n    let conn_id = conn.destination_id();\n    let id = conn_id.as_ref();\n\n    *out = id.as_ptr();\n    *out_len = id.len();\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_application_proto(\n    conn: &Connection, out: &mut *const u8, out_len: &mut size_t,\n) {\n    let proto = conn.application_proto();\n\n    *out = proto.as_ptr();\n    *out_len = proto.len();\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_peer_cert(\n    conn: &Connection, out: &mut *const u8, out_len: &mut size_t,\n) {\n    match conn.peer_cert() {\n        Some(peer_cert) => {\n            *out = peer_cert.as_ptr();\n            *out_len = peer_cert.len();\n        },\n\n        None => *out_len = 0,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_session(\n    conn: &Connection, out: &mut *const u8, out_len: &mut size_t,\n) {\n    match conn.session() {\n        Some(session) => {\n            *out = session.as_ptr();\n            *out_len = session.len();\n        },\n\n        None => *out_len = 0,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_server_name(\n    conn: &Connection, out: &mut *const u8, out_len: &mut size_t,\n) {\n    match conn.server_name() {\n        Some(server_name) => {\n            *out = server_name.as_ptr();\n            *out_len = server_name.len();\n        },\n\n        None => *out_len = 0,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_established(conn: &Connection) -> bool {\n    conn.is_established()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_resumed(conn: &Connection) -> bool {\n    conn.is_resumed()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_in_early_data(conn: &Connection) -> bool {\n    conn.is_in_early_data()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_draining(conn: &Connection) -> bool {\n    conn.is_draining()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_closed(conn: &Connection) -> bool {\n    conn.is_closed()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_timed_out(conn: &Connection) -> bool {\n    conn.is_timed_out()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_peer_error(\n    conn: &Connection, is_app: *mut bool, error_code: *mut u64,\n    reason: &mut *const u8, reason_len: &mut size_t,\n) -> bool {\n    match &conn.peer_error {\n        Some(conn_err) => unsafe {\n            *is_app = conn_err.is_app;\n            *error_code = conn_err.error_code;\n            *reason = conn_err.reason.as_ptr();\n            *reason_len = conn_err.reason.len();\n\n            true\n        },\n\n        None => false,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_local_error(\n    conn: &Connection, is_app: *mut bool, error_code: *mut u64,\n    reason: &mut *const u8, reason_len: &mut size_t,\n) -> bool {\n    match &conn.local_error {\n        Some(conn_err) => unsafe {\n            *is_app = conn_err.is_app;\n            *error_code = conn_err.error_code;\n            *reason = conn_err.reason.as_ptr();\n            *reason_len = conn_err.reason.len();\n\n            true\n        },\n\n        None => false,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_stream_iter_next(\n    iter: &mut StreamIter, stream_id: *mut u64,\n) -> bool {\n    if let Some(v) = iter.next() {\n        unsafe { *stream_id = v };\n        return true;\n    }\n\n    false\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_stream_iter_free(iter: *mut StreamIter) {\n    drop(unsafe { Box::from_raw(iter) });\n}\n\n#[repr(C)]\npub struct Stats {\n    recv: usize,\n    sent: usize,\n    lost: usize,\n    spurious_lost: usize,\n    retrans: usize,\n    sent_bytes: u64,\n    recv_bytes: u64,\n    acked_bytes: u64,\n    lost_bytes: u64,\n    stream_retrans_bytes: u64,\n    dgram_recv: usize,\n    dgram_sent: usize,\n    paths_count: usize,\n    reset_stream_count_local: u64,\n    stopped_stream_count_local: u64,\n    reset_stream_count_remote: u64,\n    stopped_stream_count_remote: u64,\n    data_blocked_sent_count: u64,\n    stream_data_blocked_sent_count: u64,\n    data_blocked_recv_count: u64,\n    stream_data_blocked_recv_count: u64,\n    streams_blocked_bidi_recv_count: u64,\n    streams_blocked_uni_recv_count: u64,\n    path_challenge_rx_count: u64,\n    bytes_in_flight_duration_msec: u64,\n    tx_buffered_inconsistent: bool,\n}\n\npub struct TransportParams {\n    max_idle_timeout: u64,\n    max_udp_payload_size: u64,\n    initial_max_data: u64,\n    initial_max_stream_data_bidi_local: u64,\n    initial_max_stream_data_bidi_remote: u64,\n    initial_max_stream_data_uni: u64,\n    initial_max_streams_bidi: u64,\n    initial_max_streams_uni: u64,\n    ack_delay_exponent: u64,\n    max_ack_delay: u64,\n    disable_active_migration: bool,\n    active_conn_id_limit: u64,\n    max_datagram_frame_size: ssize_t,\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_stats(conn: &Connection, out: &mut Stats) {\n    let stats = conn.stats();\n\n    out.recv = stats.recv;\n    out.sent = stats.sent;\n    out.lost = stats.lost;\n    out.spurious_lost = stats.spurious_lost;\n    out.retrans = stats.retrans;\n    out.sent_bytes = stats.sent_bytes;\n    out.recv_bytes = stats.recv_bytes;\n    out.acked_bytes = stats.acked_bytes;\n    out.lost_bytes = stats.lost_bytes;\n    out.stream_retrans_bytes = stats.stream_retrans_bytes;\n    out.dgram_recv = stats.dgram_recv;\n    out.dgram_sent = stats.dgram_sent;\n    out.paths_count = stats.paths_count;\n    out.reset_stream_count_local = stats.reset_stream_count_local;\n    out.stopped_stream_count_local = stats.stopped_stream_count_local;\n    out.reset_stream_count_remote = stats.reset_stream_count_remote;\n    out.stopped_stream_count_remote = stats.stopped_stream_count_remote;\n    out.data_blocked_sent_count = stats.data_blocked_sent_count;\n    out.stream_data_blocked_sent_count = stats.stream_data_blocked_sent_count;\n    out.data_blocked_recv_count = stats.data_blocked_recv_count;\n    out.stream_data_blocked_recv_count = stats.stream_data_blocked_recv_count;\n    out.streams_blocked_bidi_recv_count = stats.streams_blocked_bidi_recv_count;\n    out.streams_blocked_uni_recv_count = stats.streams_blocked_uni_recv_count;\n    out.path_challenge_rx_count = stats.path_challenge_rx_count;\n    out.bytes_in_flight_duration_msec =\n        stats.bytes_in_flight_duration.as_millis() as u64;\n    out.tx_buffered_inconsistent =\n        stats.tx_buffered_state != TxBufferTrackingState::Ok;\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_peer_transport_params(\n    conn: &Connection, out: &mut TransportParams,\n) -> bool {\n    let tps = match conn.peer_transport_params() {\n        Some(v) => v,\n        None => return false,\n    };\n\n    out.max_idle_timeout = tps.max_idle_timeout;\n    out.max_udp_payload_size = tps.max_udp_payload_size;\n    out.initial_max_data = tps.initial_max_data;\n    out.initial_max_stream_data_bidi_local =\n        tps.initial_max_stream_data_bidi_local;\n    out.initial_max_stream_data_bidi_remote =\n        tps.initial_max_stream_data_bidi_remote;\n    out.initial_max_stream_data_uni = tps.initial_max_stream_data_uni;\n    out.initial_max_streams_bidi = tps.initial_max_streams_bidi;\n    out.initial_max_streams_uni = tps.initial_max_streams_uni;\n    out.ack_delay_exponent = tps.ack_delay_exponent;\n    out.max_ack_delay = tps.max_ack_delay;\n    out.disable_active_migration = tps.disable_active_migration;\n    out.active_conn_id_limit = tps.active_conn_id_limit;\n    out.max_datagram_frame_size = match tps.max_datagram_frame_size {\n        None => Error::Done.to_c(),\n\n        Some(v) => v as ssize_t,\n    };\n\n    true\n}\n\n#[repr(C)]\npub struct PathStats {\n    local_addr: sockaddr_storage,\n    local_addr_len: socklen_t,\n    peer_addr: sockaddr_storage,\n    peer_addr_len: socklen_t,\n    validation_state: ssize_t,\n    active: bool,\n    recv: usize,\n    sent: usize,\n    lost: usize,\n    retrans: usize,\n    total_pto_count: usize,\n    dgram_recv: usize,\n    dgram_sent: usize,\n    rtt: u64,\n    min_rtt: u64,\n    max_rtt: u64,\n    rttvar: u64,\n    cwnd: usize,\n    sent_bytes: u64,\n    recv_bytes: u64,\n    lost_bytes: u64,\n    stream_retrans_bytes: u64,\n    pmtu: usize,\n    delivery_rate: u64,\n    max_bandwidth: u64,\n    startup_exit_cwnd: u64,\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_path_stats(\n    conn: &Connection, idx: usize, out: &mut PathStats,\n) -> c_int {\n    let stats = match conn.path_stats().nth(idx) {\n        Some(p) => p,\n        None => return Error::Done.to_c() as c_int,\n    };\n\n    out.local_addr_len = std_addr_to_c(&stats.local_addr, &mut out.local_addr);\n    out.peer_addr_len = std_addr_to_c(&stats.peer_addr, &mut out.peer_addr);\n    out.validation_state = stats.validation_state.to_c();\n    out.active = stats.active;\n    out.recv = stats.recv;\n    out.sent = stats.sent;\n    out.lost = stats.lost;\n    out.retrans = stats.retrans;\n    out.total_pto_count = stats.total_pto_count;\n    out.dgram_recv = stats.dgram_recv;\n    out.dgram_sent = stats.dgram_sent;\n    out.rtt = stats.rtt.as_nanos() as u64;\n    out.min_rtt = stats.min_rtt.unwrap_or_default().as_nanos() as u64;\n    out.rttvar = stats.rttvar.as_nanos() as u64;\n    out.cwnd = stats.cwnd;\n    out.sent_bytes = stats.sent_bytes;\n    out.recv_bytes = stats.recv_bytes;\n    out.lost_bytes = stats.lost_bytes;\n    out.stream_retrans_bytes = stats.stream_retrans_bytes;\n    out.pmtu = stats.pmtu;\n    out.delivery_rate = stats.delivery_rate;\n    out.max_bandwidth = stats.max_bandwidth.unwrap_or(0);\n    out.startup_exit_cwnd =\n        stats.startup_exit.map(|s| s.cwnd as u64).unwrap_or(0);\n\n    0\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_server(conn: &Connection) -> bool {\n    conn.is_server()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_max_writable_len(\n    conn: &Connection,\n) -> ssize_t {\n    match conn.dgram_max_writable_len() {\n        None => Error::Done.to_c(),\n\n        Some(v) => v as ssize_t,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_recv_front_len(conn: &Connection) -> ssize_t {\n    match conn.dgram_recv_front_len() {\n        None => Error::Done.to_c(),\n\n        Some(v) => v as ssize_t,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_recv_queue_len(conn: &Connection) -> ssize_t {\n    conn.dgram_recv_queue_len() as ssize_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_recv_queue_byte_size(\n    conn: &Connection,\n) -> ssize_t {\n    conn.dgram_recv_queue_byte_size() as ssize_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_send_queue_len(conn: &Connection) -> ssize_t {\n    conn.dgram_send_queue_len() as ssize_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_send_queue_byte_size(\n    conn: &Connection,\n) -> ssize_t {\n    conn.dgram_send_queue_byte_size() as ssize_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_send(\n    conn: &mut Connection, buf: *const u8, buf_len: size_t,\n) -> ssize_t {\n    if buf_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let buf = unsafe { slice::from_raw_parts(buf, buf_len) };\n\n    match conn.dgram_send(buf) {\n        Ok(_) => buf_len as ssize_t,\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_recv(\n    conn: &mut Connection, out: *mut u8, out_len: size_t,\n) -> ssize_t {\n    if out_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let out = unsafe { slice::from_raw_parts_mut(out, out_len) };\n\n    let out_len = match conn.dgram_recv(out) {\n        Ok(v) => v,\n\n        Err(e) => return e.to_c(),\n    };\n\n    out_len as ssize_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_dgram_purge_outgoing(\n    conn: &mut Connection, f: extern \"C\" fn(*const u8, size_t) -> bool,\n) {\n    conn.dgram_purge_outgoing(|d: &[u8]| -> bool {\n        let ptr: *const u8 = d.as_ptr();\n        let len: size_t = d.len();\n\n        f(ptr, len)\n    });\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_dgram_send_queue_full(\n    conn: &Connection,\n) -> bool {\n    conn.is_dgram_send_queue_full()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_dgram_recv_queue_full(\n    conn: &Connection,\n) -> bool {\n    conn.is_dgram_recv_queue_full()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_send_ack_eliciting(\n    conn: &mut Connection,\n) -> ssize_t {\n    match conn.send_ack_eliciting() {\n        Ok(()) => 0,\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_send_ack_eliciting_on_path(\n    conn: &mut Connection, local: &sockaddr, local_len: socklen_t,\n    peer: &sockaddr, peer_len: socklen_t,\n) -> ssize_t {\n    let local = std_addr_from_c(local, local_len);\n    let peer = std_addr_from_c(peer, peer_len);\n    match conn.send_ack_eliciting_on_path(local, peer) {\n        Ok(()) => 0,\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_free(conn: *mut Connection) {\n    drop(unsafe { Box::from_raw(conn) });\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_peer_streams_left_bidi(conn: &Connection) -> u64 {\n    conn.peer_streams_left_bidi()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_peer_streams_left_uni(conn: &Connection) -> u64 {\n    conn.peer_streams_left_uni()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_send_quantum(conn: &Connection) -> size_t {\n    conn.send_quantum() as size_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_active_scids(conn: &Connection) -> size_t {\n    conn.active_scids() as size_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_scids_left(conn: &Connection) -> size_t {\n    conn.scids_left() as size_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_new_scid(\n    conn: &mut Connection, scid: *const u8, scid_len: size_t,\n    reset_token: *const u8, retire_if_needed: bool, scid_seq: *mut u64,\n) -> c_int {\n    let scid = unsafe { slice::from_raw_parts(scid, scid_len) };\n    let scid = ConnectionId::from_ref(scid);\n\n    let reset_token = unsafe { slice::from_raw_parts(reset_token, 16) };\n    let reset_token = match reset_token.try_into() {\n        Ok(rt) => rt,\n        Err(_) => unreachable!(),\n    };\n    let reset_token = u128::from_be_bytes(reset_token);\n\n    match conn.new_scid(&scid, reset_token, retire_if_needed) {\n        Ok(c) => {\n            unsafe { *scid_seq = c }\n            0\n        },\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_retire_dcid(\n    conn: &mut Connection, dcid_seq: u64,\n) -> c_int {\n    match conn.retire_dcid(dcid_seq) {\n        Ok(_) => 0,\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_available_dcids(conn: &Connection) -> size_t {\n    conn.available_dcids() as size_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_retired_scids(conn: &Connection) -> size_t {\n    conn.retired_scids() as size_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_retired_scid_next(\n    conn: &mut Connection, out: &mut *const u8, out_len: &mut size_t,\n) -> bool {\n    match conn.retired_scid_next() {\n        None => false,\n\n        Some(conn_id) => {\n            let id = conn_id.as_ref();\n            *out = id.as_ptr();\n            *out_len = id.len();\n            true\n        },\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_send_quantum_on_path(\n    conn: &Connection, local: &sockaddr, local_len: socklen_t, peer: &sockaddr,\n    peer_len: socklen_t,\n) -> size_t {\n    let local = std_addr_from_c(local, local_len);\n    let peer = std_addr_from_c(peer, peer_len);\n\n    conn.send_quantum_on_path(local, peer) as size_t\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_paths_iter(\n    conn: &Connection, from: &sockaddr, from_len: socklen_t,\n) -> *mut SocketAddrIter {\n    let addr = std_addr_from_c(from, from_len);\n\n    Box::into_raw(Box::new(conn.paths_iter(addr)))\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_socket_addr_iter_next(\n    iter: &mut SocketAddrIter, peer: &mut sockaddr_storage,\n    peer_len: *mut socklen_t,\n) -> bool {\n    if let Some(v) = iter.next() {\n        unsafe { *peer_len = std_addr_to_c(&v, peer) }\n        return true;\n    }\n\n    false\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_socket_addr_iter_free(iter: *mut SocketAddrIter) {\n    drop(unsafe { Box::from_raw(iter) });\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_is_path_validated(\n    conn: &Connection, from: &sockaddr, from_len: socklen_t, to: &sockaddr,\n    to_len: socklen_t,\n) -> c_int {\n    let from = std_addr_from_c(from, from_len);\n    let to = std_addr_from_c(to, to_len);\n    match conn.is_path_validated(from, to) {\n        Ok(v) => v as c_int,\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_probe_path(\n    conn: &mut Connection, local: &sockaddr, local_len: socklen_t,\n    peer: &sockaddr, peer_len: socklen_t, seq: *mut u64,\n) -> c_int {\n    let local = std_addr_from_c(local, local_len);\n    let peer = std_addr_from_c(peer, peer_len);\n    match conn.probe_path(local, peer) {\n        Ok(v) => {\n            unsafe { *seq = v }\n            0\n        },\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_migrate_source(\n    conn: &mut Connection, local: &sockaddr, local_len: socklen_t, seq: *mut u64,\n) -> c_int {\n    let local = std_addr_from_c(local, local_len);\n    match conn.migrate_source(local) {\n        Ok(v) => {\n            unsafe { *seq = v }\n            0\n        },\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_migrate(\n    conn: &mut Connection, local: &sockaddr, local_len: socklen_t,\n    peer: &sockaddr, peer_len: socklen_t, seq: *mut u64,\n) -> c_int {\n    let local = std_addr_from_c(local, local_len);\n    let peer = std_addr_from_c(peer, peer_len);\n    match conn.migrate(local, peer) {\n        Ok(v) => {\n            unsafe { *seq = v }\n            0\n        },\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_conn_path_event_next(\n    conn: &mut Connection,\n) -> *mut PathEvent {\n    match conn.path_event_next() {\n        Some(v) => Box::into_raw(Box::new(v)),\n        None => ptr::null_mut(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_path_event_type(ev: &PathEvent) -> u32 {\n    match ev {\n        PathEvent::New { .. } => 0,\n\n        PathEvent::Validated { .. } => 1,\n\n        PathEvent::FailedValidation { .. } => 2,\n\n        PathEvent::Closed { .. } => 3,\n\n        PathEvent::ReusedSourceConnectionId { .. } => 4,\n\n        PathEvent::PeerMigrated { .. } => 5,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_path_event_new(\n    ev: &PathEvent, local_addr: &mut sockaddr_storage,\n    local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage,\n    peer_addr_len: &mut socklen_t,\n) {\n    match ev {\n        PathEvent::New(local, peer) => {\n            *local_addr_len = std_addr_to_c(local, local_addr);\n            *peer_addr_len = std_addr_to_c(peer, peer_addr)\n        },\n\n        _ => unreachable!(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_path_event_validated(\n    ev: &PathEvent, local_addr: &mut sockaddr_storage,\n    local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage,\n    peer_addr_len: &mut socklen_t,\n) {\n    match ev {\n        PathEvent::Validated(local, peer) => {\n            *local_addr_len = std_addr_to_c(local, local_addr);\n            *peer_addr_len = std_addr_to_c(peer, peer_addr)\n        },\n\n        _ => unreachable!(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_path_event_failed_validation(\n    ev: &PathEvent, local_addr: &mut sockaddr_storage,\n    local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage,\n    peer_addr_len: &mut socklen_t,\n) {\n    match ev {\n        PathEvent::FailedValidation(local, peer) => {\n            *local_addr_len = std_addr_to_c(local, local_addr);\n            *peer_addr_len = std_addr_to_c(peer, peer_addr)\n        },\n\n        _ => unreachable!(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_path_event_closed(\n    ev: &PathEvent, local_addr: &mut sockaddr_storage,\n    local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage,\n    peer_addr_len: &mut socklen_t,\n) {\n    match ev {\n        PathEvent::Closed(local, peer) => {\n            *local_addr_len = std_addr_to_c(local, local_addr);\n            *peer_addr_len = std_addr_to_c(peer, peer_addr)\n        },\n\n        _ => unreachable!(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_path_event_reused_source_connection_id(\n    ev: &PathEvent, cid_sequence_number: &mut u64,\n    old_local_addr: &mut sockaddr_storage, old_local_addr_len: &mut socklen_t,\n    old_peer_addr: &mut sockaddr_storage, old_peer_addr_len: &mut socklen_t,\n    local_addr: &mut sockaddr_storage, local_addr_len: &mut socklen_t,\n    peer_addr: &mut sockaddr_storage, peer_addr_len: &mut socklen_t,\n) {\n    match ev {\n        PathEvent::ReusedSourceConnectionId(id, old, new) => {\n            *cid_sequence_number = *id;\n            *old_local_addr_len = std_addr_to_c(&old.0, old_local_addr);\n            *old_peer_addr_len = std_addr_to_c(&old.1, old_peer_addr);\n\n            *local_addr_len = std_addr_to_c(&new.0, local_addr);\n            *peer_addr_len = std_addr_to_c(&new.1, peer_addr)\n        },\n\n        _ => unreachable!(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_path_event_peer_migrated(\n    ev: &PathEvent, local_addr: &mut sockaddr_storage,\n    local_addr_len: &mut socklen_t, peer_addr: &mut sockaddr_storage,\n    peer_addr_len: &mut socklen_t,\n) {\n    match ev {\n        PathEvent::PeerMigrated(local, peer) => {\n            *local_addr_len = std_addr_to_c(local, local_addr);\n            *peer_addr_len = std_addr_to_c(peer, peer_addr);\n        },\n\n        _ => unreachable!(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_path_event_free(ev: *mut PathEvent) {\n    drop(unsafe { Box::from_raw(ev) });\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_put_varint(\n    buf: *mut u8, buf_len: size_t, val: u64,\n) -> c_int {\n    let buf = unsafe { slice::from_raw_parts_mut(buf, buf_len) };\n\n    let mut b = octets::OctetsMut::with_slice(buf);\n    match b.put_varint(val) {\n        Ok(_) => 0,\n\n        Err(e) => {\n            let err: Error = e.into();\n            err.to_c() as c_int\n        },\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_get_varint(\n    buf: *const u8, buf_len: size_t, val: *mut u64,\n) -> ssize_t {\n    let buf = unsafe { slice::from_raw_parts(buf, buf_len) };\n\n    let mut b = octets::Octets::with_slice(buf);\n    match b.get_varint() {\n        Ok(v) => unsafe { *val = v },\n\n        Err(e) => {\n            let err: Error = e.into();\n            return err.to_c();\n        },\n    };\n\n    b.off() as ssize_t\n}\n\nfn optional_std_addr_from_c(\n    addr: *const sockaddr, addr_len: socklen_t,\n) -> Option<SocketAddr> {\n    if addr.is_null() || addr_len == 0 {\n        return None;\n    }\n\n    Some({\n        let addr = unsafe { slice::from_raw_parts(addr, addr_len as usize) };\n        std_addr_from_c(addr.first().unwrap(), addr_len)\n    })\n}\n\nfn std_addr_from_c(addr: &sockaddr, addr_len: socklen_t) -> SocketAddr {\n    match addr.sa_family as _ {\n        AF_INET => {\n            assert!(addr_len as usize == size_of::<sockaddr_in>());\n\n            let in4 = unsafe { *(addr as *const _ as *const sockaddr_in) };\n\n            #[cfg(not(windows))]\n            let ip_addr = Ipv4Addr::from(u32::from_be(in4.sin_addr.s_addr));\n            #[cfg(windows)]\n            let ip_addr = {\n                let ip_bytes = unsafe { in4.sin_addr.S_un.S_un_b };\n\n                Ipv4Addr::from([\n                    ip_bytes.s_b1,\n                    ip_bytes.s_b2,\n                    ip_bytes.s_b3,\n                    ip_bytes.s_b4,\n                ])\n            };\n\n            let port = u16::from_be(in4.sin_port);\n\n            let out = SocketAddrV4::new(ip_addr, port);\n\n            out.into()\n        },\n\n        AF_INET6 => {\n            assert!(addr_len as usize == size_of::<sockaddr_in6>());\n\n            let in6 = unsafe { *(addr as *const _ as *const sockaddr_in6) };\n\n            let ip_addr = Ipv6Addr::from(\n                #[cfg(not(windows))]\n                in6.sin6_addr.s6_addr,\n                #[cfg(windows)]\n                unsafe {\n                    in6.sin6_addr.u.Byte\n                },\n            );\n\n            let port = u16::from_be(in6.sin6_port);\n\n            #[cfg(not(windows))]\n            let scope_id = in6.sin6_scope_id;\n            #[cfg(windows)]\n            let scope_id = unsafe { in6.Anonymous.sin6_scope_id };\n\n            let out =\n                SocketAddrV6::new(ip_addr, port, in6.sin6_flowinfo, scope_id);\n\n            out.into()\n        },\n\n        _ => unimplemented!(\"unsupported address type\"),\n    }\n}\n\nfn std_addr_to_c(addr: &SocketAddr, out: &mut sockaddr_storage) -> socklen_t {\n    let sin_port = addr.port().to_be();\n\n    match addr {\n        SocketAddr::V4(addr) => unsafe {\n            let sa_len = size_of::<sockaddr_in>();\n            let out_in = out as *mut _ as *mut sockaddr_in;\n\n            let s_addr = u32::from_ne_bytes(addr.ip().octets());\n\n            #[cfg(not(windows))]\n            let sin_addr = in_addr { s_addr };\n            #[cfg(windows)]\n            let sin_addr = in_addr {\n                S_un: IN_ADDR_0 { S_addr: s_addr },\n            };\n\n            *out_in = sockaddr_in {\n                sin_family: AF_INET as sa_family_t,\n\n                sin_addr,\n\n                #[cfg(any(\n                    target_os = \"macos\",\n                    target_os = \"ios\",\n                    target_os = \"watchos\",\n                    target_os = \"freebsd\",\n                    target_os = \"dragonfly\",\n                    target_os = \"openbsd\",\n                    target_os = \"netbsd\"\n                ))]\n                sin_len: sa_len as u8,\n\n                sin_port,\n\n                sin_zero: std::mem::zeroed(),\n            };\n\n            sa_len as socklen_t\n        },\n\n        SocketAddr::V6(addr) => unsafe {\n            let sa_len = size_of::<sockaddr_in6>();\n            let out_in6 = out as *mut _ as *mut sockaddr_in6;\n\n            #[cfg(not(windows))]\n            let sin6_addr = in6_addr {\n                s6_addr: addr.ip().octets(),\n            };\n            #[cfg(windows)]\n            let sin6_addr = in6_addr {\n                u: IN6_ADDR_0 {\n                    Byte: addr.ip().octets(),\n                },\n            };\n\n            *out_in6 = sockaddr_in6 {\n                sin6_family: AF_INET6 as sa_family_t,\n\n                sin6_addr,\n\n                #[cfg(any(\n                    target_os = \"macos\",\n                    target_os = \"ios\",\n                    target_os = \"watchos\",\n                    target_os = \"freebsd\",\n                    target_os = \"dragonfly\",\n                    target_os = \"openbsd\",\n                    target_os = \"netbsd\"\n                ))]\n                sin6_len: sa_len as u8,\n\n                sin6_port: sin_port,\n\n                sin6_flowinfo: addr.flowinfo(),\n\n                #[cfg(not(windows))]\n                sin6_scope_id: addr.scope_id(),\n                #[cfg(windows)]\n                Anonymous: SOCKADDR_IN6_0 {\n                    sin6_scope_id: addr.scope_id(),\n                },\n            };\n\n            sa_len as socklen_t\n        },\n    }\n}\n\n#[cfg(not(any(target_os = \"macos\", target_os = \"ios\", target_os = \"windows\")))]\nfn std_time_to_c(time: &Instant, out: &mut timespec) {\n    const INSTANT_ZERO: Instant =\n        unsafe { std::mem::transmute(std::time::UNIX_EPOCH) };\n\n    let raw_time = time.duration_since(INSTANT_ZERO);\n\n    out.tv_sec = raw_time.as_secs() as libc::time_t;\n    out.tv_nsec = raw_time.subsec_nanos() as libc::c_long;\n}\n\n#[cfg(any(target_os = \"macos\", target_os = \"ios\", target_os = \"windows\"))]\nfn std_time_to_c(_time: &Instant, out: &mut timespec) {\n    // TODO: implement Instant conversion for systems that don't use timespec.\n    out.tv_sec = 0;\n    out.tv_nsec = 0;\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use libc::c_void;\n    #[cfg(windows)]\n    use windows_sys::Win32::Networking::WinSock::inet_ntop;\n\n    #[test]\n    fn addr_v4() {\n        let addr = \"127.0.0.1:8080\".parse().unwrap();\n\n        let mut out: sockaddr_storage = unsafe { std::mem::zeroed() };\n\n        assert_eq!(\n            std_addr_to_c(&addr, &mut out),\n            size_of::<sockaddr_in>() as socklen_t\n        );\n\n        let s = ffi::CString::new(\"ddd.ddd.ddd.ddd\").unwrap();\n\n        let s = unsafe {\n            let in_addr = &out as *const _ as *const sockaddr_in;\n            assert_eq!(u16::from_be((*in_addr).sin_port), addr.port());\n\n            let dst = s.into_raw();\n\n            inet_ntop(\n                AF_INET as _,\n                &((*in_addr).sin_addr) as *const _ as *const c_void,\n                dst as _,\n                16,\n            );\n\n            ffi::CString::from_raw(dst).into_string().unwrap()\n        };\n\n        assert_eq!(s, \"127.0.0.1\");\n\n        let addr = unsafe {\n            std_addr_from_c(\n                &*(&out as *const _ as *const sockaddr),\n                size_of::<sockaddr_in>() as socklen_t,\n            )\n        };\n\n        assert_eq!(addr, \"127.0.0.1:8080\".parse().unwrap());\n    }\n\n    #[test]\n    fn addr_v6() {\n        let addr = \"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080\"\n            .parse()\n            .unwrap();\n\n        let mut out: sockaddr_storage = unsafe { std::mem::zeroed() };\n\n        assert_eq!(\n            std_addr_to_c(&addr, &mut out),\n            size_of::<sockaddr_in6>() as socklen_t\n        );\n\n        let s =\n            ffi::CString::new(\"dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd\").unwrap();\n\n        let s = unsafe {\n            let in6_addr = &out as *const _ as *const sockaddr_in6;\n            assert_eq!(u16::from_be((*in6_addr).sin6_port), addr.port());\n\n            let dst = s.into_raw();\n\n            inet_ntop(\n                AF_INET6 as _,\n                &((*in6_addr).sin6_addr) as *const _ as *const c_void,\n                dst as _,\n                45,\n            );\n\n            ffi::CString::from_raw(dst).into_string().unwrap()\n        };\n\n        assert_eq!(s, \"2001:db8:85a3::8a2e:370:7334\");\n\n        let addr = unsafe {\n            std_addr_from_c(\n                &*(&out as *const _ as *const sockaddr),\n                size_of::<sockaddr_in6>() as socklen_t,\n            )\n        };\n\n        assert_eq!(\n            addr,\n            \"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080\"\n                .parse()\n                .unwrap()\n        );\n    }\n\n    #[cfg(not(windows))]\n    extern \"C\" {\n        fn inet_ntop(\n            af: c_int, src: *const c_void, dst: *mut c_char, size: socklen_t,\n        ) -> *mut c_char;\n    }\n}\n"
  },
  {
    "path": "quiche/src/flowcontrol.rs",
    "content": "// Copyright (C) 2021, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Duration;\nuse std::time::Instant;\n\n// When autotuning the receiver window, decide how much\n// we increase the window.\nconst WINDOW_INCREASE_FACTOR: u64 = 2;\n\n// When autotuning the receiver window, check if the last\n// update is within RTT * this constant.\nconst WINDOW_TRIGGER_FACTOR: u32 = 2;\n\n#[derive(Default, Debug)]\npub struct FlowControl {\n    /// Total consumed bytes by the receiver.\n    consumed: u64,\n\n    /// Flow control limit.\n    max_data: u64,\n\n    /// The receive window. This value is used for updating\n    /// flow control limit.\n    window: u64,\n\n    /// The maximum receive window.\n    max_window: u64,\n\n    /// Last update time of max_data for autotuning the window.\n    last_update: Option<Instant>,\n}\n\nimpl FlowControl {\n    pub fn new(max_data: u64, window: u64, max_window: u64) -> Self {\n        Self {\n            max_data,\n\n            window,\n\n            max_window,\n\n            ..Default::default()\n        }\n    }\n\n    /// Returns the current window size.\n    pub fn window(&self) -> u64 {\n        self.window\n    }\n\n    /// Returns the current flow limit.\n    pub fn max_data(&self) -> u64 {\n        self.max_data\n    }\n\n    /// Returns the consumed bytes by the receiver.\n    #[cfg(test)]\n    pub fn consumed(&self) -> u64 {\n        self.consumed\n    }\n\n    /// Update consumed bytes.\n    pub fn add_consumed(&mut self, consumed: u64) {\n        self.consumed += consumed;\n    }\n\n    /// Returns true if the flow control needs to update max_data.\n    ///\n    /// This happens when the available window is smaller than the half\n    /// of the current window.\n    pub fn should_update_max_data(&self) -> bool {\n        let available_window = self.max_data - self.consumed;\n\n        available_window < (self.window / 2)\n    }\n\n    /// Returns the new max_data limit.\n    pub fn max_data_next(&self) -> u64 {\n        self.consumed + self.window\n    }\n\n    /// Commits the new max_data limit.\n    pub fn update_max_data(&mut self, now: Instant) {\n        self.max_data = self.max_data_next();\n        self.last_update = Some(now);\n    }\n\n    /// Autotune the window size. When there is an another update\n    /// within RTT x 2, bump the window x 2, capped by\n    /// max_window.\n    pub fn autotune_window(&mut self, now: Instant, rtt: Duration) {\n        if let Some(last_update) = self.last_update {\n            if now - last_update < rtt * WINDOW_TRIGGER_FACTOR {\n                self.window = std::cmp::min(\n                    self.window * WINDOW_INCREASE_FACTOR,\n                    self.max_window,\n                );\n            }\n        }\n    }\n\n    /// Make sure the lower bound of the window is same to\n    /// the current window.\n    pub fn ensure_window_lower_bound(&mut self, min_window: u64) {\n        if min_window > self.window {\n            // ... we still need to clamp to `max_window`\n            self.window = std::cmp::min(min_window, self.max_window);\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn max_data() {\n        let fc = FlowControl::new(100, 20, 100);\n\n        assert_eq!(fc.max_data(), 100);\n    }\n\n    #[test]\n    fn should_update_max_data() {\n        let mut fc = FlowControl::new(100, 20, 100);\n\n        fc.add_consumed(85);\n        assert!(!fc.should_update_max_data());\n\n        fc.add_consumed(10);\n        assert!(fc.should_update_max_data());\n    }\n\n    #[test]\n    fn max_data_next() {\n        let mut fc = FlowControl::new(100, 20, 100);\n\n        let consumed = 95;\n\n        fc.add_consumed(consumed);\n        assert!(fc.should_update_max_data());\n        assert_eq!(fc.max_data_next(), consumed + 20);\n    }\n\n    #[test]\n    fn update_max_data() {\n        let mut fc = FlowControl::new(100, 20, 100);\n\n        let consumed = 95;\n\n        fc.add_consumed(consumed);\n        assert!(fc.should_update_max_data());\n\n        let max_data_next = fc.max_data_next();\n        assert_eq!(fc.max_data_next(), consumed + 20);\n\n        fc.update_max_data(Instant::now());\n        assert_eq!(fc.max_data(), max_data_next);\n    }\n\n    #[test]\n    fn autotune_window() {\n        let w = 20;\n        let mut fc = FlowControl::new(100, w, 100);\n\n        let consumed = 95;\n\n        fc.add_consumed(consumed);\n        assert!(fc.should_update_max_data());\n\n        let max_data_next = fc.max_data_next();\n        assert_eq!(max_data_next, consumed + w);\n\n        fc.update_max_data(Instant::now());\n        assert_eq!(fc.max_data(), max_data_next);\n\n        // Window size should be doubled.\n        fc.autotune_window(Instant::now(), Duration::from_millis(100));\n\n        let w = w * 2;\n        let consumed_inc = 15;\n\n        fc.add_consumed(consumed_inc);\n        assert!(fc.should_update_max_data());\n\n        let max_data_next = fc.max_data_next();\n        assert_eq!(max_data_next, consumed + consumed_inc + w);\n    }\n\n    #[test]\n    fn ensure_window_lower_bound() {\n        let w = 20;\n        let mut fc = FlowControl::new(100, w, 100);\n\n        // Window doesn't change.\n        fc.ensure_window_lower_bound(w);\n        assert_eq!(fc.window(), 20);\n\n        // Window changed to the new value.\n        fc.ensure_window_lower_bound(w * 2);\n        assert_eq!(fc.window(), 40);\n    }\n}\n"
  },
  {
    "path": "quiche/src/frame.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::convert::TryInto;\n\nuse crate::Error;\nuse crate::Result;\n\nuse crate::packet;\nuse crate::range_buf::RangeBuf;\nuse crate::ranges;\n\n#[cfg(feature = \"qlog\")]\nuse qlog::events::quic::AckedRanges;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::quic::ErrorSpace;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::quic::QuicFrame;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::quic::StreamType;\n\npub const MAX_CRYPTO_OVERHEAD: usize = 8;\npub const MAX_DGRAM_OVERHEAD: usize = 2;\npub const MAX_STREAM_OVERHEAD: usize = 12;\npub const MAX_STREAM_SIZE: u64 = 1 << 62;\n\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct EcnCounts {\n    ect0_count: u64,\n    ect1_count: u64,\n    ecn_ce_count: u64,\n}\n\n#[derive(Clone, PartialEq, Eq)]\npub enum Frame {\n    Padding {\n        len: usize,\n    },\n\n    Ping {\n        // Attach metadata to the Ping frame. This doesn't appear on the wire,\n        // but does tell us if this frame was part of a PMTUD probe and how\n        // large the probe was. This will only show up on sent frames and be\n        // None otherwise. This is the total size of the QUIC packet in the\n        // probe.\n        mtu_probe: Option<usize>,\n    },\n\n    ACK {\n        ack_delay: u64,\n        ranges: ranges::RangeSet,\n        ecn_counts: Option<EcnCounts>,\n    },\n\n    ResetStream {\n        stream_id: u64,\n        error_code: u64,\n        final_size: u64,\n    },\n\n    StopSending {\n        stream_id: u64,\n        error_code: u64,\n    },\n\n    Crypto {\n        data: RangeBuf,\n    },\n\n    CryptoHeader {\n        offset: u64,\n        length: usize,\n    },\n\n    NewToken {\n        token: Vec<u8>,\n    },\n\n    Stream {\n        stream_id: u64,\n        data: RangeBuf,\n    },\n\n    StreamHeader {\n        stream_id: u64,\n        offset: u64,\n        length: usize,\n        fin: bool,\n    },\n\n    MaxData {\n        max: u64,\n    },\n\n    MaxStreamData {\n        stream_id: u64,\n        max: u64,\n    },\n\n    MaxStreamsBidi {\n        max: u64,\n    },\n\n    MaxStreamsUni {\n        max: u64,\n    },\n\n    DataBlocked {\n        limit: u64,\n    },\n\n    StreamDataBlocked {\n        stream_id: u64,\n        limit: u64,\n    },\n\n    StreamsBlockedBidi {\n        limit: u64,\n    },\n\n    StreamsBlockedUni {\n        limit: u64,\n    },\n\n    NewConnectionId {\n        seq_num: u64,\n        retire_prior_to: u64,\n        conn_id: Vec<u8>,\n        reset_token: [u8; 16],\n    },\n\n    RetireConnectionId {\n        seq_num: u64,\n    },\n\n    PathChallenge {\n        data: [u8; 8],\n    },\n\n    PathResponse {\n        data: [u8; 8],\n    },\n\n    ConnectionClose {\n        error_code: u64,\n        frame_type: u64,\n        reason: Vec<u8>,\n    },\n\n    ApplicationClose {\n        error_code: u64,\n        reason: Vec<u8>,\n    },\n\n    HandshakeDone,\n\n    Datagram {\n        data: Vec<u8>,\n    },\n\n    DatagramHeader {\n        length: usize,\n    },\n}\n\nimpl Frame {\n    pub fn from_bytes(\n        b: &mut octets::Octets, pkt: packet::Type,\n    ) -> Result<Frame> {\n        let frame_type = b.get_varint()?;\n\n        let frame = match frame_type {\n            0x00 => {\n                let mut len = 1;\n\n                while b.peek_u8() == Ok(0x00) {\n                    b.get_u8()?;\n\n                    len += 1;\n                }\n\n                Frame::Padding { len }\n            },\n\n            0x01 => Frame::Ping { mtu_probe: None },\n\n            0x02..=0x03 => parse_ack_frame(frame_type, b)?,\n\n            0x04 => Frame::ResetStream {\n                stream_id: b.get_varint()?,\n                error_code: b.get_varint()?,\n                final_size: b.get_varint()?,\n            },\n\n            0x05 => Frame::StopSending {\n                stream_id: b.get_varint()?,\n                error_code: b.get_varint()?,\n            },\n\n            0x06 => {\n                let offset = b.get_varint()?;\n                let data = b.get_bytes_with_varint_length()?;\n                let data = <RangeBuf>::from(data.as_ref(), offset, false);\n\n                Frame::Crypto { data }\n            },\n\n            0x07 => {\n                let len = b.get_varint()?;\n                if len == 0 {\n                    return Err(Error::InvalidFrame);\n                }\n\n                Frame::NewToken {\n                    token: b.get_bytes(len as usize)?.to_vec(),\n                }\n            },\n\n            0x08..=0x0f => parse_stream_frame(frame_type, b)?,\n\n            0x10 => Frame::MaxData {\n                max: b.get_varint()?,\n            },\n\n            0x11 => Frame::MaxStreamData {\n                stream_id: b.get_varint()?,\n                max: b.get_varint()?,\n            },\n\n            0x12 => Frame::MaxStreamsBidi {\n                max: b.get_varint()?,\n            },\n\n            0x13 => Frame::MaxStreamsUni {\n                max: b.get_varint()?,\n            },\n\n            0x14 => Frame::DataBlocked {\n                limit: b.get_varint()?,\n            },\n\n            0x15 => Frame::StreamDataBlocked {\n                stream_id: b.get_varint()?,\n                limit: b.get_varint()?,\n            },\n\n            0x16 => Frame::StreamsBlockedBidi {\n                limit: b.get_varint()?,\n            },\n\n            0x17 => Frame::StreamsBlockedUni {\n                limit: b.get_varint()?,\n            },\n\n            0x18 => {\n                let seq_num = b.get_varint()?;\n                let retire_prior_to = b.get_varint()?;\n                let conn_id_len = b.get_u8()?;\n\n                if !(1..=packet::MAX_CID_LEN).contains(&conn_id_len) {\n                    return Err(Error::InvalidFrame);\n                }\n\n                Frame::NewConnectionId {\n                    seq_num,\n                    retire_prior_to,\n                    conn_id: b.get_bytes(conn_id_len as usize)?.to_vec(),\n                    reset_token: b\n                        .get_bytes(16)?\n                        .buf()\n                        .try_into()\n                        .map_err(|_| Error::BufferTooShort)?,\n                }\n            },\n\n            0x19 => Frame::RetireConnectionId {\n                seq_num: b.get_varint()?,\n            },\n\n            0x1a => Frame::PathChallenge {\n                data: b\n                    .get_bytes(8)?\n                    .buf()\n                    .try_into()\n                    .map_err(|_| Error::BufferTooShort)?,\n            },\n\n            0x1b => Frame::PathResponse {\n                data: b\n                    .get_bytes(8)?\n                    .buf()\n                    .try_into()\n                    .map_err(|_| Error::BufferTooShort)?,\n            },\n\n            0x1c => Frame::ConnectionClose {\n                error_code: b.get_varint()?,\n                frame_type: b.get_varint()?,\n                reason: b.get_bytes_with_varint_length()?.to_vec(),\n            },\n\n            0x1d => Frame::ApplicationClose {\n                error_code: b.get_varint()?,\n                reason: b.get_bytes_with_varint_length()?.to_vec(),\n            },\n\n            0x1e => Frame::HandshakeDone,\n\n            0x30 | 0x31 => parse_datagram_frame(frame_type, b)?,\n\n            _ => return Err(Error::InvalidFrame),\n        };\n\n        let allowed = match (pkt, &frame) {\n            // PADDING and PING are allowed on all packet types.\n            (_, Frame::Padding { .. }) | (_, Frame::Ping { .. }) => true,\n\n            // ACK, CRYPTO, HANDSHAKE_DONE, NEW_TOKEN, PATH_RESPONSE, and\n            // RETIRE_CONNECTION_ID can't be sent on 0-RTT packets.\n            (packet::Type::ZeroRTT, Frame::ACK { .. }) => false,\n            (packet::Type::ZeroRTT, Frame::Crypto { .. }) => false,\n            (packet::Type::ZeroRTT, Frame::HandshakeDone) => false,\n            (packet::Type::ZeroRTT, Frame::NewToken { .. }) => false,\n            (packet::Type::ZeroRTT, Frame::PathResponse { .. }) => false,\n            (packet::Type::ZeroRTT, Frame::RetireConnectionId { .. }) => false,\n            (packet::Type::ZeroRTT, Frame::ConnectionClose { .. }) => false,\n\n            // ACK, CRYPTO and CONNECTION_CLOSE can be sent on all other packet\n            // types.\n            (_, Frame::ACK { .. }) => true,\n            (_, Frame::Crypto { .. }) => true,\n            (_, Frame::ConnectionClose { .. }) => true,\n\n            // All frames are allowed on 0-RTT and 1-RTT packets.\n            (packet::Type::Short, _) => true,\n            (packet::Type::ZeroRTT, _) => true,\n\n            // All other cases are forbidden.\n            (..) => false,\n        };\n\n        if !allowed {\n            return Err(Error::InvalidPacket);\n        }\n\n        Ok(frame)\n    }\n\n    pub fn to_bytes(&self, b: &mut octets::OctetsMut) -> Result<usize> {\n        let before = b.cap();\n\n        match self {\n            Frame::Padding { len } => {\n                let mut left = *len;\n\n                while left > 0 {\n                    b.put_varint(0x00)?;\n\n                    left -= 1;\n                }\n            },\n\n            Frame::Ping { .. } => {\n                b.put_varint(0x01)?;\n            },\n\n            Frame::ACK {\n                ack_delay,\n                ranges,\n                ecn_counts,\n            } => {\n                if ecn_counts.is_none() {\n                    b.put_varint(0x02)?;\n                } else {\n                    b.put_varint(0x03)?;\n                }\n\n                let mut it = ranges.iter().rev();\n\n                let first = it.next().unwrap();\n                let ack_block = (first.end - 1) - first.start;\n\n                b.put_varint(first.end - 1)?;\n                b.put_varint(*ack_delay)?;\n                b.put_varint(it.len() as u64)?;\n                b.put_varint(ack_block)?;\n\n                let mut smallest_ack = first.start;\n\n                for block in it {\n                    let gap = smallest_ack - block.end - 1;\n                    let ack_block = (block.end - 1) - block.start;\n\n                    b.put_varint(gap)?;\n                    b.put_varint(ack_block)?;\n\n                    smallest_ack = block.start;\n                }\n\n                if let Some(ecn) = ecn_counts {\n                    b.put_varint(ecn.ect0_count)?;\n                    b.put_varint(ecn.ect1_count)?;\n                    b.put_varint(ecn.ecn_ce_count)?;\n                }\n            },\n\n            Frame::ResetStream {\n                stream_id,\n                error_code,\n                final_size,\n            } => {\n                b.put_varint(0x04)?;\n\n                b.put_varint(*stream_id)?;\n                b.put_varint(*error_code)?;\n                b.put_varint(*final_size)?;\n            },\n\n            Frame::StopSending {\n                stream_id,\n                error_code,\n            } => {\n                b.put_varint(0x05)?;\n\n                b.put_varint(*stream_id)?;\n                b.put_varint(*error_code)?;\n            },\n\n            Frame::Crypto { data } => {\n                encode_crypto_header(data.off(), data.len() as u64, b)?;\n\n                b.put_bytes(data)?;\n            },\n\n            Frame::CryptoHeader { .. } => (),\n\n            Frame::NewToken { token } => {\n                b.put_varint(0x07)?;\n\n                b.put_varint(token.len() as u64)?;\n                b.put_bytes(token)?;\n            },\n\n            Frame::Stream { stream_id, data } => {\n                encode_stream_header(\n                    *stream_id,\n                    data.off(),\n                    data.len() as u64,\n                    data.fin(),\n                    b,\n                )?;\n\n                b.put_bytes(data)?;\n            },\n\n            Frame::StreamHeader { .. } => (),\n\n            Frame::MaxData { max } => {\n                b.put_varint(0x10)?;\n\n                b.put_varint(*max)?;\n            },\n\n            Frame::MaxStreamData { stream_id, max } => {\n                b.put_varint(0x11)?;\n\n                b.put_varint(*stream_id)?;\n                b.put_varint(*max)?;\n            },\n\n            Frame::MaxStreamsBidi { max } => {\n                b.put_varint(0x12)?;\n\n                b.put_varint(*max)?;\n            },\n\n            Frame::MaxStreamsUni { max } => {\n                b.put_varint(0x13)?;\n\n                b.put_varint(*max)?;\n            },\n\n            Frame::DataBlocked { limit } => {\n                b.put_varint(0x14)?;\n\n                b.put_varint(*limit)?;\n            },\n\n            Frame::StreamDataBlocked { stream_id, limit } => {\n                b.put_varint(0x15)?;\n\n                b.put_varint(*stream_id)?;\n                b.put_varint(*limit)?;\n            },\n\n            Frame::StreamsBlockedBidi { limit } => {\n                b.put_varint(0x16)?;\n\n                b.put_varint(*limit)?;\n            },\n\n            Frame::StreamsBlockedUni { limit } => {\n                b.put_varint(0x17)?;\n\n                b.put_varint(*limit)?;\n            },\n\n            Frame::NewConnectionId {\n                seq_num,\n                retire_prior_to,\n                conn_id,\n                reset_token,\n            } => {\n                b.put_varint(0x18)?;\n\n                b.put_varint(*seq_num)?;\n                b.put_varint(*retire_prior_to)?;\n                b.put_u8(conn_id.len() as u8)?;\n                b.put_bytes(conn_id.as_ref())?;\n                b.put_bytes(reset_token.as_ref())?;\n            },\n\n            Frame::RetireConnectionId { seq_num } => {\n                b.put_varint(0x19)?;\n\n                b.put_varint(*seq_num)?;\n            },\n\n            Frame::PathChallenge { data } => {\n                b.put_varint(0x1a)?;\n\n                b.put_bytes(data.as_ref())?;\n            },\n\n            Frame::PathResponse { data } => {\n                b.put_varint(0x1b)?;\n\n                b.put_bytes(data.as_ref())?;\n            },\n\n            Frame::ConnectionClose {\n                error_code,\n                frame_type,\n                reason,\n            } => {\n                b.put_varint(0x1c)?;\n\n                b.put_varint(*error_code)?;\n                b.put_varint(*frame_type)?;\n                b.put_varint(reason.len() as u64)?;\n                b.put_bytes(reason.as_ref())?;\n            },\n\n            Frame::ApplicationClose { error_code, reason } => {\n                b.put_varint(0x1d)?;\n\n                b.put_varint(*error_code)?;\n                b.put_varint(reason.len() as u64)?;\n                b.put_bytes(reason.as_ref())?;\n            },\n\n            Frame::HandshakeDone => {\n                b.put_varint(0x1e)?;\n            },\n\n            Frame::Datagram { data } => {\n                encode_dgram_header(data.len() as u64, b)?;\n\n                b.put_bytes(data.as_ref())?;\n            },\n\n            Frame::DatagramHeader { .. } => (),\n        }\n\n        Ok(before - b.cap())\n    }\n\n    pub fn wire_len(&self) -> usize {\n        match self {\n            Frame::Padding { len } => *len,\n\n            Frame::Ping { .. } => 1,\n\n            Frame::ACK {\n                ack_delay,\n                ranges,\n                ecn_counts,\n            } => {\n                let mut it = ranges.iter().rev();\n\n                let first = it.next().unwrap();\n                let ack_block = (first.end - 1) - first.start;\n\n                let mut len = 1 + // frame type\n                    octets::varint_len(first.end - 1) + // largest_ack\n                    octets::varint_len(*ack_delay) + // ack_delay\n                    octets::varint_len(it.len() as u64) + // block_count\n                    octets::varint_len(ack_block); // first_block\n\n                let mut smallest_ack = first.start;\n\n                for block in it {\n                    let gap = smallest_ack - block.end - 1;\n                    let ack_block = (block.end - 1) - block.start;\n\n                    len += octets::varint_len(gap) + // gap\n                           octets::varint_len(ack_block); // ack_block\n\n                    smallest_ack = block.start;\n                }\n\n                if let Some(ecn) = ecn_counts {\n                    len += octets::varint_len(ecn.ect0_count) +\n                        octets::varint_len(ecn.ect1_count) +\n                        octets::varint_len(ecn.ecn_ce_count);\n                }\n\n                len\n            },\n\n            Frame::ResetStream {\n                stream_id,\n                error_code,\n                final_size,\n            } => {\n                1 + // frame type\n                octets::varint_len(*stream_id) + // stream_id\n                octets::varint_len(*error_code) + // error_code\n                octets::varint_len(*final_size) // final_size\n            },\n\n            Frame::StopSending {\n                stream_id,\n                error_code,\n            } => {\n                1 + // frame type\n                octets::varint_len(*stream_id) + // stream_id\n                octets::varint_len(*error_code) // error_code\n            },\n\n            Frame::Crypto { data } => {\n                1 + // frame type\n                octets::varint_len(data.off()) + // offset\n                2 + // length, always encode as 2-byte varint\n                data.len() // data\n            },\n\n            Frame::CryptoHeader { offset, length, .. } => {\n                1 + // frame type\n                octets::varint_len(*offset) + // offset\n                2 + // length, always encode as 2-byte varint\n                length // data\n            },\n\n            Frame::NewToken { token } => {\n                1 + // frame type\n                octets::varint_len(token.len() as u64) + // token length\n                token.len() // token\n            },\n\n            Frame::Stream { stream_id, data } => {\n                1 + // frame type\n                octets::varint_len(*stream_id) + // stream_id\n                octets::varint_len(data.off()) + // offset\n                2 + // length, always encode as 2-byte varint\n                data.len() // data\n            },\n\n            Frame::StreamHeader {\n                stream_id,\n                offset,\n                length,\n                ..\n            } => {\n                1 + // frame type\n                octets::varint_len(*stream_id) + // stream_id\n                octets::varint_len(*offset) + // offset\n                2 + // length, always encode as 2-byte varint\n                length // data\n            },\n\n            Frame::MaxData { max } => {\n                1 + // frame type\n                octets::varint_len(*max) // max\n            },\n\n            Frame::MaxStreamData { stream_id, max } => {\n                1 + // frame type\n                octets::varint_len(*stream_id) + // stream_id\n                octets::varint_len(*max) // max\n            },\n\n            Frame::MaxStreamsBidi { max } => {\n                1 + // frame type\n                octets::varint_len(*max) // max\n            },\n\n            Frame::MaxStreamsUni { max } => {\n                1 + // frame type\n                octets::varint_len(*max) // max\n            },\n\n            Frame::DataBlocked { limit } => {\n                1 + // frame type\n                octets::varint_len(*limit) // limit\n            },\n\n            Frame::StreamDataBlocked { stream_id, limit } => {\n                1 + // frame type\n                octets::varint_len(*stream_id) + // stream_id\n                octets::varint_len(*limit) // limit\n            },\n\n            Frame::StreamsBlockedBidi { limit } => {\n                1 + // frame type\n                octets::varint_len(*limit) // limit\n            },\n\n            Frame::StreamsBlockedUni { limit } => {\n                1 + // frame type\n                octets::varint_len(*limit) // limit\n            },\n\n            Frame::NewConnectionId {\n                seq_num,\n                retire_prior_to,\n                conn_id,\n                reset_token,\n            } => {\n                1 + // frame type\n                octets::varint_len(*seq_num) + // seq_num\n                octets::varint_len(*retire_prior_to) + // retire_prior_to\n                1 + // conn_id length\n                conn_id.len() + // conn_id\n                reset_token.len() // reset_token\n            },\n\n            Frame::RetireConnectionId { seq_num } => {\n                1 + // frame type\n                octets::varint_len(*seq_num) // seq_num\n            },\n\n            Frame::PathChallenge { .. } => {\n                1 + // frame type\n                8 // data\n            },\n\n            Frame::PathResponse { .. } => {\n                1 + // frame type\n                8 // data\n            },\n\n            Frame::ConnectionClose {\n                frame_type,\n                error_code,\n                reason,\n                ..\n            } => {\n                1 + // frame type\n                octets::varint_len(*error_code) + // error_code\n                octets::varint_len(*frame_type) + // frame_type\n                octets::varint_len(reason.len() as u64) + // reason_len\n                reason.len() // reason\n            },\n\n            Frame::ApplicationClose { reason, error_code } => {\n                1 + // frame type\n                octets::varint_len(*error_code) + // error_code\n                octets::varint_len(reason.len() as u64) + // reason_len\n                reason.len() // reason\n            },\n\n            Frame::HandshakeDone => {\n                1 // frame type\n            },\n\n            Frame::Datagram { data } => {\n                1 + // frame type\n                2 + // length, always encode as 2-byte varint\n                data.len() // data\n            },\n\n            Frame::DatagramHeader { length } => {\n                1 + // frame type\n                2 + // length, always encode as 2-byte varint\n                *length // data\n            },\n        }\n    }\n\n    pub fn ack_eliciting(&self) -> bool {\n        // Any other frame is ack-eliciting (note the `!`).\n        !matches!(\n            self,\n            Frame::Padding { .. } |\n                Frame::ACK { .. } |\n                Frame::ApplicationClose { .. } |\n                Frame::ConnectionClose { .. }\n        )\n    }\n\n    pub fn probing(&self) -> bool {\n        matches!(\n            self,\n            Frame::Padding { .. } |\n                Frame::NewConnectionId { .. } |\n                Frame::PathChallenge { .. } |\n                Frame::PathResponse { .. }\n        )\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub fn to_qlog(&self) -> QuicFrame {\n        use qlog::events::ConnectionClosedFrameError;\n        use qlog::events::RawInfo;\n\n        match self {\n            Frame::Padding { len } => QuicFrame::Padding {\n                raw: Some(RawInfo {\n                    length: None,\n                    payload_length: Some(*len as u64),\n                    data: None,\n                }),\n            },\n\n            Frame::Ping { .. } => QuicFrame::Ping { raw: None },\n\n            Frame::ACK {\n                ack_delay,\n                ranges,\n                ecn_counts,\n            } => {\n                let ack_ranges = AckedRanges::Double(\n                    ranges.iter().map(|r| (r.start, r.end - 1)).collect(),\n                );\n\n                let (ect0, ect1, ce) = match ecn_counts {\n                    Some(ecn) => (\n                        Some(ecn.ect0_count),\n                        Some(ecn.ect1_count),\n                        Some(ecn.ecn_ce_count),\n                    ),\n\n                    None => (None, None, None),\n                };\n\n                QuicFrame::Ack {\n                    ack_delay: Some(*ack_delay as f32 / 1000.0),\n                    acked_ranges: Some(ack_ranges),\n                    ect1,\n                    ect0,\n                    ce,\n                    raw: None,\n                }\n            },\n\n            Frame::ResetStream {\n                stream_id,\n                error_code,\n                final_size,\n            } => QuicFrame::ResetStream {\n                stream_id: *stream_id,\n                error: qlog::events::ApplicationError::Unknown,\n                error_code: Some(*error_code),\n                final_size: *final_size,\n                raw: None,\n            },\n\n            Frame::StopSending {\n                stream_id,\n                error_code,\n            } => QuicFrame::StopSending {\n                stream_id: *stream_id,\n                error: qlog::events::ApplicationError::Unknown,\n                error_code: Some(*error_code),\n                raw: None,\n            },\n\n            Frame::Crypto { data } => QuicFrame::Crypto {\n                offset: data.off(),\n                raw: Some(RawInfo {\n                    length: None,\n                    payload_length: Some(data.len() as u64),\n                    data: None,\n                }),\n            },\n\n            Frame::CryptoHeader { offset, length } => QuicFrame::Crypto {\n                offset: *offset,\n                raw: Some(RawInfo {\n                    length: None,\n                    payload_length: Some(*length as u64),\n                    data: None,\n                }),\n            },\n\n            Frame::NewToken { token } => QuicFrame::NewToken {\n                token: qlog::Token {\n                    // TODO: pick the token type some how\n                    ty: Some(qlog::TokenType::Retry),\n                    raw: Some(RawInfo {\n                        data: qlog::HexSlice::maybe_string(Some(token)),\n                        length: Some(token.len() as u64),\n                        payload_length: None,\n                    }),\n                    details: None,\n                },\n                raw: None,\n            },\n\n            Frame::Stream { stream_id, data } => QuicFrame::Stream {\n                stream_id: *stream_id,\n                offset: Some(data.off()),\n                fin: data.fin().then_some(true),\n                raw: Some(RawInfo {\n                    length: None,\n                    payload_length: Some(data.len() as u64),\n                    data: None,\n                }),\n            },\n\n            Frame::StreamHeader {\n                stream_id,\n                offset,\n                length,\n                fin,\n            } => QuicFrame::Stream {\n                stream_id: *stream_id,\n                offset: Some(*offset),\n                fin: fin.then(|| true),\n                raw: Some(RawInfo {\n                    length: None,\n                    payload_length: Some(*length as u64),\n                    data: None,\n                }),\n            },\n\n            Frame::MaxData { max } => QuicFrame::MaxData {\n                maximum: *max,\n                raw: None,\n            },\n\n            Frame::MaxStreamData { stream_id, max } => QuicFrame::MaxStreamData {\n                stream_id: *stream_id,\n                maximum: *max,\n                raw: None,\n            },\n\n            Frame::MaxStreamsBidi { max } => QuicFrame::MaxStreams {\n                stream_type: StreamType::Bidirectional,\n                maximum: *max,\n                raw: None,\n            },\n\n            Frame::MaxStreamsUni { max } => QuicFrame::MaxStreams {\n                stream_type: StreamType::Unidirectional,\n                maximum: *max,\n                raw: None,\n            },\n\n            Frame::DataBlocked { limit } => QuicFrame::DataBlocked {\n                limit: *limit,\n                raw: None,\n            },\n\n            Frame::StreamDataBlocked { stream_id, limit } =>\n                QuicFrame::StreamDataBlocked {\n                    stream_id: *stream_id,\n                    limit: *limit,\n                    raw: None,\n                },\n\n            Frame::StreamsBlockedBidi { limit } => QuicFrame::StreamsBlocked {\n                stream_type: StreamType::Bidirectional,\n                limit: *limit,\n                raw: None,\n            },\n\n            Frame::StreamsBlockedUni { limit } => QuicFrame::StreamsBlocked {\n                stream_type: StreamType::Unidirectional,\n                limit: *limit,\n                raw: None,\n            },\n\n            Frame::NewConnectionId {\n                seq_num,\n                retire_prior_to,\n                conn_id,\n                reset_token,\n            } => QuicFrame::NewConnectionId {\n                sequence_number: *seq_num,\n                retire_prior_to: *retire_prior_to,\n                connection_id_length: Some(conn_id.len() as u8),\n                connection_id: format!(\"{}\", qlog::HexSlice::new(conn_id)),\n                stateless_reset_token: qlog::HexSlice::maybe_string(Some(\n                    reset_token,\n                )),\n                raw: None,\n            },\n\n            Frame::RetireConnectionId { seq_num } =>\n                QuicFrame::RetireConnectionId {\n                    sequence_number: *seq_num,\n                    raw: None,\n                },\n\n            Frame::PathChallenge { .. } => QuicFrame::PathChallenge {\n                data: None,\n                raw: None,\n            },\n\n            Frame::PathResponse { .. } => QuicFrame::PathResponse {\n                data: None,\n                raw: None,\n            },\n\n            Frame::ConnectionClose {\n                error_code, reason, ..\n            } => QuicFrame::ConnectionClose {\n                // TODO: use actual variant not unknown\n                error: Some(ConnectionClosedFrameError::TransportError(\n                    qlog::events::quic::TransportError::Unknown,\n                )),\n                error_space: Some(ErrorSpace::Transport),\n                error_code: Some(*error_code),\n                reason: Some(String::from_utf8_lossy(reason).into_owned()),\n                reason_bytes: None,\n                trigger_frame_type: None, // don't know trigger type\n            },\n\n            Frame::ApplicationClose { error_code, reason } => {\n                QuicFrame::ConnectionClose {\n                    error: Some(ConnectionClosedFrameError::ApplicationError(\n                        qlog::events::ApplicationError::Unknown,\n                    )),\n                    error_space: Some(ErrorSpace::Application),\n                    error_code: Some(*error_code),\n                    reason: Some(String::from_utf8_lossy(reason).into_owned()),\n                    reason_bytes: None,\n                    trigger_frame_type: None, // don't know trigger type\n                }\n            },\n\n            Frame::HandshakeDone => QuicFrame::HandshakeDone { raw: None },\n\n            Frame::Datagram { data } => QuicFrame::Datagram {\n                raw: Some(RawInfo {\n                    length: None,\n                    payload_length: Some(data.len() as u64),\n                    data: None,\n                }),\n            },\n\n            Frame::DatagramHeader { length } => QuicFrame::Datagram {\n                raw: Some(RawInfo {\n                    length: None,\n                    payload_length: Some(*length as u64),\n                    data: None,\n                }),\n            },\n        }\n    }\n}\n\nimpl std::fmt::Debug for Frame {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self {\n            Frame::Padding { len } => {\n                write!(f, \"PADDING len={len}\")?;\n            },\n\n            Frame::Ping { mtu_probe } => {\n                write!(f, \"PING mtu_probe={mtu_probe:?}\")?;\n            },\n\n            Frame::ACK {\n                ack_delay,\n                ranges,\n                ecn_counts,\n            } => {\n                write!(\n                    f,\n                    \"ACK delay={ack_delay} blocks={ranges:?} ecn_counts={ecn_counts:?}\"\n                )?;\n            },\n\n            Frame::ResetStream {\n                stream_id,\n                error_code,\n                final_size,\n            } => {\n                write!(\n                    f,\n                    \"RESET_STREAM stream={stream_id} err={error_code:x} size={final_size}\"\n                )?;\n            },\n\n            Frame::StopSending {\n                stream_id,\n                error_code,\n            } => {\n                write!(f, \"STOP_SENDING stream={stream_id} err={error_code:x}\")?;\n            },\n\n            Frame::Crypto { data } => {\n                write!(f, \"CRYPTO off={} len={}\", data.off(), data.len())?;\n            },\n\n            Frame::CryptoHeader { offset, length } => {\n                write!(f, \"CRYPTO off={offset} len={length}\")?;\n            },\n\n            Frame::NewToken { token } => {\n                write!(f, \"NEW_TOKEN len={}\", token.len())?;\n            },\n\n            Frame::Stream { stream_id, data } => {\n                write!(\n                    f,\n                    \"STREAM id={} off={} len={} fin={}\",\n                    stream_id,\n                    data.off(),\n                    data.len(),\n                    data.fin()\n                )?;\n            },\n\n            Frame::StreamHeader {\n                stream_id,\n                offset,\n                length,\n                fin,\n            } => {\n                write!(\n                    f,\n                    \"STREAM id={stream_id} off={offset} len={length} fin={fin}\"\n                )?;\n            },\n\n            Frame::MaxData { max } => {\n                write!(f, \"MAX_DATA max={max}\")?;\n            },\n\n            Frame::MaxStreamData { stream_id, max } => {\n                write!(f, \"MAX_STREAM_DATA stream={stream_id} max={max}\")?;\n            },\n\n            Frame::MaxStreamsBidi { max } => {\n                write!(f, \"MAX_STREAMS type=bidi max={max}\")?;\n            },\n\n            Frame::MaxStreamsUni { max } => {\n                write!(f, \"MAX_STREAMS type=uni max={max}\")?;\n            },\n\n            Frame::DataBlocked { limit } => {\n                write!(f, \"DATA_BLOCKED limit={limit}\")?;\n            },\n\n            Frame::StreamDataBlocked { stream_id, limit } => {\n                write!(\n                    f,\n                    \"STREAM_DATA_BLOCKED stream={stream_id} limit={limit}\"\n                )?;\n            },\n\n            Frame::StreamsBlockedBidi { limit } => {\n                write!(f, \"STREAMS_BLOCKED type=bidi limit={limit}\")?;\n            },\n\n            Frame::StreamsBlockedUni { limit } => {\n                write!(f, \"STREAMS_BLOCKED type=uni limit={limit}\")?;\n            },\n\n            Frame::NewConnectionId {\n                seq_num,\n                retire_prior_to,\n                conn_id,\n                reset_token,\n            } => {\n                write!(\n                    f,\n                    \"NEW_CONNECTION_ID seq_num={seq_num} retire_prior_to={retire_prior_to} conn_id={conn_id:02x?} reset_token={reset_token:02x?}\",\n                )?;\n            },\n\n            Frame::RetireConnectionId { seq_num } => {\n                write!(f, \"RETIRE_CONNECTION_ID seq_num={seq_num}\")?;\n            },\n\n            Frame::PathChallenge { data } => {\n                write!(f, \"PATH_CHALLENGE data={data:02x?}\")?;\n            },\n\n            Frame::PathResponse { data } => {\n                write!(f, \"PATH_RESPONSE data={data:02x?}\")?;\n            },\n\n            Frame::ConnectionClose {\n                error_code,\n                frame_type,\n                reason,\n            } => {\n                write!(\n                    f,\n                    \"CONNECTION_CLOSE err={error_code:x} frame={frame_type:x} reason={reason:x?}\"\n                )?;\n            },\n\n            Frame::ApplicationClose { error_code, reason } => {\n                write!(\n                    f,\n                    \"APPLICATION_CLOSE err={error_code:x} reason={reason:x?}\"\n                )?;\n            },\n\n            Frame::HandshakeDone => {\n                write!(f, \"HANDSHAKE_DONE\")?;\n            },\n\n            Frame::Datagram { data } => {\n                write!(f, \"DATAGRAM len={}\", data.len())?;\n            },\n\n            Frame::DatagramHeader { length } => {\n                write!(f, \"DATAGRAM len={length}\")?;\n            },\n        }\n\n        Ok(())\n    }\n}\n\nfn parse_ack_frame(ty: u64, b: &mut octets::Octets) -> Result<Frame> {\n    let first = ty as u8;\n\n    let largest_ack = b.get_varint()?;\n    let ack_delay = b.get_varint()?;\n    let block_count = b.get_varint()?;\n    let ack_block = b.get_varint()?;\n\n    if largest_ack < ack_block {\n        return Err(Error::InvalidFrame);\n    }\n\n    let mut smallest_ack = largest_ack - ack_block;\n\n    let mut ranges = ranges::RangeSet::default();\n\n    ranges.insert(smallest_ack..largest_ack + 1);\n\n    for _i in 0..block_count {\n        let gap = b.get_varint()?;\n\n        if smallest_ack < 2 + gap {\n            return Err(Error::InvalidFrame);\n        }\n\n        let largest_ack = (smallest_ack - gap) - 2;\n        let ack_block = b.get_varint()?;\n\n        if largest_ack < ack_block {\n            return Err(Error::InvalidFrame);\n        }\n\n        smallest_ack = largest_ack - ack_block;\n\n        ranges.insert(smallest_ack..largest_ack + 1);\n    }\n\n    let ecn_counts = if first & 0x01 != 0 {\n        let ecn = EcnCounts {\n            ect0_count: b.get_varint()?,\n            ect1_count: b.get_varint()?,\n            ecn_ce_count: b.get_varint()?,\n        };\n\n        Some(ecn)\n    } else {\n        None\n    };\n\n    Ok(Frame::ACK {\n        ack_delay,\n        ranges,\n        ecn_counts,\n    })\n}\n\npub fn encode_crypto_header(\n    offset: u64, length: u64, b: &mut octets::OctetsMut,\n) -> Result<()> {\n    b.put_varint(0x06)?;\n\n    b.put_varint(offset)?;\n\n    // Always encode length field as 2-byte varint.\n    b.put_varint_with_len(length, 2)?;\n\n    Ok(())\n}\n\npub fn encode_stream_header(\n    stream_id: u64, offset: u64, length: u64, fin: bool,\n    b: &mut octets::OctetsMut,\n) -> Result<()> {\n    let mut ty: u8 = 0x08;\n\n    // Always encode offset.\n    ty |= 0x04;\n\n    // Always encode length.\n    ty |= 0x02;\n\n    if fin {\n        ty |= 0x01;\n    }\n\n    b.put_varint(u64::from(ty))?;\n\n    b.put_varint(stream_id)?;\n    b.put_varint(offset)?;\n\n    // Always encode length field as 2-byte varint.\n    b.put_varint_with_len(length, 2)?;\n\n    Ok(())\n}\n\npub fn encode_dgram_header(length: u64, b: &mut octets::OctetsMut) -> Result<()> {\n    let mut ty: u8 = 0x30;\n\n    // Always encode length\n    ty |= 0x01;\n\n    b.put_varint(u64::from(ty))?;\n\n    // Always encode length field as 2-byte varint.\n    b.put_varint_with_len(length, 2)?;\n\n    Ok(())\n}\n\nfn parse_stream_frame(ty: u64, b: &mut octets::Octets) -> Result<Frame> {\n    let first = ty as u8;\n\n    let stream_id = b.get_varint()?;\n\n    let offset = if first & 0x04 != 0 {\n        b.get_varint()?\n    } else {\n        0\n    };\n\n    let len = if first & 0x02 != 0 {\n        b.get_varint()? as usize\n    } else {\n        b.cap()\n    };\n\n    if offset + len as u64 >= MAX_STREAM_SIZE {\n        return Err(Error::InvalidFrame);\n    }\n\n    let fin = first & 0x01 != 0;\n\n    let data = b.get_bytes(len)?;\n    let data = <RangeBuf>::from(data.as_ref(), offset, fin);\n\n    Ok(Frame::Stream { stream_id, data })\n}\n\nfn parse_datagram_frame(ty: u64, b: &mut octets::Octets) -> Result<Frame> {\n    let first = ty as u8;\n\n    let len = if first & 0x01 != 0 {\n        b.get_varint()? as usize\n    } else {\n        b.cap()\n    };\n\n    let data = b.get_bytes(len)?;\n\n    Ok(Frame::Datagram {\n        data: Vec::from(data.buf()),\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn padding() {\n        let mut d = [42; 128];\n\n        let frame = Frame::Padding { len: 128 };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 128);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_ok());\n    }\n\n    #[test]\n    fn ping() {\n        let mut d = [42; 128];\n\n        let frame = Frame::Ping { mtu_probe: None };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 1);\n        assert_eq!(&d[..wire_len], [0x01_u8]);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_ok());\n    }\n\n    #[test]\n    fn ack() {\n        let mut d = [42; 128];\n\n        let mut ranges = ranges::RangeSet::default();\n        ranges.insert(4..7);\n        ranges.insert(9..12);\n        ranges.insert(15..19);\n        ranges.insert(3000..5000);\n\n        let frame = Frame::ACK {\n            ack_delay: 874_656_534,\n            ranges,\n            ecn_counts: None,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 17);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_ok());\n    }\n\n    #[test]\n    fn ack_ecn() {\n        let mut d = [42; 128];\n\n        let mut ranges = ranges::RangeSet::default();\n        ranges.insert(4..7);\n        ranges.insert(9..12);\n        ranges.insert(15..19);\n        ranges.insert(3000..5000);\n\n        let ecn_counts = Some(EcnCounts {\n            ect0_count: 100,\n            ect1_count: 200,\n            ecn_ce_count: 300,\n        });\n\n        let frame = Frame::ACK {\n            ack_delay: 874_656_534,\n            ranges,\n            ecn_counts,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 23);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_ok());\n    }\n\n    #[test]\n    fn reset_stream() {\n        let mut d = [42; 128];\n\n        let frame = Frame::ResetStream {\n            stream_id: 123_213,\n            error_code: 21_123_767,\n            final_size: 21_123_767,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 13);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn stop_sending() {\n        let mut d = [42; 128];\n\n        let frame = Frame::StopSending {\n            stream_id: 123_213,\n            error_code: 15_352,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 7);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn crypto() {\n        let mut d = [42; 128];\n\n        let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n\n        let frame = Frame::Crypto {\n            data: <RangeBuf>::from(&data, 1230976, false),\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 19);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_ok());\n    }\n\n    #[test]\n    fn new_token() {\n        let mut d = [42; 128];\n\n        let frame = Frame::NewToken {\n            token: Vec::from(\"this is a token\"),\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 17);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn stream() {\n        let mut d = [42; 128];\n\n        let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n\n        let frame = Frame::Stream {\n            stream_id: 32,\n            data: <RangeBuf>::from(&data, 1230976, true),\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 20);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn stream_too_big() {\n        let mut d = [42; 128];\n\n        let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n\n        let frame = Frame::Stream {\n            stream_id: 32,\n            data: <RangeBuf>::from(&data, MAX_STREAM_SIZE - 11, true),\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 24);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(\n            Frame::from_bytes(&mut b, packet::Type::Short),\n            Err(Error::InvalidFrame)\n        );\n    }\n\n    #[test]\n    fn max_data() {\n        let mut d = [42; 128];\n\n        let frame = Frame::MaxData { max: 128_318_273 };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 5);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn max_stream_data() {\n        let mut d = [42; 128];\n\n        let frame = Frame::MaxStreamData {\n            stream_id: 12_321,\n            max: 128_318_273,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 7);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn max_streams_bidi() {\n        let mut d = [42; 128];\n\n        let frame = Frame::MaxStreamsBidi { max: 128_318_273 };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 5);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn max_streams_uni() {\n        let mut d = [42; 128];\n\n        let frame = Frame::MaxStreamsUni { max: 128_318_273 };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 5);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn data_blocked() {\n        let mut d = [42; 128];\n\n        let frame = Frame::DataBlocked { limit: 128_318_273 };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 5);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn stream_data_blocked() {\n        let mut d = [42; 128];\n\n        let frame = Frame::StreamDataBlocked {\n            stream_id: 12_321,\n            limit: 128_318_273,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 7);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn streams_blocked_bidi() {\n        let mut d = [42; 128];\n\n        let frame = Frame::StreamsBlockedBidi { limit: 128_318_273 };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 5);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn streams_blocked_uni() {\n        let mut d = [42; 128];\n\n        let frame = Frame::StreamsBlockedUni { limit: 128_318_273 };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 5);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn new_connection_id() {\n        let mut d = [42; 128];\n\n        let frame = Frame::NewConnectionId {\n            seq_num: 123_213,\n            retire_prior_to: 122_211,\n            conn_id: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],\n            reset_token: [0x42; 16],\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 41);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn retire_connection_id() {\n        let mut d = [42; 128];\n\n        let frame = Frame::RetireConnectionId { seq_num: 123_213 };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 5);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn path_challenge() {\n        let mut d = [42; 128];\n\n        let frame = Frame::PathChallenge {\n            data: [1, 2, 3, 4, 5, 6, 7, 8],\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 9);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn path_response() {\n        let mut d = [42; 128];\n\n        let frame = Frame::PathResponse {\n            data: [1, 2, 3, 4, 5, 6, 7, 8],\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 9);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn connection_close() {\n        let mut d = [42; 128];\n\n        let frame = Frame::ConnectionClose {\n            error_code: 0xbeef,\n            frame_type: 523_423,\n            reason: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 22);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_ok());\n    }\n\n    #[test]\n    fn application_close() {\n        let mut d = [42; 128];\n\n        let frame = Frame::ApplicationClose {\n            error_code: 0xbeef,\n            reason: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 18);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn handshake_done() {\n        let mut d = [42; 128];\n\n        let frame = Frame::HandshakeDone;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 1);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(Frame::from_bytes(&mut b, packet::Type::Short), Ok(frame));\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n    }\n\n    #[test]\n    fn datagram() {\n        let mut d = [42; 128];\n\n        let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n\n        let frame = Frame::Datagram { data: data.clone() };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, 15);\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert_eq!(\n            Frame::from_bytes(&mut b, packet::Type::Short),\n            Ok(frame.clone())\n        );\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Initial).is_err());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::ZeroRTT).is_ok());\n\n        let mut b = octets::Octets::with_slice(&d);\n        assert!(Frame::from_bytes(&mut b, packet::Type::Handshake).is_err());\n\n        let frame_data = match &frame {\n            Frame::Datagram { data } => data.clone(),\n\n            _ => unreachable!(),\n        };\n\n        assert_eq!(frame_data, data);\n    }\n}\n"
  },
  {
    "path": "quiche/src/h3/AGENTS.md",
    "content": "# quiche/src/h3/ — HTTP/3 Module\n\n## OVERVIEW\n\nHTTP/3 wire protocol over QUIC. `Connection` manages H3 state (streams, QPACK, SETTINGS, GOAWAY) on top of `quiche::Connection<F>`. Event-driven: caller loops `poll()` → `Event`. Own `Error`/`Result` types, separate from `quiche::Error`.\n\n## STRUCTURE\n\n```\nmod.rs       (7549 lines)  H3 Connection, Config, Error, Event, Header, NameValue, Priority\nstream.rs    (1565)        H3 stream state machine (Type, State enums; frame parsing FSM)\nframe.rs     (1337)        H3 frame encode/decode (Frame enum, settings constants)\nffi.rs                     C FFI for H3 — behind `ffi` feature\nqpack/\n  mod.rs                   Re-exports\n  encoder.rs               QPACK encoder\n  decoder.rs               QPACK decoder\n  static_table.rs          Static header table (RFC 9204)\n```\n\n## WHERE TO LOOK\n\n| Task | Location |\n|------|----------|\n| Send/recv requests+responses | `mod.rs` — `send_request()`, `send_response()`, `poll()` |\n| Body data | `mod.rs` — `send_body()`, `recv_body()` |\n| Priority handling | `mod.rs` — `Priority` struct, `send_priority_update_for_request()` |\n| H3 stream lifecycle | `stream.rs` — `Stream` struct, `State` FSM |\n| Frame wire format | `frame.rs` — `Frame` enum, `encode()`/`decode()` |\n| QPACK header compression | `qpack/encoder.rs`, `qpack/decoder.rs` |\n| H3 C API | `ffi.rs` |\n\n## ANTI-PATTERNS\n\n- **h3::Error != quiche::Error.** 19 variants; `TransportError(quiche::Error)` wraps transport errors. Don't confuse.\n- **`Error::Done` is success** in poll/read loops. Not a failure — signals \"no more work\".\n- **`to_wire()` maps `BufferTooShort` → `0x999`** — non-standard wire code. Don't propagate this pattern.\n- **`to_c()` skips -12** — was previously `TransportError`. Gap is intentional for ABI stability.\n- **`to_c()` for `TransportError`:** offsets by `-1000` from underlying `quic_error.to_c()`.\n- **`send_request()` sends empty `b\"\"` to create QUIC stream** before writing headers. Required because QUIC stream doesn't exist until first write.\n- **stream.rs `Stream` ≠ quiche::stream::Stream.** H3 stream is a frame-parsing state machine layered on top.\n\n## NOTES\n\n- Methods are generic over `F: BufFactory` (zero-copy) and `T: NameValue` (header access).\n- `NameValue` trait: `name() -> &[u8]`, `value() -> &[u8]`. Blanket impl for `(N, V)` tuples.\n- `Header` is `(Vec<u8>, Vec<u8>)` newtype implementing `NameValue`.\n- `Event` variants: `Headers`, `Data`, `Finished`, `Reset(u64)`, `PriorityUpdate`, `GoAway`.\n- Priority: RFC 9218 Extensible Priorities. `sfv` feature enables `TryFrom` parsing.\n- `PRIORITY_URGENCY_OFFSET = 124` maps external urgency 0-7 to internal quiche priority.\n- `APPLICATION_PROTOCOL = &[b\"h3\"]` — ALPN constant.\n- `From<quiche::Error>` converts `Done→Done`, everything else → `TransportError(e)`.\n- `From<octets::BufferTooShortError>` → `Error::BufferTooShort`.\n- All `#[cfg(feature = \"qlog\")]` instrumentation inline in mod.rs — heavy conditional compilation.\n"
  },
  {
    "path": "quiche/src/h3/ffi.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[cfg(feature = \"sfv\")]\nuse std::convert::TryFrom;\n\nuse std::ptr;\nuse std::slice;\n\nuse libc::c_int;\nuse libc::c_void;\nuse libc::size_t;\nuse libc::ssize_t;\n\nuse crate::*;\n\nuse crate::h3::NameValue;\nuse crate::h3::Priority;\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_config_new() -> *mut h3::Config {\n    match h3::Config::new() {\n        Ok(c) => Box::into_raw(Box::new(c)),\n\n        Err(_) => ptr::null_mut(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_config_set_max_field_section_size(\n    config: &mut h3::Config, v: u64,\n) {\n    config.set_max_field_section_size(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_config_set_qpack_max_table_capacity(\n    config: &mut h3::Config, v: u64,\n) {\n    config.set_qpack_max_table_capacity(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_config_set_qpack_blocked_streams(\n    config: &mut h3::Config, v: u64,\n) {\n    config.set_qpack_blocked_streams(v);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_config_enable_extended_connect(\n    config: &mut h3::Config, enabled: bool,\n) {\n    config.enable_extended_connect(enabled);\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_config_free(config: *mut h3::Config) {\n    drop(unsafe { Box::from_raw(config) });\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_conn_new_with_transport(\n    quic_conn: &mut Connection, config: &mut h3::Config,\n) -> *mut h3::Connection {\n    match h3::Connection::with_transport(quic_conn, config) {\n        Ok(c) => Box::into_raw(Box::new(c)),\n\n        Err(_) => ptr::null_mut(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_for_each_setting(\n    conn: &h3::Connection,\n    cb: extern \"C\" fn(identifier: u64, value: u64, argp: *mut c_void) -> c_int,\n    argp: *mut c_void,\n) -> c_int {\n    match conn.peer_settings_raw() {\n        Some(raw) => {\n            for setting in raw {\n                let rc = cb(setting.0, setting.1, argp);\n\n                if rc != 0 {\n                    return rc;\n                }\n            }\n\n            0\n        },\n\n        None => -1,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_conn_poll(\n    conn: &mut h3::Connection, quic_conn: &mut Connection,\n    ev: *mut *const h3::Event,\n) -> i64 {\n    match conn.poll(quic_conn) {\n        Ok((id, v)) => {\n            unsafe {\n                *ev = Box::into_raw(Box::new(v));\n            }\n\n            id as i64\n        },\n\n        Err(e) => e.to_c() as i64,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_event_type(ev: &h3::Event) -> u32 {\n    match ev {\n        h3::Event::Headers { .. } => 0,\n\n        h3::Event::Data => 1,\n\n        h3::Event::Finished => 2,\n\n        h3::Event::GoAway => 3,\n\n        h3::Event::Reset { .. } => 4,\n\n        h3::Event::PriorityUpdate => 5,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_event_for_each_header(\n    ev: &h3::Event,\n    cb: extern \"C\" fn(\n        name: *const u8,\n        name_len: size_t,\n\n        value: *const u8,\n        value_len: size_t,\n\n        argp: *mut c_void,\n    ) -> c_int,\n    argp: *mut c_void,\n) -> c_int {\n    match ev {\n        h3::Event::Headers { list, .. } =>\n            for h in list {\n                let rc = cb(\n                    h.name().as_ptr(),\n                    h.name().len(),\n                    h.value().as_ptr(),\n                    h.value().len(),\n                    argp,\n                );\n\n                if rc != 0 {\n                    return rc;\n                }\n            },\n\n        _ => unreachable!(),\n    }\n\n    0\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_event_headers_has_more_frames(\n    ev: &h3::Event,\n) -> bool {\n    match ev {\n        h3::Event::Headers { more_frames, .. } => *more_frames,\n\n        _ => unreachable!(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_extended_connect_enabled_by_peer(\n    conn: &h3::Connection,\n) -> bool {\n    conn.extended_connect_enabled_by_peer()\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_event_free(ev: *mut h3::Event) {\n    drop(unsafe { Box::from_raw(ev) });\n}\n\n#[repr(C)]\npub struct Header {\n    name: *mut u8,\n    name_len: usize,\n\n    value: *mut u8,\n    value_len: usize,\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_send_request(\n    conn: &mut h3::Connection, quic_conn: &mut Connection,\n    headers: *const Header, headers_len: size_t, fin: bool,\n) -> i64 {\n    let req_headers = headers_from_ptr(headers, headers_len);\n\n    match conn.send_request(quic_conn, &req_headers, fin) {\n        Ok(v) => v as i64,\n\n        Err(e) => e.to_c() as i64,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_send_response(\n    conn: &mut h3::Connection, quic_conn: &mut Connection, stream_id: u64,\n    headers: *const Header, headers_len: size_t, fin: bool,\n) -> c_int {\n    let resp_headers = headers_from_ptr(headers, headers_len);\n\n    match conn.send_response(quic_conn, stream_id, &resp_headers, fin) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_send_response_with_priority(\n    conn: &mut h3::Connection, quic_conn: &mut Connection, stream_id: u64,\n    headers: *const Header, headers_len: size_t, priority: &Priority, fin: bool,\n) -> c_int {\n    let resp_headers = headers_from_ptr(headers, headers_len);\n\n    match conn.send_response_with_priority(\n        quic_conn,\n        stream_id,\n        &resp_headers,\n        priority,\n        fin,\n    ) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_send_additional_headers(\n    conn: &mut h3::Connection, quic_conn: &mut Connection, stream_id: u64,\n    headers: *const Header, headers_len: size_t, is_trailer_section: bool,\n    fin: bool,\n) -> c_int {\n    let headers = headers_from_ptr(headers, headers_len);\n\n    match conn.send_additional_headers(\n        quic_conn,\n        stream_id,\n        &headers,\n        is_trailer_section,\n        fin,\n    ) {\n        Ok(_) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_send_body(\n    conn: &mut h3::Connection, quic_conn: &mut Connection, stream_id: u64,\n    body: *const u8, body_len: size_t, fin: bool,\n) -> ssize_t {\n    if body_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let body = unsafe { slice::from_raw_parts(body, body_len) };\n\n    match conn.send_body(quic_conn, stream_id, body, fin) {\n        Ok(v) => v as ssize_t,\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_recv_body(\n    conn: &mut h3::Connection, quic_conn: &mut Connection, stream_id: u64,\n    out: *mut u8, out_len: size_t,\n) -> ssize_t {\n    if out_len > <ssize_t>::MAX as usize {\n        panic!(\"The provided buffer is too large\");\n    }\n\n    let out = unsafe { slice::from_raw_parts_mut(out, out_len) };\n\n    match conn.recv_body(quic_conn, stream_id, out) {\n        Ok(v) => v as ssize_t,\n\n        Err(e) => e.to_c(),\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_send_goaway(\n    conn: &mut h3::Connection, quic_conn: &mut Connection, id: u64,\n) -> c_int {\n    match conn.send_goaway(quic_conn, id) {\n        Ok(()) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\n#[cfg(feature = \"sfv\")]\npub extern \"C\" fn quiche_h3_parse_extensible_priority(\n    priority: *const u8, priority_len: size_t, parsed: &mut Priority,\n) -> c_int {\n    let priority = unsafe { slice::from_raw_parts(priority, priority_len) };\n\n    match Priority::try_from(priority) {\n        Ok(v) => {\n            parsed.urgency = v.urgency;\n            parsed.incremental = v.incremental;\n            0\n        },\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_send_priority_update_for_request(\n    conn: &mut h3::Connection, quic_conn: &mut Connection, stream_id: u64,\n    priority: &Priority,\n) -> c_int {\n    match conn.send_priority_update_for_request(quic_conn, stream_id, priority) {\n        Ok(()) => 0,\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_take_last_priority_update(\n    conn: &mut h3::Connection, prioritized_element_id: u64,\n    cb: extern \"C\" fn(\n        priority_field_value: *const u8,\n        priority_field_value_len: size_t,\n        argp: *mut c_void,\n    ) -> c_int,\n    argp: *mut c_void,\n) -> c_int {\n    match conn.take_last_priority_update(prioritized_element_id) {\n        Ok(priority) => {\n            let rc = cb(priority.as_ptr(), priority.len(), argp);\n\n            if rc != 0 {\n                return rc;\n            }\n\n            0\n        },\n\n        Err(e) => e.to_c() as c_int,\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_dgram_enabled_by_peer(\n    conn: &h3::Connection, quic_conn: &Connection,\n) -> bool {\n    conn.dgram_enabled_by_peer(quic_conn)\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_conn_free(conn: *mut h3::Connection) {\n    drop(unsafe { Box::from_raw(conn) });\n}\n\nfn headers_from_ptr<'a>(\n    ptr: *const Header, len: size_t,\n) -> Vec<h3::HeaderRef<'a>> {\n    let headers = unsafe { slice::from_raw_parts(ptr, len) };\n\n    let mut out = Vec::new();\n\n    for h in headers {\n        out.push({\n            let name = unsafe { slice::from_raw_parts(h.name, h.name_len) };\n            let value = unsafe { slice::from_raw_parts(h.value, h.value_len) };\n\n            h3::HeaderRef::new(name, value)\n        });\n    }\n\n    out\n}\n\n#[repr(C)]\npub struct Stats {\n    qpack_encoder_stream_recv_bytes: u64,\n    qpack_decoder_stream_recv_bytes: u64,\n}\n\n#[no_mangle]\npub extern \"C\" fn quiche_h3_conn_stats(conn: &h3::Connection, out: &mut Stats) {\n    let stats = conn.stats();\n\n    out.qpack_encoder_stream_recv_bytes = stats.qpack_encoder_stream_recv_bytes;\n    out.qpack_decoder_stream_recv_bytes = stats.qpack_decoder_stream_recv_bytes;\n}\n"
  },
  {
    "path": "quiche/src/h3/frame.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::Result;\n\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::Http3Frame;\n\npub const DATA_FRAME_TYPE_ID: u64 = 0x0;\npub const HEADERS_FRAME_TYPE_ID: u64 = 0x1;\npub const CANCEL_PUSH_FRAME_TYPE_ID: u64 = 0x3;\npub const SETTINGS_FRAME_TYPE_ID: u64 = 0x4;\npub const PUSH_PROMISE_FRAME_TYPE_ID: u64 = 0x5;\npub const GOAWAY_FRAME_TYPE_ID: u64 = 0x7;\npub const MAX_PUSH_FRAME_TYPE_ID: u64 = 0xD;\npub const PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID: u64 = 0xF0700;\npub const PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID: u64 = 0xF0701;\n\npub const SETTINGS_QPACK_MAX_TABLE_CAPACITY: u64 = 0x1;\npub const SETTINGS_MAX_FIELD_SECTION_SIZE: u64 = 0x6;\npub const SETTINGS_QPACK_BLOCKED_STREAMS: u64 = 0x7;\npub const SETTINGS_ENABLE_CONNECT_PROTOCOL: u64 = 0x8;\npub const SETTINGS_H3_DATAGRAM_00: u64 = 0x276;\npub const SETTINGS_H3_DATAGRAM: u64 = 0x33;\n\n// Permit between 16 maximally-encoded and 128 minimally-encoded SETTINGS.\nconst MAX_SETTINGS_PAYLOAD_SIZE: usize = 256;\n\n#[derive(Clone, PartialEq, Eq)]\npub enum Frame {\n    Data {\n        payload: Vec<u8>,\n    },\n\n    Headers {\n        header_block: Vec<u8>,\n    },\n\n    CancelPush {\n        push_id: u64,\n    },\n\n    Settings {\n        max_field_section_size: Option<u64>,\n        qpack_max_table_capacity: Option<u64>,\n        qpack_blocked_streams: Option<u64>,\n        connect_protocol_enabled: Option<u64>,\n        h3_datagram: Option<u64>,\n        grease: Option<(u64, u64)>,\n        additional_settings: Option<Vec<(u64, u64)>>,\n        raw: Option<Vec<(u64, u64)>>,\n    },\n\n    PushPromise {\n        push_id: u64,\n        header_block: Vec<u8>,\n    },\n\n    GoAway {\n        id: u64,\n    },\n\n    MaxPushId {\n        push_id: u64,\n    },\n\n    PriorityUpdateRequest {\n        prioritized_element_id: u64,\n        priority_field_value: Vec<u8>,\n    },\n\n    PriorityUpdatePush {\n        prioritized_element_id: u64,\n        priority_field_value: Vec<u8>,\n    },\n\n    Unknown {\n        raw_type: u64,\n        payload: Vec<u8>,\n    },\n}\n\nimpl Frame {\n    pub fn from_bytes(\n        frame_type: u64, payload_length: u64, bytes: &[u8],\n    ) -> Result<Frame> {\n        let mut b = octets::Octets::with_slice(bytes);\n\n        // TODO: handling of 0-length frames\n        let frame = match frame_type {\n            DATA_FRAME_TYPE_ID => Frame::Data {\n                payload: b.get_bytes(payload_length as usize)?.to_vec(),\n            },\n\n            HEADERS_FRAME_TYPE_ID => Frame::Headers {\n                header_block: b.get_bytes(payload_length as usize)?.to_vec(),\n            },\n\n            CANCEL_PUSH_FRAME_TYPE_ID => Frame::CancelPush {\n                push_id: b.get_varint()?,\n            },\n\n            SETTINGS_FRAME_TYPE_ID =>\n                parse_settings_frame(&mut b, payload_length as usize)?,\n\n            PUSH_PROMISE_FRAME_TYPE_ID =>\n                parse_push_promise(payload_length, &mut b)?,\n\n            GOAWAY_FRAME_TYPE_ID => Frame::GoAway {\n                id: b.get_varint()?,\n            },\n\n            MAX_PUSH_FRAME_TYPE_ID => Frame::MaxPushId {\n                push_id: b.get_varint()?,\n            },\n\n            PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID |\n            PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID =>\n                parse_priority_update(frame_type, payload_length, &mut b)?,\n\n            _ => Frame::Unknown {\n                raw_type: frame_type,\n                payload: b.get_bytes(payload_length as usize)?.to_vec(),\n            },\n        };\n\n        Ok(frame)\n    }\n\n    pub fn to_bytes(&self, b: &mut octets::OctetsMut) -> Result<usize> {\n        let before = b.cap();\n\n        match self {\n            Frame::Data { payload } => {\n                b.put_varint(DATA_FRAME_TYPE_ID)?;\n                b.put_varint(payload.len() as u64)?;\n\n                b.put_bytes(payload.as_ref())?;\n            },\n\n            Frame::Headers { header_block } => {\n                b.put_varint(HEADERS_FRAME_TYPE_ID)?;\n                b.put_varint(header_block.len() as u64)?;\n\n                b.put_bytes(header_block.as_ref())?;\n            },\n\n            Frame::CancelPush { push_id } => {\n                b.put_varint(CANCEL_PUSH_FRAME_TYPE_ID)?;\n                b.put_varint(octets::varint_len(*push_id) as u64)?;\n\n                b.put_varint(*push_id)?;\n            },\n\n            Frame::Settings {\n                max_field_section_size,\n                qpack_max_table_capacity,\n                qpack_blocked_streams,\n                connect_protocol_enabled,\n                h3_datagram,\n                grease,\n                additional_settings,\n                ..\n            } => {\n                let mut len = 0;\n\n                if let Some(val) = max_field_section_size {\n                    len += octets::varint_len(SETTINGS_MAX_FIELD_SECTION_SIZE);\n                    len += octets::varint_len(*val);\n                }\n\n                if let Some(val) = qpack_max_table_capacity {\n                    len += octets::varint_len(SETTINGS_QPACK_MAX_TABLE_CAPACITY);\n                    len += octets::varint_len(*val);\n                }\n\n                if let Some(val) = qpack_blocked_streams {\n                    len += octets::varint_len(SETTINGS_QPACK_BLOCKED_STREAMS);\n                    len += octets::varint_len(*val);\n                }\n\n                if let Some(val) = connect_protocol_enabled {\n                    len += octets::varint_len(SETTINGS_ENABLE_CONNECT_PROTOCOL);\n                    len += octets::varint_len(*val);\n                }\n\n                if let Some(val) = h3_datagram {\n                    len += octets::varint_len(SETTINGS_H3_DATAGRAM_00);\n                    len += octets::varint_len(*val);\n                    len += octets::varint_len(SETTINGS_H3_DATAGRAM);\n                    len += octets::varint_len(*val);\n                }\n\n                if let Some(val) = grease {\n                    len += octets::varint_len(val.0);\n                    len += octets::varint_len(val.1);\n                }\n\n                if let Some(vals) = additional_settings {\n                    for val in vals {\n                        len += octets::varint_len(val.0);\n                        len += octets::varint_len(val.1);\n                    }\n                }\n\n                b.put_varint(SETTINGS_FRAME_TYPE_ID)?;\n                b.put_varint(len as u64)?;\n\n                if let Some(val) = max_field_section_size {\n                    b.put_varint(SETTINGS_MAX_FIELD_SECTION_SIZE)?;\n                    b.put_varint(*val)?;\n                }\n\n                if let Some(val) = qpack_max_table_capacity {\n                    b.put_varint(SETTINGS_QPACK_MAX_TABLE_CAPACITY)?;\n                    b.put_varint(*val)?;\n                }\n\n                if let Some(val) = qpack_blocked_streams {\n                    b.put_varint(SETTINGS_QPACK_BLOCKED_STREAMS)?;\n                    b.put_varint(*val)?;\n                }\n\n                if let Some(val) = connect_protocol_enabled {\n                    b.put_varint(SETTINGS_ENABLE_CONNECT_PROTOCOL)?;\n                    b.put_varint(*val)?;\n                }\n\n                if let Some(val) = h3_datagram {\n                    b.put_varint(SETTINGS_H3_DATAGRAM_00)?;\n                    b.put_varint(*val)?;\n                    b.put_varint(SETTINGS_H3_DATAGRAM)?;\n                    b.put_varint(*val)?;\n                }\n\n                if let Some(val) = grease {\n                    b.put_varint(val.0)?;\n                    b.put_varint(val.1)?;\n                }\n\n                if let Some(vals) = additional_settings {\n                    for val in vals {\n                        b.put_varint(val.0)?;\n                        b.put_varint(val.1)?;\n                    }\n                }\n            },\n\n            Frame::PushPromise {\n                push_id,\n                header_block,\n            } => {\n                let len = octets::varint_len(*push_id) + header_block.len();\n                b.put_varint(PUSH_PROMISE_FRAME_TYPE_ID)?;\n                b.put_varint(len as u64)?;\n\n                b.put_varint(*push_id)?;\n                b.put_bytes(header_block.as_ref())?;\n            },\n\n            Frame::GoAway { id } => {\n                b.put_varint(GOAWAY_FRAME_TYPE_ID)?;\n                b.put_varint(octets::varint_len(*id) as u64)?;\n\n                b.put_varint(*id)?;\n            },\n\n            Frame::MaxPushId { push_id } => {\n                b.put_varint(MAX_PUSH_FRAME_TYPE_ID)?;\n                b.put_varint(octets::varint_len(*push_id) as u64)?;\n\n                b.put_varint(*push_id)?;\n            },\n\n            Frame::PriorityUpdateRequest {\n                prioritized_element_id,\n                priority_field_value,\n            } => {\n                let len = octets::varint_len(*prioritized_element_id) +\n                    priority_field_value.len();\n\n                b.put_varint(PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID)?;\n                b.put_varint(len as u64)?;\n\n                b.put_varint(*prioritized_element_id)?;\n                b.put_bytes(priority_field_value)?;\n            },\n\n            Frame::PriorityUpdatePush {\n                prioritized_element_id,\n                priority_field_value,\n            } => {\n                let len = octets::varint_len(*prioritized_element_id) +\n                    priority_field_value.len();\n\n                b.put_varint(PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID)?;\n                b.put_varint(len as u64)?;\n\n                b.put_varint(*prioritized_element_id)?;\n                b.put_bytes(priority_field_value)?;\n            },\n\n            Frame::Unknown { raw_type, payload } => {\n                b.put_varint(*raw_type)?;\n                b.put_varint(payload.len() as u64)?;\n\n                b.put_bytes(payload.as_ref())?;\n            },\n        }\n\n        Ok(before - b.cap())\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub fn to_qlog(&self) -> Http3Frame {\n        use qlog::events::RawInfo;\n\n        match self {\n            Frame::Data { .. } => Http3Frame::Data { raw: None },\n\n            // Qlog expects the `headers` to be represented as an array of\n            // name:value pairs. At this stage, we only have the qpack block, so\n            // populate the field with an empty vec.\n            Frame::Headers { .. } => Http3Frame::Headers { headers: vec![] },\n\n            Frame::CancelPush { push_id } =>\n                Http3Frame::CancelPush { push_id: *push_id },\n\n            Frame::Settings {\n                max_field_section_size,\n                qpack_max_table_capacity,\n                qpack_blocked_streams,\n                connect_protocol_enabled,\n                h3_datagram,\n                grease,\n                additional_settings,\n                ..\n            } => {\n                let mut settings = vec![];\n\n                if let Some(v) = max_field_section_size {\n                    settings.push(qlog::events::http3::Setting {\n                        name: Some(\"MAX_FIELD_SECTION_SIZE\".to_string()),\n                        name_bytes: None,\n                        value: *v,\n                    });\n                }\n\n                if let Some(v) = qpack_max_table_capacity {\n                    settings.push(qlog::events::http3::Setting {\n                        name: Some(\"QPACK_MAX_TABLE_CAPACITY\".to_string()),\n                        name_bytes: None,\n                        value: *v,\n                    });\n                }\n\n                if let Some(v) = qpack_blocked_streams {\n                    settings.push(qlog::events::http3::Setting {\n                        name: Some(\"QPACK_BLOCKED_STREAMS\".to_string()),\n                        name_bytes: None,\n                        value: *v,\n                    });\n                }\n\n                if let Some(v) = connect_protocol_enabled {\n                    settings.push(qlog::events::http3::Setting {\n                        name: Some(\n                            \"SETTINGS_ENABLE_CONNECT_PROTOCOL\".to_string(),\n                        ),\n                        name_bytes: None,\n                        value: *v,\n                    });\n                }\n\n                if let Some(v) = h3_datagram {\n                    settings.push(qlog::events::http3::Setting {\n                        name: Some(\"H3_DATAGRAM\".to_string()),\n                        name_bytes: None,\n                        value: *v,\n                    });\n                }\n\n                if let Some((k, v)) = grease {\n                    settings.push(qlog::events::http3::Setting {\n                        name: Some(k.to_string()),\n                        name_bytes: None,\n                        value: *v,\n                    });\n                }\n\n                if let Some(additional_settings) = additional_settings {\n                    for (k, v) in additional_settings {\n                        settings.push(qlog::events::http3::Setting {\n                            name: Some(k.to_string()),\n                            name_bytes: None,\n                            value: *v,\n                        });\n                    }\n                }\n\n                Http3Frame::Settings { settings }\n            },\n\n            // Qlog expects the `headers` to be represented as an array of\n            // name:value pairs. At this stage, we only have the qpack block, so\n            // populate the field with an empty vec.\n            Frame::PushPromise { push_id, .. } => Http3Frame::PushPromise {\n                push_id: *push_id,\n                headers: vec![],\n            },\n\n            Frame::GoAway { id } => Http3Frame::Goaway { id: *id },\n\n            Frame::MaxPushId { push_id } =>\n                Http3Frame::MaxPushId { push_id: *push_id },\n\n            Frame::PriorityUpdateRequest {\n                prioritized_element_id,\n                priority_field_value,\n            } => Http3Frame::PriorityUpdate {\n                target_stream_type:\n                    qlog::events::http3::PriorityTargetStreamType::Request,\n                prioritized_element_id: *prioritized_element_id,\n                priority_field_value: String::from_utf8_lossy(\n                    priority_field_value,\n                )\n                .into_owned(),\n            },\n\n            Frame::PriorityUpdatePush {\n                prioritized_element_id,\n                priority_field_value,\n            } => Http3Frame::PriorityUpdate {\n                target_stream_type:\n                    qlog::events::http3::PriorityTargetStreamType::Request,\n                prioritized_element_id: *prioritized_element_id,\n                priority_field_value: String::from_utf8_lossy(\n                    priority_field_value,\n                )\n                .into_owned(),\n            },\n\n            Frame::Unknown { raw_type, payload } => Http3Frame::Unknown {\n                frame_type_value: *raw_type,\n                raw: Some(RawInfo {\n                    data: None,\n                    payload_length: Some(payload.len() as u64),\n                    length: None,\n                }),\n            },\n        }\n    }\n}\n\nimpl std::fmt::Debug for Frame {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self {\n            Frame::Data { .. } => {\n                write!(f, \"DATA\")?;\n            },\n\n            Frame::Headers { .. } => {\n                write!(f, \"HEADERS\")?;\n            },\n\n            Frame::CancelPush { push_id } => {\n                write!(f, \"CANCEL_PUSH push_id={push_id}\")?;\n            },\n\n            Frame::Settings {\n                max_field_section_size,\n                qpack_max_table_capacity,\n                qpack_blocked_streams,\n                additional_settings,\n                raw,\n                ..\n            } => {\n                write!(f, \"SETTINGS max_field_section={max_field_section_size:?}, qpack_max_table={qpack_max_table_capacity:?}, qpack_blocked={qpack_blocked_streams:?} raw={raw:?}, additional_settings={additional_settings:?}\")?;\n            },\n\n            Frame::PushPromise {\n                push_id,\n                header_block,\n            } => {\n                write!(\n                    f,\n                    \"PUSH_PROMISE push_id={} len={}\",\n                    push_id,\n                    header_block.len()\n                )?;\n            },\n\n            Frame::GoAway { id } => {\n                write!(f, \"GOAWAY id={id}\")?;\n            },\n\n            Frame::MaxPushId { push_id } => {\n                write!(f, \"MAX_PUSH_ID push_id={push_id}\")?;\n            },\n\n            Frame::PriorityUpdateRequest {\n                prioritized_element_id,\n                priority_field_value,\n            } => {\n                write!(\n                    f,\n                    \"PRIORITY_UPDATE request_stream_id={}, priority_field_len={}\",\n                    prioritized_element_id,\n                    priority_field_value.len()\n                )?;\n            },\n\n            Frame::PriorityUpdatePush {\n                prioritized_element_id,\n                priority_field_value,\n            } => {\n                write!(\n                    f,\n                    \"PRIORITY_UPDATE push_id={}, priority_field_len={}\",\n                    prioritized_element_id,\n                    priority_field_value.len()\n                )?;\n            },\n\n            Frame::Unknown { raw_type, .. } => {\n                write!(f, \"UNKNOWN raw_type={raw_type}\",)?;\n            },\n        }\n\n        Ok(())\n    }\n}\n\nfn parse_settings_frame(\n    b: &mut octets::Octets, settings_length: usize,\n) -> Result<Frame> {\n    let mut max_field_section_size = None;\n    let mut qpack_max_table_capacity = None;\n    let mut qpack_blocked_streams = None;\n    let mut connect_protocol_enabled = None;\n    let mut h3_datagram = None;\n    let mut raw = Vec::new();\n    let mut additional_settings: Option<Vec<(u64, u64)>> = None;\n\n    // Reject SETTINGS frames that are too long.\n    if settings_length > MAX_SETTINGS_PAYLOAD_SIZE {\n        return Err(super::Error::ExcessiveLoad);\n    }\n\n    while b.off() < settings_length {\n        let identifier = b.get_varint()?;\n        let value = b.get_varint()?;\n\n        // MAX_SETTINGS_PAYLOAD_SIZE protects us from storing too many raw\n        // settings.\n        raw.push((identifier, value));\n\n        match identifier {\n            SETTINGS_QPACK_MAX_TABLE_CAPACITY => {\n                qpack_max_table_capacity = Some(value);\n            },\n\n            SETTINGS_MAX_FIELD_SECTION_SIZE => {\n                max_field_section_size = Some(value);\n            },\n\n            SETTINGS_QPACK_BLOCKED_STREAMS => {\n                qpack_blocked_streams = Some(value);\n            },\n\n            SETTINGS_ENABLE_CONNECT_PROTOCOL => {\n                if value > 1 {\n                    return Err(super::Error::SettingsError);\n                }\n\n                connect_protocol_enabled = Some(value);\n            },\n\n            SETTINGS_H3_DATAGRAM_00 | SETTINGS_H3_DATAGRAM => {\n                if value > 1 {\n                    return Err(super::Error::SettingsError);\n                }\n\n                h3_datagram = Some(value);\n            },\n\n            // Reserved values overlap with HTTP/2 and MUST be rejected\n            0x0 | 0x2 | 0x3 | 0x4 | 0x5 =>\n                return Err(super::Error::SettingsError),\n\n            // Unknown Settings parameters go into additional_settings.\n            _ => {\n                let s: &mut Vec<(u64, u64)> =\n                    additional_settings.get_or_insert(vec![]);\n                s.push((identifier, value));\n            },\n        }\n    }\n\n    Ok(Frame::Settings {\n        max_field_section_size,\n        qpack_max_table_capacity,\n        qpack_blocked_streams,\n        connect_protocol_enabled,\n        h3_datagram,\n        grease: None,\n        raw: Some(raw),\n        additional_settings,\n    })\n}\n\nfn parse_push_promise(\n    payload_length: u64, b: &mut octets::Octets,\n) -> Result<Frame> {\n    let push_id = b.get_varint()?;\n    let header_block_length = payload_length - octets::varint_len(push_id) as u64;\n    let header_block = b.get_bytes(header_block_length as usize)?.to_vec();\n\n    Ok(Frame::PushPromise {\n        push_id,\n        header_block,\n    })\n}\n\nfn parse_priority_update(\n    frame_type: u64, payload_length: u64, b: &mut octets::Octets,\n) -> Result<Frame> {\n    let prioritized_element_id = b.get_varint()?;\n    let priority_field_value_length =\n        payload_length - octets::varint_len(prioritized_element_id) as u64;\n    let priority_field_value =\n        b.get_bytes(priority_field_value_length as usize)?.to_vec();\n\n    match frame_type {\n        PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID =>\n            Ok(Frame::PriorityUpdateRequest {\n                prioritized_element_id,\n                priority_field_value,\n            }),\n\n        PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID => Ok(Frame::PriorityUpdatePush {\n            prioritized_element_id,\n            priority_field_value,\n        }),\n\n        _ => unreachable!(),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn data() {\n        let mut d = [42; 128];\n\n        let payload = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let frame_payload_len = payload.len();\n        let frame_header_len = 2;\n\n        let frame = Frame::Data { payload };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                DATA_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn headers() {\n        let mut d = [42; 128];\n\n        let header_block = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let frame_payload_len = header_block.len();\n        let frame_header_len = 2;\n\n        let frame = Frame::Headers { header_block };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                HEADERS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn cancel_push() {\n        let mut d = [42; 128];\n\n        let frame = Frame::CancelPush { push_id: 0 };\n\n        let frame_payload_len = 1;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                CANCEL_PUSH_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn settings_all_no_grease() {\n        let mut d = [42; 128];\n\n        let raw_settings = vec![\n            (SETTINGS_MAX_FIELD_SECTION_SIZE, 0),\n            (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),\n            (SETTINGS_QPACK_BLOCKED_STREAMS, 0),\n            (SETTINGS_ENABLE_CONNECT_PROTOCOL, 0),\n            (SETTINGS_H3_DATAGRAM_00, 0),\n            (SETTINGS_H3_DATAGRAM, 0),\n        ];\n\n        let frame = Frame::Settings {\n            max_field_section_size: Some(0),\n            qpack_max_table_capacity: Some(0),\n            qpack_blocked_streams: Some(0),\n            connect_protocol_enabled: Some(0),\n            h3_datagram: Some(0),\n            grease: None,\n            raw: Some(raw_settings),\n            additional_settings: None,\n        };\n\n        let frame_payload_len = 13;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn settings_all_grease() {\n        let mut d = [42; 128];\n\n        let frame = Frame::Settings {\n            max_field_section_size: Some(0),\n            qpack_max_table_capacity: Some(0),\n            qpack_blocked_streams: Some(0),\n            connect_protocol_enabled: Some(0),\n            h3_datagram: Some(0),\n            grease: Some((33, 33)),\n            raw: Default::default(),\n            additional_settings: None,\n        };\n\n        let raw_settings = vec![\n            (SETTINGS_MAX_FIELD_SECTION_SIZE, 0),\n            (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),\n            (SETTINGS_QPACK_BLOCKED_STREAMS, 0),\n            (SETTINGS_ENABLE_CONNECT_PROTOCOL, 0),\n            (SETTINGS_H3_DATAGRAM_00, 0),\n            (SETTINGS_H3_DATAGRAM, 0),\n            (33, 33),\n        ];\n\n        // Frame parsing will not populate GREASE property but will be in the\n        // raw info.\n        let frame_parsed = Frame::Settings {\n            max_field_section_size: Some(0),\n            qpack_max_table_capacity: Some(0),\n            qpack_blocked_streams: Some(0),\n            connect_protocol_enabled: Some(0),\n            h3_datagram: Some(0),\n            grease: None,\n            raw: Some(raw_settings),\n            additional_settings: Some(vec![(33, 33)]),\n        };\n\n        let frame_payload_len = 15;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame_parsed\n        );\n    }\n\n    #[test]\n    fn settings_h3_only() {\n        let mut d = [42; 128];\n\n        let raw_settings = vec![(SETTINGS_MAX_FIELD_SECTION_SIZE, 1024)];\n\n        let frame = Frame::Settings {\n            max_field_section_size: Some(1024),\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            raw: Some(raw_settings),\n            additional_settings: None,\n        };\n\n        let frame_payload_len = 3;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn settings_h3_connect_protocol_enabled() {\n        let mut d = [42; 128];\n\n        let raw_settings = vec![(SETTINGS_ENABLE_CONNECT_PROTOCOL, 1)];\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: Some(1),\n            h3_datagram: None,\n            grease: None,\n            raw: Some(raw_settings),\n            additional_settings: None,\n        };\n\n        let frame_payload_len = 2;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn settings_h3_connect_protocol_enabled_bad() {\n        let mut d = [42; 128];\n\n        let raw_settings = vec![(SETTINGS_ENABLE_CONNECT_PROTOCOL, 9)];\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: Some(9),\n            h3_datagram: None,\n            grease: None,\n            raw: Some(raw_settings),\n            additional_settings: None,\n        };\n\n        let frame_payload_len = 2;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            ),\n            Err(crate::h3::Error::SettingsError)\n        );\n    }\n\n    #[test]\n    fn settings_h3_dgram_only() {\n        let mut d = [42; 128];\n\n        let raw_settings =\n            vec![(SETTINGS_H3_DATAGRAM_00, 1), (SETTINGS_H3_DATAGRAM, 1)];\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: Some(1),\n            grease: None,\n            raw: Some(raw_settings),\n            additional_settings: None,\n        };\n\n        let frame_payload_len = 5;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn settings_h3_dgram_bad() {\n        let mut d = [42; 128];\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: Some(5),\n            grease: None,\n            raw: Default::default(),\n            additional_settings: None,\n        };\n\n        let frame_payload_len = 5;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            ),\n            Err(crate::h3::Error::SettingsError)\n        );\n    }\n\n    #[test]\n    fn settings_qpack_only() {\n        let mut d = [42; 128];\n\n        let raw_settings = vec![\n            (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),\n            (SETTINGS_QPACK_BLOCKED_STREAMS, 0),\n        ];\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: Some(0),\n            qpack_blocked_streams: Some(0),\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            raw: Some(raw_settings),\n            additional_settings: None,\n        };\n\n        let frame_payload_len = 4;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn settings_h2_prohibited() {\n        // We need to test the prohibited values (0x0 | 0x2 | 0x3 | 0x4 | 0x5)\n        // but the quiche API doesn't support that, so use a manually created\n        // frame data buffer where d[frame_header_len] is the SETTING type field.\n        let frame_payload_len = 2u64;\n        let frame_header_len = 2;\n        let mut d = [\n            SETTINGS_FRAME_TYPE_ID as u8,\n            frame_payload_len as u8,\n            0x0,\n            1,\n        ];\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len,\n                &d[frame_header_len..]\n            ),\n            Err(crate::h3::Error::SettingsError)\n        );\n\n        d[frame_header_len] = 0x2;\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len,\n                &d[frame_header_len..]\n            ),\n            Err(crate::h3::Error::SettingsError)\n        );\n\n        d[frame_header_len] = 0x3;\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len,\n                &d[frame_header_len..]\n            ),\n            Err(crate::h3::Error::SettingsError)\n        );\n\n        d[frame_header_len] = 0x4;\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len,\n                &d[frame_header_len..]\n            ),\n            Err(crate::h3::Error::SettingsError)\n        );\n\n        d[frame_header_len] = 0x5;\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len,\n                &d[frame_header_len..]\n            ),\n            Err(crate::h3::Error::SettingsError)\n        );\n    }\n\n    #[test]\n    fn settings_too_big() {\n        // We need to test a SETTINGS frame that exceeds\n        // MAX_SETTINGS_PAYLOAD_SIZE, so just craft a special buffer that look\n        // likes the frame. The payload content doesn't matter since quiche\n        // should abort before then.\n        let frame_payload_len = MAX_SETTINGS_PAYLOAD_SIZE + 1;\n        let frame_header_len = 2;\n        let d = [\n            SETTINGS_FRAME_TYPE_ID as u8,\n            frame_payload_len as u8,\n            0x1,\n            1,\n        ];\n\n        assert_eq!(\n            Frame::from_bytes(\n                SETTINGS_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            ),\n            Err(crate::h3::Error::ExcessiveLoad)\n        );\n    }\n\n    #[test]\n    fn push_promise() {\n        let mut d = [42; 128];\n\n        let header_block = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let frame_payload_len = 1 + header_block.len();\n        let frame_header_len = 2;\n\n        let frame = Frame::PushPromise {\n            push_id: 0,\n            header_block,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                PUSH_PROMISE_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn goaway() {\n        let mut d = [42; 128];\n\n        let frame = Frame::GoAway { id: 32 };\n\n        let frame_payload_len = 1;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                GOAWAY_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn max_push_id() {\n        let mut d = [42; 128];\n\n        let frame = Frame::MaxPushId { push_id: 128 };\n\n        let frame_payload_len = 2;\n        let frame_header_len = 2;\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                MAX_PUSH_FRAME_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn priority_update_request() {\n        let mut d = [42; 128];\n\n        let prioritized_element_id = 4;\n        let priority_field_value = b\"abcdefghijklm\".to_vec();\n        let frame_payload_len = 1 + priority_field_value.len();\n        let frame_header_len = 5;\n\n        let frame = Frame::PriorityUpdateRequest {\n            prioritized_element_id,\n            priority_field_value,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn priority_update_push() {\n        let mut d = [42; 128];\n\n        let prioritized_element_id = 6;\n        let priority_field_value = b\"abcdefghijklm\".to_vec();\n        let frame_payload_len = 1 + priority_field_value.len();\n        let frame_header_len = 5;\n\n        let frame = Frame::PriorityUpdatePush {\n            prioritized_element_id,\n            priority_field_value,\n        };\n\n        let wire_len = {\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n            frame.to_bytes(&mut b).unwrap()\n        };\n\n        assert_eq!(wire_len, frame_header_len + frame_payload_len);\n\n        assert_eq!(\n            Frame::from_bytes(\n                PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID,\n                frame_payload_len as u64,\n                &d[frame_header_len..]\n            )\n            .unwrap(),\n            frame\n        );\n    }\n\n    #[test]\n    fn unknown_type() {\n        let d = [42; 12];\n\n        assert_eq!(\n            Frame::from_bytes(255, 12, &d[..]),\n            Ok(Frame::Unknown {\n                raw_type: 255,\n                payload: vec![42; 12]\n            })\n        );\n    }\n}\n"
  },
  {
    "path": "quiche/src/h3/mod.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! HTTP/3 wire protocol and QPACK implementation.\n//!\n//! This module provides a high level API for sending and receiving HTTP/3\n//! requests and responses on top of the QUIC transport protocol.\n//!\n//! ## Connection setup\n//!\n//! HTTP/3 connections require a QUIC transport-layer connection, see\n//! [Connection setup] for a full description of the setup process.\n//!\n//! To use HTTP/3, the QUIC connection must be configured with a suitable\n//! Application Layer Protocol Negotiation (ALPN) Protocol ID:\n//!\n//! ```\n//! let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! config.set_application_protos(quiche::h3::APPLICATION_PROTOCOL)?;\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! The QUIC handshake is driven by [sending] and [receiving] QUIC packets.\n//!\n//! Once the handshake has completed, the first step in establishing an HTTP/3\n//! connection is creating its configuration object:\n//!\n//! ```\n//! let h3_config = quiche::h3::Config::new()?;\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! HTTP/3 client and server connections are both created using the\n//! [`with_transport()`] function, the role is inferred from the type of QUIC\n//! connection:\n//!\n//! ```no_run\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config).unwrap();\n//! # let h3_config = quiche::h3::Config::new()?;\n//! let h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?;\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! ## Sending a request\n//!\n//! An HTTP/3 client can send a request by using the connection's\n//! [`send_request()`] method to queue request headers; [sending] QUIC packets\n//! causes the requests to get sent to the peer:\n//!\n//! ```no_run\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::connect(None, &scid, local, peer, &mut config).unwrap();\n//! # let h3_config = quiche::h3::Config::new()?;\n//! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?;\n//! let req = vec![\n//!     quiche::h3::Header::new(b\":method\", b\"GET\"),\n//!     quiche::h3::Header::new(b\":scheme\", b\"https\"),\n//!     quiche::h3::Header::new(b\":authority\", b\"quic.tech\"),\n//!     quiche::h3::Header::new(b\":path\", b\"/\"),\n//!     quiche::h3::Header::new(b\"user-agent\", b\"quiche\"),\n//! ];\n//!\n//! h3_conn.send_request(&mut conn, &req, true)?;\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! An HTTP/3 client can send a request with additional body data by using\n//! the connection's [`send_body()`] method:\n//!\n//! ```no_run\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::connect(None, &scid, local, peer, &mut config).unwrap();\n//! # let h3_config = quiche::h3::Config::new()?;\n//! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?;\n//! let req = vec![\n//!     quiche::h3::Header::new(b\":method\", b\"GET\"),\n//!     quiche::h3::Header::new(b\":scheme\", b\"https\"),\n//!     quiche::h3::Header::new(b\":authority\", b\"quic.tech\"),\n//!     quiche::h3::Header::new(b\":path\", b\"/\"),\n//!     quiche::h3::Header::new(b\"user-agent\", b\"quiche\"),\n//! ];\n//!\n//! let stream_id = h3_conn.send_request(&mut conn, &req, false)?;\n//! h3_conn.send_body(&mut conn, stream_id, b\"Hello World!\", true)?;\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! ## Handling requests and responses\n//!\n//! After [receiving] QUIC packets, HTTP/3 data is processed using the\n//! connection's [`poll()`] method. On success, this returns an [`Event`] object\n//! and an ID corresponding to the stream where the `Event` originated.\n//!\n//! An HTTP/3 server uses [`poll()`] to read requests and responds to them using\n//! [`send_response()`] and [`send_body()`]:\n//!\n//! ```no_run\n//! use quiche::h3::NameValue;\n//!\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:1234\".parse().unwrap();\n//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config).unwrap();\n//! # let h3_config = quiche::h3::Config::new()?;\n//! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?;\n//! loop {\n//!     match h3_conn.poll(&mut conn) {\n//!         Ok((stream_id, quiche::h3::Event::Headers{list, more_frames})) => {\n//!             let mut headers = list.into_iter();\n//!\n//!             // Look for the request's method.\n//!             let method = headers.find(|h| h.name() == b\":method\").unwrap();\n//!\n//!             // Look for the request's path.\n//!             let path = headers.find(|h| h.name() == b\":path\").unwrap();\n//!\n//!             if method.value() == b\"GET\" && path.value() == b\"/\" {\n//!                 let resp = vec![\n//!                     quiche::h3::Header::new(b\":status\", 200.to_string().as_bytes()),\n//!                     quiche::h3::Header::new(b\"server\", b\"quiche\"),\n//!                 ];\n//!\n//!                 h3_conn.send_response(&mut conn, stream_id, &resp, false)?;\n//!                 h3_conn.send_body(&mut conn, stream_id, b\"Hello World!\", true)?;\n//!             }\n//!         },\n//!\n//!         Ok((stream_id, quiche::h3::Event::Data)) => {\n//!             // Request body data, handle it.\n//!             # return Ok(());\n//!         },\n//!\n//!         Ok((stream_id, quiche::h3::Event::Finished)) => {\n//!             // Peer terminated stream, handle it.\n//!         },\n//!\n//!         Ok((stream_id, quiche::h3::Event::Reset(err))) => {\n//!             // Peer reset the stream, handle it.\n//!         },\n//!\n//!         Ok((_flow_id, quiche::h3::Event::PriorityUpdate)) => (),\n//!\n//!         Ok((goaway_id, quiche::h3::Event::GoAway)) => {\n//!              // Peer signalled it is going away, handle it.\n//!         },\n//!\n//!         Err(quiche::h3::Error::Done) => {\n//!             // Done reading.\n//!             break;\n//!         },\n//!\n//!         Err(e) => {\n//!             // An error occurred, handle it.\n//!             break;\n//!         },\n//!     }\n//! }\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! An HTTP/3 client uses [`poll()`] to read responses:\n//!\n//! ```no_run\n//! use quiche::h3::NameValue;\n//!\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:1234\".parse().unwrap();\n//! # let mut conn = quiche::connect(None, &scid, local, peer, &mut config).unwrap();\n//! # let h3_config = quiche::h3::Config::new()?;\n//! # let mut h3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?;\n//! loop {\n//!     match h3_conn.poll(&mut conn) {\n//!         Ok((stream_id, quiche::h3::Event::Headers{list, more_frames})) => {\n//!             let status = list.iter().find(|h| h.name() == b\":status\").unwrap();\n//!             println!(\"Received {} response on stream {}\",\n//!                      std::str::from_utf8(status.value()).unwrap(),\n//!                      stream_id);\n//!         },\n//!\n//!         Ok((stream_id, quiche::h3::Event::Data)) => {\n//!             let mut body = vec![0; 4096];\n//!\n//!             // Consume all body data received on the stream.\n//!             while let Ok(read) =\n//!                 h3_conn.recv_body(&mut conn, stream_id, &mut body)\n//!             {\n//!                 println!(\"Received {} bytes of payload on stream {}\",\n//!                          read, stream_id);\n//!             }\n//!         },\n//!\n//!         Ok((stream_id, quiche::h3::Event::Finished)) => {\n//!             // Peer terminated stream, handle it.\n//!         },\n//!\n//!         Ok((stream_id, quiche::h3::Event::Reset(err))) => {\n//!             // Peer reset the stream, handle it.\n//!         },\n//!\n//!         Ok((_prioritized_element_id, quiche::h3::Event::PriorityUpdate)) => (),\n//!\n//!         Ok((goaway_id, quiche::h3::Event::GoAway)) => {\n//!              // Peer signalled it is going away, handle it.\n//!         },\n//!\n//!         Err(quiche::h3::Error::Done) => {\n//!             // Done reading.\n//!             break;\n//!         },\n//!\n//!         Err(e) => {\n//!             // An error occurred, handle it.\n//!             break;\n//!         },\n//!     }\n//! }\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! ## Detecting end of request or response\n//!\n//! A single HTTP/3 request or response may consist of several HEADERS and DATA\n//! frames; it is finished when the QUIC stream is closed. Calling [`poll()`]\n//! repeatedly will generate an [`Event`] for each of these. The application may\n//! use these event to do additional HTTP semantic validation.\n//!\n//! ## HTTP/3 protocol errors\n//!\n//! Quiche is responsible for managing the HTTP/3 connection, ensuring it is in\n//! a correct state and validating all messages received by a peer. This mainly\n//! takes place in the [`poll()`] method. If an HTTP/3 error occurs, quiche will\n//! close the connection and send an appropriate CONNECTION_CLOSE frame to the\n//! peer. An [`Error`] is returned to the application so that it can perform any\n//! required tidy up such as closing sockets.\n//!\n//! [`application_proto()`]: ../struct.Connection.html#method.application_proto\n//! [`stream_finished()`]: ../struct.Connection.html#method.stream_finished\n//! [Connection setup]: ../index.html#connection-setup\n//! [sending]: ../index.html#generating-outgoing-packets\n//! [receiving]: ../index.html#handling-incoming-packets\n//! [`with_transport()`]: struct.Connection.html#method.with_transport\n//! [`poll()`]: struct.Connection.html#method.poll\n//! [`Event`]: enum.Event.html\n//! [`Error`]: enum.Error.html\n//! [`send_request()`]: struct.Connection.html#method.send_response\n//! [`send_response()`]: struct.Connection.html#method.send_response\n//! [`send_body()`]: struct.Connection.html#method.send_body\n\nuse std::collections::HashSet;\nuse std::collections::VecDeque;\n\n#[cfg(feature = \"sfv\")]\nuse std::convert::TryFrom;\nuse std::fmt;\nuse std::fmt::Write;\n\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::FrameCreated;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::FrameParsed;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::Http3EventType;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::Http3Frame;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::Initiator;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::PriorityTargetStreamType;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::StreamType;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::http3::StreamTypeSet;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventData;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventImportance;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventType;\n\nuse crate::buffers::BufFactory;\nuse crate::BufSplit;\n\n/// List of ALPN tokens of supported HTTP/3 versions.\n///\n/// This can be passed directly to the [`Config::set_application_protos()`]\n/// method when implementing HTTP/3 applications.\n///\n/// [`Config::set_application_protos()`]:\n/// ../struct.Config.html#method.set_application_protos\npub const APPLICATION_PROTOCOL: &[&[u8]] = &[b\"h3\"];\n\n// The offset used when converting HTTP/3 urgency to quiche urgency.\nconst PRIORITY_URGENCY_OFFSET: u8 = 124;\n\n// Parameter values as specified in [Extensible Priorities].\n//\n// [Extensible Priorities]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4.\nconst PRIORITY_URGENCY_LOWER_BOUND: u8 = 0;\nconst PRIORITY_URGENCY_UPPER_BOUND: u8 = 7;\nconst PRIORITY_URGENCY_DEFAULT: u8 = 3;\nconst PRIORITY_INCREMENTAL_DEFAULT: bool = false;\n\n#[cfg(feature = \"qlog\")]\nconst QLOG_FRAME_CREATED: EventType =\n    EventType::Http3EventType(Http3EventType::FrameCreated);\n#[cfg(feature = \"qlog\")]\nconst QLOG_FRAME_PARSED: EventType =\n    EventType::Http3EventType(Http3EventType::FrameParsed);\n#[cfg(feature = \"qlog\")]\nconst QLOG_STREAM_TYPE_SET: EventType =\n    EventType::Http3EventType(Http3EventType::StreamTypeSet);\n\n/// A specialized [`Result`] type for quiche HTTP/3 operations.\n///\n/// This type is used throughout quiche's HTTP/3 public API for any operation\n/// that can produce an error.\n///\n/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html\npub type Result<T> = std::result::Result<T, Error>;\n\n/// An HTTP/3 error.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum Error {\n    /// There is no error or no work to do\n    Done,\n\n    /// The provided buffer is too short.\n    BufferTooShort,\n\n    /// Internal error in the HTTP/3 stack.\n    InternalError,\n\n    /// Endpoint detected that the peer is exhibiting behavior that causes.\n    /// excessive load.\n    ExcessiveLoad,\n\n    /// Stream ID or Push ID greater that current maximum was\n    /// used incorrectly, such as exceeding a limit, reducing a limit,\n    /// or being reused.\n    IdError,\n\n    /// The endpoint detected that its peer created a stream that it will not\n    /// accept.\n    StreamCreationError,\n\n    /// A required critical stream was closed.\n    ClosedCriticalStream,\n\n    /// No SETTINGS frame at beginning of control stream.\n    MissingSettings,\n\n    /// A frame was received which is not permitted in the current state.\n    FrameUnexpected,\n\n    /// Frame violated layout or size rules.\n    FrameError,\n\n    /// QPACK Header block decompression failure.\n    QpackDecompressionFailed,\n\n    /// Error originated from the transport layer.\n    TransportError(crate::Error),\n\n    /// The underlying QUIC stream (or connection) doesn't have enough capacity\n    /// for the operation to complete. The application should retry later on.\n    StreamBlocked,\n\n    /// Error in the payload of a SETTINGS frame.\n    SettingsError,\n\n    /// Server rejected request.\n    RequestRejected,\n\n    /// Request or its response cancelled.\n    RequestCancelled,\n\n    /// Client's request stream terminated without containing a full-formed\n    /// request.\n    RequestIncomplete,\n\n    /// An HTTP message was malformed and cannot be processed.\n    MessageError,\n\n    /// The TCP connection established in response to a CONNECT request was\n    /// reset or abnormally closed.\n    ConnectError,\n\n    /// The requested operation cannot be served over HTTP/3. Peer should retry\n    /// over HTTP/1.1.\n    VersionFallback,\n}\n\n/// HTTP/3 error codes sent on the wire.\n///\n/// As defined in [RFC9114](https://www.rfc-editor.org/rfc/rfc9114.html#http-error-codes).\n#[derive(Copy, Clone, Debug, Eq, PartialEq)]\npub enum WireErrorCode {\n    /// No error. This is used when the connection or stream needs to be closed,\n    /// but there is no error to signal.\n    NoError              = 0x100,\n    /// Peer violated protocol requirements in a way that does not match a more\n    /// specific error code or endpoint declines to use the more specific\n    /// error code.\n    GeneralProtocolError = 0x101,\n    /// An internal error has occurred in the HTTP stack.\n    InternalError        = 0x102,\n    /// The endpoint detected that its peer created a stream that it will not\n    /// accept.\n    StreamCreationError  = 0x103,\n    /// A stream required by the HTTP/3 connection was closed or reset.\n    ClosedCriticalStream = 0x104,\n    /// A frame was received that was not permitted in the current state or on\n    /// the current stream.\n    FrameUnexpected      = 0x105,\n    /// A frame that fails to satisfy layout requirements or with an invalid\n    /// size was received.\n    FrameError           = 0x106,\n    /// The endpoint detected that its peer is exhibiting a behavior that might\n    /// be generating excessive load.\n    ExcessiveLoad        = 0x107,\n    /// A stream ID or push ID was used incorrectly, such as exceeding a limit,\n    /// reducing a limit, or being reused.\n    IdError              = 0x108,\n    /// An endpoint detected an error in the payload of a SETTINGS frame.\n    SettingsError        = 0x109,\n    /// No SETTINGS frame was received at the beginning of the control stream.\n    MissingSettings      = 0x10a,\n    /// A server rejected a request without performing any application\n    /// processing.\n    RequestRejected      = 0x10b,\n    /// The request or its response (including pushed response) is cancelled.\n    RequestCancelled     = 0x10c,\n    /// The client's stream terminated without containing a fully formed\n    /// request.\n    RequestIncomplete    = 0x10d,\n    /// An HTTP message was malformed and cannot be processed.\n    MessageError         = 0x10e,\n    /// The TCP connection established in response to a CONNECT request was\n    /// reset or abnormally closed.\n    ConnectError         = 0x10f,\n    /// The requested operation cannot be served over HTTP/3. The peer should\n    /// retry over HTTP/1.1.\n    VersionFallback      = 0x110,\n}\n\nimpl Error {\n    fn to_wire(self) -> u64 {\n        match self {\n            Error::Done => WireErrorCode::NoError as u64,\n            Error::InternalError => WireErrorCode::InternalError as u64,\n            Error::StreamCreationError =>\n                WireErrorCode::StreamCreationError as u64,\n            Error::ClosedCriticalStream =>\n                WireErrorCode::ClosedCriticalStream as u64,\n            Error::FrameUnexpected => WireErrorCode::FrameUnexpected as u64,\n            Error::FrameError => WireErrorCode::FrameError as u64,\n            Error::ExcessiveLoad => WireErrorCode::ExcessiveLoad as u64,\n            Error::IdError => WireErrorCode::IdError as u64,\n            Error::MissingSettings => WireErrorCode::MissingSettings as u64,\n            Error::QpackDecompressionFailed => 0x200,\n            Error::BufferTooShort => 0x999,\n            Error::TransportError { .. } | Error::StreamBlocked => 0xFF,\n            Error::SettingsError => WireErrorCode::SettingsError as u64,\n            Error::RequestRejected => WireErrorCode::RequestRejected as u64,\n            Error::RequestCancelled => WireErrorCode::RequestCancelled as u64,\n            Error::RequestIncomplete => WireErrorCode::RequestIncomplete as u64,\n            Error::MessageError => WireErrorCode::MessageError as u64,\n            Error::ConnectError => WireErrorCode::ConnectError as u64,\n            Error::VersionFallback => WireErrorCode::VersionFallback as u64,\n        }\n    }\n\n    #[cfg(feature = \"ffi\")]\n    fn to_c(self) -> libc::ssize_t {\n        match self {\n            Error::Done => -1,\n            Error::BufferTooShort => -2,\n            Error::InternalError => -3,\n            Error::ExcessiveLoad => -4,\n            Error::IdError => -5,\n            Error::StreamCreationError => -6,\n            Error::ClosedCriticalStream => -7,\n            Error::MissingSettings => -8,\n            Error::FrameUnexpected => -9,\n            Error::FrameError => -10,\n            Error::QpackDecompressionFailed => -11,\n            // -12 was previously used for TransportError, skip it\n            Error::StreamBlocked => -13,\n            Error::SettingsError => -14,\n            Error::RequestRejected => -15,\n            Error::RequestCancelled => -16,\n            Error::RequestIncomplete => -17,\n            Error::MessageError => -18,\n            Error::ConnectError => -19,\n            Error::VersionFallback => -20,\n\n            Error::TransportError(quic_error) => quic_error.to_c() - 1000,\n        }\n    }\n}\n\nimpl fmt::Display for Error {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl std::error::Error for Error {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        None\n    }\n}\n\nimpl From<super::Error> for Error {\n    fn from(err: super::Error) -> Self {\n        match err {\n            super::Error::Done => Error::Done,\n\n            _ => Error::TransportError(err),\n        }\n    }\n}\n\nimpl From<octets::BufferTooShortError> for Error {\n    fn from(_err: octets::BufferTooShortError) -> Self {\n        Error::BufferTooShort\n    }\n}\n\n/// An HTTP/3 configuration.\npub struct Config {\n    max_field_section_size: Option<u64>,\n    qpack_max_table_capacity: Option<u64>,\n    qpack_blocked_streams: Option<u64>,\n    connect_protocol_enabled: Option<u64>,\n    /// additional settings are settings that are not part of the H3\n    /// settings explicitly handled above\n    additional_settings: Option<Vec<(u64, u64)>>,\n}\n\nimpl Config {\n    /// Creates a new configuration object with default settings.\n    pub const fn new() -> Result<Config> {\n        Ok(Config {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            additional_settings: None,\n        })\n    }\n\n    /// Sets the `SETTINGS_MAX_FIELD_SECTION_SIZE` setting.\n    ///\n    /// By default no limit is enforced. When a request whose headers exceed\n    /// the limit set by the application is received, the call to the [`poll()`]\n    /// method will return the [`Error::ExcessiveLoad`] error, and the\n    /// connection will be closed.\n    ///\n    /// [`poll()`]: struct.Connection.html#method.poll\n    /// [`Error::ExcessiveLoad`]: enum.Error.html#variant.ExcessiveLoad\n    pub fn set_max_field_section_size(&mut self, v: u64) {\n        self.max_field_section_size = Some(v);\n    }\n\n    /// Sets the `SETTINGS_QPACK_MAX_TABLE_CAPACITY` setting.\n    ///\n    /// The default value is `0`.\n    pub fn set_qpack_max_table_capacity(&mut self, v: u64) {\n        self.qpack_max_table_capacity = Some(v);\n    }\n\n    /// Sets the `SETTINGS_QPACK_BLOCKED_STREAMS` setting.\n    ///\n    /// The default value is `0`.\n    pub fn set_qpack_blocked_streams(&mut self, v: u64) {\n        self.qpack_blocked_streams = Some(v);\n    }\n\n    /// Sets or omits the `SETTINGS_ENABLE_CONNECT_PROTOCOL` setting.\n    ///\n    /// The default value is `false`.\n    pub fn enable_extended_connect(&mut self, enabled: bool) {\n        if enabled {\n            self.connect_protocol_enabled = Some(1);\n        } else {\n            self.connect_protocol_enabled = None;\n        }\n    }\n\n    /// Sets additional HTTP/3 settings.\n    ///\n    /// The default value is no additional settings.\n    /// The `additional_settings` parameter must not the following\n    /// settings as they are already handled by this library:\n    ///\n    /// - SETTINGS_QPACK_MAX_TABLE_CAPACITY\n    /// - SETTINGS_MAX_FIELD_SECTION_SIZE\n    /// - SETTINGS_QPACK_BLOCKED_STREAMS\n    /// - SETTINGS_ENABLE_CONNECT_PROTOCOL\n    /// - SETTINGS_H3_DATAGRAM\n    ///\n    /// If such a setting is present in the `additional_settings`,\n    /// the method will return the [`Error::SettingsError`] error.\n    ///\n    /// If a setting identifier is present twice in `additional_settings`,\n    /// the method will return the [`Error::SettingsError`] error.\n    ///\n    /// [`Error::SettingsError`]: enum.Error.html#variant.SettingsError\n    pub fn set_additional_settings(\n        &mut self, additional_settings: Vec<(u64, u64)>,\n    ) -> Result<()> {\n        let explicit_quiche_settings = HashSet::from([\n            frame::SETTINGS_QPACK_MAX_TABLE_CAPACITY,\n            frame::SETTINGS_MAX_FIELD_SECTION_SIZE,\n            frame::SETTINGS_QPACK_BLOCKED_STREAMS,\n            frame::SETTINGS_ENABLE_CONNECT_PROTOCOL,\n            frame::SETTINGS_H3_DATAGRAM,\n            frame::SETTINGS_H3_DATAGRAM_00,\n        ]);\n\n        let dedup_settings: HashSet<u64> =\n            additional_settings.iter().map(|(key, _)| *key).collect();\n\n        if dedup_settings.len() != additional_settings.len() ||\n            !explicit_quiche_settings.is_disjoint(&dedup_settings)\n        {\n            return Err(Error::SettingsError);\n        }\n        self.additional_settings = Some(additional_settings);\n        Ok(())\n    }\n}\n\n/// A trait for types with associated string name and value.\npub trait NameValue {\n    /// Returns the object's name.\n    fn name(&self) -> &[u8];\n\n    /// Returns the object's value.\n    fn value(&self) -> &[u8];\n}\n\nimpl<N, V> NameValue for (N, V)\nwhere\n    N: AsRef<[u8]>,\n    V: AsRef<[u8]>,\n{\n    fn name(&self) -> &[u8] {\n        self.0.as_ref()\n    }\n\n    fn value(&self) -> &[u8] {\n        self.1.as_ref()\n    }\n}\n\n/// An owned name-value pair representing a raw HTTP header.\n#[derive(Clone, PartialEq, Eq)]\npub struct Header(Vec<u8>, Vec<u8>);\n\nfn try_print_as_readable(hdr: &[u8], f: &mut fmt::Formatter) -> fmt::Result {\n    match std::str::from_utf8(hdr) {\n        Ok(s) => f.write_str(&s.escape_default().to_string()),\n        Err(_) => write!(f, \"{hdr:?}\"),\n    }\n}\n\nimpl fmt::Debug for Header {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.write_char('\"')?;\n        try_print_as_readable(&self.0, f)?;\n        f.write_str(\": \")?;\n        try_print_as_readable(&self.1, f)?;\n        f.write_char('\"')\n    }\n}\n\nimpl Header {\n    /// Creates a new header.\n    ///\n    /// Both `name` and `value` will be cloned.\n    pub fn new(name: &[u8], value: &[u8]) -> Self {\n        Self(name.to_vec(), value.to_vec())\n    }\n}\n\nimpl NameValue for Header {\n    fn name(&self) -> &[u8] {\n        &self.0\n    }\n\n    fn value(&self) -> &[u8] {\n        &self.1\n    }\n}\n\n/// A non-owned name-value pair representing a raw HTTP header.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct HeaderRef<'a>(&'a [u8], &'a [u8]);\n\nimpl<'a> HeaderRef<'a> {\n    /// Creates a new header.\n    pub const fn new(name: &'a [u8], value: &'a [u8]) -> Self {\n        Self(name, value)\n    }\n}\n\nimpl NameValue for HeaderRef<'_> {\n    fn name(&self) -> &[u8] {\n        self.0\n    }\n\n    fn value(&self) -> &[u8] {\n        self.1\n    }\n}\n\n/// An HTTP/3 connection event.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Event {\n    /// Request/response headers were received.\n    Headers {\n        /// The list of received header fields. The application should validate\n        /// pseudo-headers and headers.\n        list: Vec<Header>,\n\n        /// Whether more frames will follow the headers on the stream.\n        more_frames: bool,\n    },\n\n    /// Data was received.\n    ///\n    /// This indicates that the application can use the [`recv_body()`] method\n    /// to retrieve the data from the stream.\n    ///\n    /// Note that [`recv_body()`] will need to be called repeatedly until the\n    /// [`Done`] value is returned, as the event will not be re-armed until all\n    /// buffered data is read.\n    ///\n    /// [`recv_body()`]: struct.Connection.html#method.recv_body\n    /// [`Done`]: enum.Error.html#variant.Done\n    Data,\n\n    /// Stream was closed,\n    Finished,\n\n    /// Stream was reset.\n    ///\n    /// The associated data represents the error code sent by the peer.\n    Reset(u64),\n\n    /// PRIORITY_UPDATE was received.\n    ///\n    /// This indicates that the application can use the\n    /// [`take_last_priority_update()`] method to take the last received\n    /// PRIORITY_UPDATE for a specified stream.\n    ///\n    /// This event is triggered once per stream until the last PRIORITY_UPDATE\n    /// is taken. It is recommended that applications defer taking the\n    /// PRIORITY_UPDATE until after [`poll()`] returns [`Done`].\n    ///\n    /// [`take_last_priority_update()`]: struct.Connection.html#method.take_last_priority_update\n    /// [`poll()`]: struct.Connection.html#method.poll\n    /// [`Done`]: enum.Error.html#variant.Done\n    PriorityUpdate,\n\n    /// GOAWAY was received.\n    GoAway,\n}\n\n/// Extensible Priorities parameters.\n///\n/// The `TryFrom` trait supports constructing this object from the serialized\n/// Structured Fields Dictionary field value. I.e, use `TryFrom` to parse the\n/// value of a Priority header field or a PRIORITY_UPDATE frame. Using this\n/// trait requires the `sfv` feature to be enabled.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n#[repr(C)]\npub struct Priority {\n    urgency: u8,\n    incremental: bool,\n}\n\nimpl Default for Priority {\n    fn default() -> Self {\n        Priority {\n            urgency: PRIORITY_URGENCY_DEFAULT,\n            incremental: PRIORITY_INCREMENTAL_DEFAULT,\n        }\n    }\n}\n\nimpl Priority {\n    /// Creates a new Priority.\n    pub const fn new(urgency: u8, incremental: bool) -> Self {\n        Priority {\n            urgency,\n            incremental,\n        }\n    }\n}\n\n#[cfg(feature = \"sfv\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"sfv\")))]\nimpl TryFrom<&[u8]> for Priority {\n    type Error = Error;\n\n    /// Try to parse an Extensible Priority field value.\n    ///\n    /// The field value is expected to be a Structured Fields Dictionary; see\n    /// [Extensible Priorities].\n    ///\n    /// If the `u` or `i` fields are contained with correct types, a constructed\n    /// Priority object is returned. Note that urgency values outside of valid\n    /// range (0 through 7) are clamped to 7.\n    ///\n    /// If the `u` or `i` fields are contained with the wrong types,\n    /// Error::Done is returned.\n    ///\n    /// Omitted parameters will yield default values.\n    ///\n    /// [Extensible Priorities]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4.\n    fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {\n        let dict = match sfv::Parser::parse_dictionary(value) {\n            Ok(v) => v,\n\n            Err(_) => return Err(Error::Done),\n        };\n\n        let urgency = match dict.get(\"u\") {\n            // If there is a u parameter, try to read it as an Item of type\n            // Integer. If the value out of the spec's allowed range\n            // (0 through 7), that's an error so set it to the upper\n            // bound (lowest priority) to avoid interference with\n            // other streams.\n            Some(sfv::ListEntry::Item(item)) => match item.bare_item.as_int() {\n                Some(v) => {\n                    if !(PRIORITY_URGENCY_LOWER_BOUND as i64..=\n                        PRIORITY_URGENCY_UPPER_BOUND as i64)\n                        .contains(&v)\n                    {\n                        PRIORITY_URGENCY_UPPER_BOUND\n                    } else {\n                        v as u8\n                    }\n                },\n\n                None => return Err(Error::Done),\n            },\n\n            Some(sfv::ListEntry::InnerList(_)) => return Err(Error::Done),\n\n            // Omitted so use default value.\n            None => PRIORITY_URGENCY_DEFAULT,\n        };\n\n        let incremental = match dict.get(\"i\") {\n            Some(sfv::ListEntry::Item(item)) =>\n                item.bare_item.as_bool().ok_or(Error::Done)?,\n\n            // Omitted so use default value.\n            _ => false,\n        };\n\n        Ok(Priority::new(urgency, incremental))\n    }\n}\n\nstruct ConnectionSettings {\n    pub max_field_section_size: Option<u64>,\n    pub qpack_max_table_capacity: Option<u64>,\n    pub qpack_blocked_streams: Option<u64>,\n    pub connect_protocol_enabled: Option<u64>,\n    pub h3_datagram: Option<u64>,\n    pub additional_settings: Option<Vec<(u64, u64)>>,\n    pub raw: Option<Vec<(u64, u64)>>,\n}\n\n#[derive(Default)]\nstruct QpackStreams {\n    pub encoder_stream_id: Option<u64>,\n    pub encoder_stream_bytes: u64,\n    pub decoder_stream_id: Option<u64>,\n    pub decoder_stream_bytes: u64,\n}\n\n/// Statistics about the connection.\n///\n/// A connection's statistics can be collected using the [`stats()`] method.\n///\n/// [`stats()`]: struct.Connection.html#method.stats\n#[derive(Clone, Default)]\npub struct Stats {\n    /// The number of bytes received on the QPACK encoder stream.\n    pub qpack_encoder_stream_recv_bytes: u64,\n    /// The number of bytes received on the QPACK decoder stream.\n    pub qpack_decoder_stream_recv_bytes: u64,\n}\n\nfn close_conn_critical_stream<F: BufFactory>(\n    conn: &mut super::Connection<F>,\n) -> Result<()> {\n    conn.close(\n        true,\n        Error::ClosedCriticalStream.to_wire(),\n        b\"Critical stream closed.\",\n    )?;\n\n    Err(Error::ClosedCriticalStream)\n}\n\nfn close_conn_if_critical_stream_finished<F: BufFactory>(\n    conn: &mut super::Connection<F>, stream_id: u64,\n) -> Result<()> {\n    if conn.stream_finished(stream_id) {\n        close_conn_critical_stream(conn)?;\n    }\n\n    Ok(())\n}\n\n/// An HTTP/3 connection.\npub struct Connection {\n    is_server: bool,\n\n    next_request_stream_id: u64,\n    next_uni_stream_id: u64,\n\n    streams: crate::stream::StreamIdHashMap<stream::Stream>,\n\n    local_settings: ConnectionSettings,\n    peer_settings: ConnectionSettings,\n\n    control_stream_id: Option<u64>,\n    peer_control_stream_id: Option<u64>,\n\n    qpack_encoder: qpack::Encoder,\n    qpack_decoder: qpack::Decoder,\n\n    local_qpack_streams: QpackStreams,\n    peer_qpack_streams: QpackStreams,\n\n    max_push_id: u64,\n\n    finished_streams: VecDeque<u64>,\n\n    frames_greased: bool,\n\n    local_goaway_id: Option<u64>,\n    peer_goaway_id: Option<u64>,\n}\n\nimpl Connection {\n    fn new(\n        config: &Config, is_server: bool, enable_dgram: bool,\n    ) -> Result<Connection> {\n        let initial_uni_stream_id = if is_server { 0x3 } else { 0x2 };\n        let h3_datagram = if enable_dgram { Some(1) } else { None };\n\n        Ok(Connection {\n            is_server,\n\n            next_request_stream_id: 0,\n\n            next_uni_stream_id: initial_uni_stream_id,\n\n            streams: Default::default(),\n\n            local_settings: ConnectionSettings {\n                max_field_section_size: config.max_field_section_size,\n                qpack_max_table_capacity: config.qpack_max_table_capacity,\n                qpack_blocked_streams: config.qpack_blocked_streams,\n                connect_protocol_enabled: config.connect_protocol_enabled,\n                h3_datagram,\n                additional_settings: config.additional_settings.clone(),\n                raw: Default::default(),\n            },\n\n            peer_settings: ConnectionSettings {\n                max_field_section_size: None,\n                qpack_max_table_capacity: None,\n                qpack_blocked_streams: None,\n                h3_datagram: None,\n                connect_protocol_enabled: None,\n                additional_settings: Default::default(),\n                raw: Default::default(),\n            },\n\n            control_stream_id: None,\n            peer_control_stream_id: None,\n\n            qpack_encoder: qpack::Encoder::new(),\n            qpack_decoder: qpack::Decoder::new(),\n\n            local_qpack_streams: Default::default(),\n            peer_qpack_streams: Default::default(),\n\n            max_push_id: 0,\n\n            finished_streams: VecDeque::new(),\n\n            frames_greased: false,\n\n            local_goaway_id: None,\n            peer_goaway_id: None,\n        })\n    }\n\n    /// Creates a new HTTP/3 connection using the provided QUIC connection.\n    ///\n    /// This will also initiate the HTTP/3 handshake with the peer by opening\n    /// all control streams (including QPACK) and sending the local settings.\n    ///\n    /// On success the new connection is returned.\n    ///\n    /// The [`StreamLimit`] error is returned when the HTTP/3 control stream\n    /// cannot be created due to stream limits.\n    ///\n    /// The [`InternalError`] error is returned when either the underlying QUIC\n    /// connection is not in a suitable state, or the HTTP/3 control stream\n    /// cannot be created due to flow control limits.\n    ///\n    /// [`StreamLimit`]: ../enum.Error.html#variant.StreamLimit\n    /// [`InternalError`]: ../enum.Error.html#variant.InternalError\n    pub fn with_transport<F: BufFactory>(\n        conn: &mut super::Connection<F>, config: &Config,\n    ) -> Result<Connection> {\n        let is_client = !conn.is_server;\n        if is_client && !(conn.is_established() || conn.is_in_early_data()) {\n            trace!(\"{} QUIC connection must be established or in early data before creating an HTTP/3 connection\", conn.trace_id());\n            return Err(Error::InternalError);\n        }\n\n        let mut http3_conn =\n            Connection::new(config, conn.is_server, conn.dgram_enabled())?;\n\n        match http3_conn.send_settings(conn) {\n            Ok(_) => (),\n\n            Err(e) => {\n                conn.close(true, e.to_wire(), b\"Error opening control stream\")?;\n                return Err(e);\n            },\n        };\n\n        // Try opening QPACK streams, but ignore errors if it fails since we\n        // don't need them right now.\n        http3_conn.open_qpack_encoder_stream(conn).ok();\n        http3_conn.open_qpack_decoder_stream(conn).ok();\n\n        if conn.grease {\n            // Try opening a GREASE stream, but ignore errors since it's not\n            // critical.\n            http3_conn.open_grease_stream(conn).ok();\n        }\n\n        Ok(http3_conn)\n    }\n\n    /// Sends an HTTP/3 request.\n    ///\n    /// The request is encoded from the provided list of headers without a\n    /// body, and sent on a newly allocated stream. To include a body,\n    /// set `fin` as `false` and subsequently call [`send_body()`] with the\n    /// same `conn` and the `stream_id` returned from this method.\n    ///\n    /// On success the newly allocated stream ID is returned.\n    ///\n    /// The [`StreamBlocked`] error is returned when the underlying QUIC stream\n    /// doesn't have enough capacity for the operation to complete. When this\n    /// happens the application should retry the operation once the stream is\n    /// reported as writable again.\n    ///\n    /// [`send_body()`]: struct.Connection.html#method.send_body\n    /// [`StreamBlocked`]: enum.Error.html#variant.StreamBlocked\n    pub fn send_request<T: NameValue, F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, headers: &[T], fin: bool,\n    ) -> Result<u64> {\n        // If we received a GOAWAY from the peer, MUST NOT initiate new\n        // requests.\n        if self.peer_goaway_id.is_some() {\n            return Err(Error::FrameUnexpected);\n        }\n\n        let stream_id = self.next_request_stream_id;\n\n        self.streams\n            .insert(stream_id, <stream::Stream>::new(stream_id, true));\n\n        // The underlying QUIC stream does not exist yet, so calls to e.g.\n        // stream_capacity() will fail. By writing a 0-length buffer, we force\n        // the creation of the QUIC stream state, without actually writing\n        // anything.\n        if let Err(e) = conn.stream_send(stream_id, b\"\", false) {\n            self.streams.remove(&stream_id);\n\n            if e == super::Error::Done {\n                return Err(Error::StreamBlocked);\n            }\n\n            return Err(e.into());\n        };\n\n        self.send_headers(conn, stream_id, headers, fin)?;\n\n        // To avoid skipping stream IDs, we only calculate the next available\n        // stream ID when a request has been successfully buffered.\n        self.next_request_stream_id = self\n            .next_request_stream_id\n            .checked_add(4)\n            .ok_or(Error::IdError)?;\n\n        Ok(stream_id)\n    }\n\n    /// Sends an HTTP/3 response on the specified stream with default priority.\n    ///\n    /// This method sends the provided `headers` as a single initial response\n    /// without a body.\n    ///\n    /// To send a non-final 1xx, then a final 200+ without body:\n    ///   * send_response() with `fin` set to `false`.\n    ///   * [`send_additional_headers()`] with fin set to `true` using the same\n    ///     `stream_id` value.\n    ///\n    /// To send a non-final 1xx, then a final 200+ with body:\n    ///   * send_response() with `fin` set to `false`.\n    ///   * [`send_additional_headers()`] with fin set to `false` and same\n    ///     `stream_id` value.\n    ///   * [`send_body()`] with same `stream_id`.\n    ///\n    /// To send a final 200+ with body:\n    ///   * send_response() with `fin` set to `false`.\n    ///   * [`send_body()`] with same `stream_id`.\n    ///\n    /// Additional headers can only be sent during certain phases of an HTTP/3\n    /// message exchange, see [Section 4.1 of RFC 9114]. The [`FrameUnexpected`]\n    /// error is returned if this method, or [`send_response_with_priority()`],\n    /// are called multiple times with the same `stream_id` value.\n    ///\n    /// The [`StreamBlocked`] error is returned when the underlying QUIC stream\n    /// doesn't have enough capacity for the operation to complete. When this\n    /// happens the application should retry the operation once the stream is\n    /// reported as writable again.\n    ///\n    /// [`send_body()`]: struct.Connection.html#method.send_body\n    /// [`send_additional_headers()`]:\n    ///     struct.Connection.html#method.send_additional_headers\n    /// [`send_response_with_priority()`]:\n    ///     struct.Connection.html#method.send_response_with_priority\n    /// [`FrameUnexpected`]: enum.Error.html#variant.FrameUnexpected\n    /// [`StreamBlocked`]: enum.Error.html#variant.StreamBlocked\n    pub fn send_response<T: NameValue, F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        headers: &[T], fin: bool,\n    ) -> Result<()> {\n        let priority = Default::default();\n\n        self.send_response_with_priority(\n            conn, stream_id, headers, &priority, fin,\n        )?;\n\n        Ok(())\n    }\n\n    /// Sends an HTTP/3 response on the specified stream with specified\n    /// priority.\n    ///\n    /// This method sends the provided `headers` as a single initial response\n    /// without a body.\n    ///\n    /// To send a non-final 1xx, then a final 200+ without body:\n    ///   * send_response_with_priority() with `fin` set to `false`.\n    ///   * [`send_additional_headers()`] with fin set to `true` using the same\n    ///     `stream_id` value.\n    ///\n    /// To send a non-final 1xx, then a final 200+ with body:\n    ///   * send_response_with_priority() with `fin` set to `false`.\n    ///   * [`send_additional_headers()`] with fin set to `false` and same\n    ///     `stream_id` value.\n    ///   * [`send_body()`] with same `stream_id`.\n    ///\n    /// To send a final 200+ with body:\n    ///   * send_response_with_priority() with `fin` set to `false`.\n    ///   * [`send_body()`] with same `stream_id`.\n    ///\n    /// The `priority` parameter represents [Extensible Priority]\n    /// parameters. If the urgency is outside the range 0-7, it will be clamped\n    /// to 7.\n    ///\n    /// Additional headers can only be sent during certain phases of an HTTP/3\n    /// message exchange, see [Section 4.1 of RFC 9114]. The [`FrameUnexpected`]\n    /// error is returned if this method, or [`send_response()`],\n    /// are called multiple times with the same `stream_id` value.\n    ///\n    /// The [`StreamBlocked`] error is returned when the underlying QUIC stream\n    /// doesn't have enough capacity for the operation to complete. When this\n    /// happens the application should retry the operation once the stream is\n    /// reported as writable again.\n    ///\n    /// [`send_body()`]: struct.Connection.html#method.send_body\n    /// [`send_additional_headers()`]:\n    ///     struct.Connection.html#method.send_additional_headers\n    /// [`send_response()`]:\n    ///     struct.Connection.html#method.send_response\n    /// [`FrameUnexpected`]: enum.Error.html#variant.FrameUnexpected\n    /// [`StreamBlocked`]: enum.Error.html#variant.StreamBlocked\n    /// [Extensible Priority]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4.\n    pub fn send_response_with_priority<T: NameValue, F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        headers: &[T], priority: &Priority, fin: bool,\n    ) -> Result<()> {\n        match self.streams.get(&stream_id) {\n            Some(s) => {\n                // Only one initial HEADERS allowed.\n                if s.local_initialized() {\n                    return Err(Error::FrameUnexpected);\n                }\n\n                s\n            },\n\n            None => return Err(Error::FrameUnexpected),\n        };\n\n        self.send_headers(conn, stream_id, headers, fin)?;\n\n        // Clamp and shift urgency into quiche-priority space\n        let urgency = priority\n            .urgency\n            .clamp(PRIORITY_URGENCY_LOWER_BOUND, PRIORITY_URGENCY_UPPER_BOUND) +\n            PRIORITY_URGENCY_OFFSET;\n\n        conn.stream_priority(stream_id, urgency, priority.incremental)?;\n\n        Ok(())\n    }\n\n    /// Sends additional HTTP/3 headers.\n    ///\n    /// After the initial request or response headers have been sent, using\n    /// [`send_request()`] or [`send_response()`] respectively, this method can\n    /// be used send an additional HEADERS frame. For example, to send a single\n    /// instance of trailers after a request with a body, or to issue another\n    /// non-final 1xx after a preceding 1xx, or to issue a final response after\n    /// a preceding 1xx.\n    ///\n    /// Additional headers can only be sent during certain phases of an HTTP/3\n    /// message exchange, see [Section 4.1 of RFC 9114]. The [`FrameUnexpected`]\n    /// error is returned when this method is called during the wrong phase,\n    /// such as before initial headers have been sent, or if trailers have\n    /// already been sent.\n    ///\n    /// The [`StreamBlocked`] error is returned when the underlying QUIC stream\n    /// doesn't have enough capacity for the operation to complete. When this\n    /// happens the application should retry the operation once the stream is\n    /// reported as writable again.\n    ///\n    /// [`send_request()`]: struct.Connection.html#method.send_request\n    /// [`send_response()`]: struct.Connection.html#method.send_response\n    /// [`StreamBlocked`]: enum.Error.html#variant.StreamBlocked\n    /// [`FrameUnexpected`]: enum.Error.html#variant.FrameUnexpected\n    /// [Section 4.1 of RFC 9114]:\n    ///     https://www.rfc-editor.org/rfc/rfc9114.html#section-4.1.\n    pub fn send_additional_headers<T: NameValue, F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        headers: &[T], is_trailer_section: bool, fin: bool,\n    ) -> Result<()> {\n        // Clients can only send trailer headers.\n        if !self.is_server && !is_trailer_section {\n            return Err(Error::FrameUnexpected);\n        }\n\n        match self.streams.get(&stream_id) {\n            Some(s) => {\n                // Initial HEADERS must have been sent.\n                if !s.local_initialized() {\n                    return Err(Error::FrameUnexpected);\n                }\n\n                // Only one trailing HEADERS allowed.\n                if s.trailers_sent() {\n                    return Err(Error::FrameUnexpected);\n                }\n\n                s\n            },\n\n            None => return Err(Error::FrameUnexpected),\n        };\n\n        self.send_headers(conn, stream_id, headers, fin)?;\n\n        if is_trailer_section {\n            // send_headers() might have tidied the stream away, so we need to\n            // check again.\n            if let Some(s) = self.streams.get_mut(&stream_id) {\n                s.mark_trailers_sent();\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Sends additional HTTP/3 headers with specified priority.\n    ///\n    /// After the initial request or response headers have been sent, using\n    /// [`send_request()`] or [`send_response()`] respectively, this method can\n    /// be used send an additional HEADERS frame. For example, to send a single\n    /// instance of trailers after a request with a body, or to issue another\n    /// non-final 1xx after a preceding 1xx, or to issue a final response after\n    /// a preceding 1xx.\n    ///\n    /// The `priority` parameter represents [Extensible Priority]\n    /// parameters. If the urgency is outside the range 0-7, it will be clamped\n    /// to 7.\n    ///\n    /// Additional headers can only be sent during certain phases of an HTTP/3\n    /// message exchange, see [Section 4.1 of RFC 9114]. The [`FrameUnexpected`]\n    /// error is returned when this method is called during the wrong phase,\n    /// such as before initial headers have been sent, or if trailers have\n    /// already been sent.\n    ///\n    /// The [`StreamBlocked`] error is returned when the underlying QUIC stream\n    /// doesn't have enough capacity for the operation to complete. When this\n    /// happens the application should retry the operation once the stream is\n    /// reported as writable again.\n    ///\n    /// [`send_request()`]: struct.Connection.html#method.send_request\n    /// [`send_response()`]: struct.Connection.html#method.send_response\n    /// [`StreamBlocked`]: enum.Error.html#variant.StreamBlocked\n    /// [`FrameUnexpected`]: enum.Error.html#variant.FrameUnexpected\n    /// [Section 4.1 of RFC 9114]:\n    ///     https://www.rfc-editor.org/rfc/rfc9114.html#section-4.1.\n    /// [Extensible Priority]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4.\n    pub fn send_additional_headers_with_priority<T: NameValue, F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        headers: &[T], priority: &Priority, is_trailer_section: bool, fin: bool,\n    ) -> Result<()> {\n        self.send_additional_headers(\n            conn,\n            stream_id,\n            headers,\n            is_trailer_section,\n            fin,\n        )?;\n\n        // Clamp and shift urgency into quiche-priority space\n        let urgency = priority\n            .urgency\n            .clamp(PRIORITY_URGENCY_LOWER_BOUND, PRIORITY_URGENCY_UPPER_BOUND) +\n            PRIORITY_URGENCY_OFFSET;\n\n        conn.stream_priority(stream_id, urgency, priority.incremental)?;\n\n        Ok(())\n    }\n\n    fn encode_header_block<T: NameValue>(\n        &mut self, headers: &[T],\n    ) -> Result<Vec<u8>> {\n        let headers_len = headers\n            .iter()\n            .fold(0, |acc, h| acc + h.value().len() + h.name().len() + 32);\n\n        let mut header_block = vec![0; headers_len];\n        let len = self\n            .qpack_encoder\n            .encode(headers, &mut header_block)\n            .map_err(|_| Error::InternalError)?;\n\n        header_block.truncate(len);\n\n        Ok(header_block)\n    }\n\n    fn send_headers<T: NameValue, F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        headers: &[T], fin: bool,\n    ) -> Result<()> {\n        let mut d = [42; 10];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        if !self.frames_greased && conn.grease {\n            self.send_grease_frames(conn, stream_id)?;\n            self.frames_greased = true;\n        }\n\n        let header_block = self.encode_header_block(headers)?;\n\n        let overhead = octets::varint_len(frame::HEADERS_FRAME_TYPE_ID) +\n            octets::varint_len(header_block.len() as u64);\n\n        // Headers need to be sent atomically, so make sure the stream has\n        // enough capacity.\n        match conn.stream_writable(stream_id, overhead + header_block.len()) {\n            Ok(true) => (),\n\n            Ok(false) => return Err(Error::StreamBlocked),\n\n            Err(e) => {\n                if conn.stream_finished(stream_id) {\n                    self.streams.remove(&stream_id);\n                }\n\n                return Err(e.into());\n            },\n        };\n\n        b.put_varint(frame::HEADERS_FRAME_TYPE_ID)?;\n        b.put_varint(header_block.len() as u64)?;\n        let off = b.off();\n        conn.stream_send(stream_id, &d[..off], false)?;\n\n        // Sending header block separately avoids unnecessary copy.\n        conn.stream_send(stream_id, &header_block, fin)?;\n\n        trace!(\n            \"{} tx frm HEADERS stream={} len={} fin={}\",\n            conn.trace_id(),\n            stream_id,\n            header_block.len(),\n            fin\n        );\n\n        qlog_with_type!(QLOG_FRAME_CREATED, conn.qlog, q, {\n            let qlog_headers = headers\n                .iter()\n                .map(|h| qlog::events::http3::HttpHeader {\n                    name: Some(String::from_utf8_lossy(h.name()).into_owned()),\n                    name_bytes: None,\n                    value: Some(String::from_utf8_lossy(h.value()).into_owned()),\n                    value_bytes: None,\n                })\n                .collect();\n\n            let frame = Http3Frame::Headers {\n                headers: qlog_headers,\n            };\n            let ev_data = EventData::Http3FrameCreated(FrameCreated {\n                stream_id,\n                length: Some(header_block.len() as u64),\n                frame,\n                ..Default::default()\n            });\n\n            q.add_event_data_now(ev_data).ok();\n        });\n\n        if let Some(s) = self.streams.get_mut(&stream_id) {\n            s.initialize_local();\n        }\n\n        if fin && conn.stream_finished(stream_id) {\n            self.streams.remove(&stream_id);\n        }\n\n        Ok(())\n    }\n\n    /// Sends an HTTP/3 body chunk on the given stream.\n    ///\n    /// On success the number of bytes written is returned, or [`Done`] if no\n    /// bytes could be written (e.g. because the stream is blocked).\n    ///\n    /// Note that the number of written bytes returned can be lower than the\n    /// length of the input buffer when the underlying QUIC stream doesn't have\n    /// enough capacity for the operation to complete.\n    ///\n    /// When a partial write happens (including when [`Done`] is returned) the\n    /// application should retry the operation once the stream is reported as\n    /// writable again.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    pub fn send_body<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64, body: &[u8],\n        fin: bool,\n    ) -> Result<usize> {\n        self.do_send_body(\n            conn,\n            stream_id,\n            body,\n            fin,\n            |conn: &mut super::Connection<F>,\n             header: &[u8],\n             stream_id: u64,\n             body: &[u8],\n             body_len: usize,\n             fin: bool| {\n                conn.stream_send(stream_id, header, false)?;\n                Ok(conn\n                    .stream_send(stream_id, &body[..body_len], fin)\n                    .map(|v| (v, v))?)\n            },\n        )\n    }\n\n    /// Sends an HTTP/3 body chunk provided as a raw buffer on the given stream.\n    ///\n    /// If the capacity allows it the buffer will be appended to the stream's\n    /// send queue with zero copying.\n    ///\n    /// On success the number of bytes written is returned, or [`Done`] if no\n    /// bytes could be written (e.g. because the stream is blocked).\n    ///\n    /// Note that the number of written bytes returned can be lower than the\n    /// length of the input buffer when the underlying QUIC stream doesn't have\n    /// enough capacity for the operation to complete.\n    ///\n    /// When a partial write happens (including when [`Done`] is returned) the\n    /// remaining (unwrittent) buffer will also be returned. The application\n    /// should retry the operation once the stream is reported as writable\n    /// again.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    pub fn send_body_zc<F>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        body: &mut F::Buf, fin: bool,\n    ) -> Result<usize>\n    where\n        F: BufFactory,\n        F::Buf: BufSplit,\n    {\n        self.do_send_body(\n            conn,\n            stream_id,\n            body,\n            fin,\n            |conn: &mut super::Connection<F>,\n             header: &[u8],\n             stream_id: u64,\n             body: &mut F::Buf,\n             mut body_len: usize,\n             fin: bool| {\n                let with_prefix = body.try_add_prefix(header);\n                if !with_prefix {\n                    conn.stream_send(stream_id, header, false)?;\n                } else {\n                    body_len += header.len();\n                }\n\n                let (mut n, rem) = conn.stream_send_zc(\n                    stream_id,\n                    body.clone(),\n                    Some(body_len),\n                    fin,\n                )?;\n\n                if with_prefix {\n                    n -= header.len();\n                }\n\n                if let Some(rem) = rem {\n                    let _ = std::mem::replace(body, rem);\n                }\n\n                Ok((n, n))\n            },\n        )\n    }\n\n    fn do_send_body<F, B, R, SND>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64, body: B,\n        fin: bool, write_fn: SND,\n    ) -> Result<R>\n    where\n        F: BufFactory,\n        B: AsRef<[u8]>,\n        SND: FnOnce(\n            &mut super::Connection<F>,\n            &[u8],\n            u64,\n            B,\n            usize,\n            bool,\n        ) -> Result<(usize, R)>,\n    {\n        let mut d = [42; 10];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let len = body.as_ref().len();\n\n        // Validate that it is sane to send data on the stream.\n        if stream_id % 4 != 0 {\n            return Err(Error::FrameUnexpected);\n        }\n\n        match self.streams.get_mut(&stream_id) {\n            Some(s) => {\n                if !s.local_initialized() {\n                    return Err(Error::FrameUnexpected);\n                }\n\n                if s.trailers_sent() {\n                    return Err(Error::FrameUnexpected);\n                }\n            },\n\n            None => {\n                return Err(Error::FrameUnexpected);\n            },\n        };\n\n        // Avoid sending 0-length DATA frames when the fin flag is false.\n        if len == 0 && !fin {\n            return Err(Error::Done);\n        }\n\n        let overhead = octets::varint_len(frame::DATA_FRAME_TYPE_ID) +\n            octets::varint_len(len as u64);\n\n        let stream_cap = match conn.stream_capacity(stream_id) {\n            Ok(v) => v,\n\n            Err(e) => {\n                if conn.stream_finished(stream_id) {\n                    self.streams.remove(&stream_id);\n                }\n\n                return Err(e.into());\n            },\n        };\n\n        // Make sure there is enough capacity to send the DATA frame header.\n        if stream_cap < overhead {\n            let _ = conn.stream_writable(stream_id, overhead + 1);\n            return Err(Error::Done);\n        }\n\n        // Cap the frame payload length to the stream's capacity.\n        let body_len = std::cmp::min(len, stream_cap - overhead);\n\n        // If we can't send the entire body, set the fin flag to false so the\n        // application can try again later.\n        let fin = if body_len != len { false } else { fin };\n\n        // Again, avoid sending 0-length DATA frames when the fin flag is false.\n        if body_len == 0 && !fin {\n            let _ = conn.stream_writable(stream_id, overhead + 1);\n            return Err(Error::Done);\n        }\n\n        b.put_varint(frame::DATA_FRAME_TYPE_ID)?;\n        b.put_varint(body_len as u64)?;\n        let off = b.off();\n\n        // Return how many bytes were written, excluding the frame header.\n        // Sending body separately avoids unnecessary copy.\n        let (written, ret) =\n            write_fn(conn, &d[..off], stream_id, body, body_len, fin)?;\n\n        trace!(\n            \"{} tx frm DATA stream={} len={} fin={}\",\n            conn.trace_id(),\n            stream_id,\n            written,\n            fin\n        );\n\n        qlog_with_type!(QLOG_FRAME_CREATED, conn.qlog, q, {\n            let frame = Http3Frame::Data { raw: None };\n            let ev_data = EventData::Http3FrameCreated(FrameCreated {\n                stream_id,\n                length: Some(written as u64),\n                frame,\n                ..Default::default()\n            });\n\n            q.add_event_data_now(ev_data).ok();\n        });\n\n        if written < len {\n            // Ensure the peer is notified that the connection or stream is\n            // blocked when the stream's capacity is limited by flow control.\n            //\n            // We only need enough capacity to send a few bytes, to make sure\n            // the stream doesn't hang due to congestion window not growing\n            // enough.\n            let _ = conn.stream_writable(stream_id, overhead + 1);\n        }\n\n        if fin && written == len && conn.stream_finished(stream_id) {\n            self.streams.remove(&stream_id);\n        }\n\n        Ok(ret)\n    }\n\n    /// Returns whether the peer enabled HTTP/3 DATAGRAM frame support.\n    ///\n    /// Support is signalled by the peer's SETTINGS, so this method always\n    /// returns false until they have been processed using the [`poll()`]\n    /// method.\n    ///\n    /// [`poll()`]: struct.Connection.html#method.poll\n    pub fn dgram_enabled_by_peer<F: BufFactory>(\n        &self, conn: &super::Connection<F>,\n    ) -> bool {\n        self.peer_settings.h3_datagram == Some(1) &&\n            conn.dgram_max_writable_len().is_some()\n    }\n\n    /// Returns whether the peer enabled extended CONNECT support.\n    ///\n    /// Support is signalled by the peer's SETTINGS, so this method always\n    /// returns false until they have been processed using the [`poll()`]\n    /// method.\n    ///\n    /// [`poll()`]: struct.Connection.html#method.poll\n    pub fn extended_connect_enabled_by_peer(&self) -> bool {\n        self.peer_settings.connect_protocol_enabled == Some(1)\n    }\n\n    /// Reads request or response body data into the provided buffer.\n    ///\n    /// Applications should call this method whenever the [`poll()`] method\n    /// returns a [`Data`] event.\n    ///\n    /// On success the amount of bytes read is returned, or [`Done`] if there\n    /// is no data to read.\n    ///\n    /// [`poll()`]: struct.Connection.html#method.poll\n    /// [`Data`]: enum.Event.html#variant.Data\n    /// [`Done`]: enum.Error.html#variant.Done\n    pub fn recv_body<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        out: &mut [u8],\n    ) -> Result<usize> {\n        let mut total = 0;\n\n        // Try to consume all buffered data for the stream, even across multiple\n        // DATA frames.\n        while total < out.len() {\n            let stream = self.streams.get_mut(&stream_id).ok_or(Error::Done)?;\n\n            if stream.state() != stream::State::Data {\n                break;\n            }\n\n            let (read, fin) =\n                match stream.try_consume_data(conn, &mut out[total..]) {\n                    Ok(v) => v,\n\n                    Err(Error::Done) => break,\n\n                    Err(e) => return Err(e),\n                };\n\n            total += read;\n\n            // No more data to read, we are done.\n            if read == 0 || fin {\n                break;\n            }\n\n            // Process incoming data from the stream. For example, if a whole\n            // DATA frame was consumed, and another one is queued behind it,\n            // this will ensure the additional data will also be returned to\n            // the application.\n            match self.process_readable_stream(conn, stream_id, false) {\n                Ok(_) => unreachable!(),\n\n                Err(Error::Done) => (),\n\n                Err(e) => return Err(e),\n            };\n\n            if conn.stream_finished(stream_id) {\n                break;\n            }\n        }\n\n        // While body is being received, the stream is marked as finished only\n        // when all data is read by the application.\n        if conn.stream_finished(stream_id) {\n            self.process_finished_stream(stream_id);\n        }\n\n        if total == 0 {\n            return Err(Error::Done);\n        }\n\n        Ok(total)\n    }\n\n    /// Sends a PRIORITY_UPDATE frame on the control stream with specified\n    /// request stream ID and priority.\n    ///\n    /// The `priority` parameter represents [Extensible Priority]\n    /// parameters. If the urgency is outside the range 0-7, it will be clamped\n    /// to 7.\n    ///\n    /// The [`StreamBlocked`] error is returned when the underlying QUIC stream\n    /// doesn't have enough capacity for the operation to complete. When this\n    /// happens the application should retry the operation once the stream is\n    /// reported as writable again.\n    ///\n    /// [`StreamBlocked`]: enum.Error.html#variant.StreamBlocked\n    /// [Extensible Priority]: https://www.rfc-editor.org/rfc/rfc9218.html#section-4.\n    pub fn send_priority_update_for_request<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        priority: &Priority,\n    ) -> Result<()> {\n        let mut d = [42; 20];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        // Validate that it is sane to send PRIORITY_UPDATE.\n        if self.is_server {\n            return Err(Error::FrameUnexpected);\n        }\n\n        if stream_id % 4 != 0 {\n            return Err(Error::FrameUnexpected);\n        }\n\n        let control_stream_id =\n            self.control_stream_id.ok_or(Error::FrameUnexpected)?;\n\n        let urgency = priority\n            .urgency\n            .clamp(PRIORITY_URGENCY_LOWER_BOUND, PRIORITY_URGENCY_UPPER_BOUND);\n\n        let mut field_value = format!(\"u={urgency}\");\n\n        if priority.incremental {\n            field_value.push_str(\",i\");\n        }\n\n        let priority_field_value = field_value.as_bytes();\n        let frame_payload_len =\n            octets::varint_len(stream_id) + priority_field_value.len();\n\n        let overhead =\n            octets::varint_len(frame::PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID) +\n                octets::varint_len(stream_id) +\n                octets::varint_len(frame_payload_len as u64);\n\n        // Make sure the control stream has enough capacity.\n        match conn.stream_writable(\n            control_stream_id,\n            overhead + priority_field_value.len(),\n        ) {\n            Ok(true) => (),\n\n            Ok(false) => return Err(Error::StreamBlocked),\n\n            Err(e) => {\n                return Err(e.into());\n            },\n        }\n\n        b.put_varint(frame::PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID)?;\n        b.put_varint(frame_payload_len as u64)?;\n        b.put_varint(stream_id)?;\n        let off = b.off();\n        conn.stream_send(control_stream_id, &d[..off], false)?;\n\n        // Sending field value separately avoids unnecessary copy.\n        conn.stream_send(control_stream_id, priority_field_value, false)?;\n\n        trace!(\n            \"{} tx frm PRIORITY_UPDATE request_stream={} priority_field_value={}\",\n            conn.trace_id(),\n            stream_id,\n            field_value,\n        );\n\n        qlog_with_type!(QLOG_FRAME_CREATED, conn.qlog, q, {\n            let frame = Http3Frame::PriorityUpdate {\n                target_stream_type: PriorityTargetStreamType::Request,\n                prioritized_element_id: stream_id,\n                priority_field_value: field_value.clone(),\n            };\n\n            let ev_data = EventData::Http3FrameCreated(FrameCreated {\n                stream_id,\n                length: Some(priority_field_value.len() as u64),\n                frame,\n                ..Default::default()\n            });\n\n            q.add_event_data_now(ev_data).ok();\n        });\n\n        Ok(())\n    }\n\n    /// Take the last PRIORITY_UPDATE for a prioritized element ID.\n    ///\n    /// When the [`poll()`] method returns a [`PriorityUpdate`] event for a\n    /// prioritized element, the event has triggered and will not rearm until\n    /// applications call this method. It is recommended that applications defer\n    /// taking the PRIORITY_UPDATE until after [`poll()`] returns [`Done`].\n    ///\n    /// On success the Priority Field Value is returned, or [`Done`] if there is\n    /// no PRIORITY_UPDATE to read (either because there is no value to take, or\n    /// because the prioritized element does not exist).\n    ///\n    /// [`poll()`]: struct.Connection.html#method.poll\n    /// [`PriorityUpdate`]: enum.Event.html#variant.PriorityUpdate\n    /// [`Done`]: enum.Error.html#variant.Done\n    pub fn take_last_priority_update(\n        &mut self, prioritized_element_id: u64,\n    ) -> Result<Vec<u8>> {\n        if let Some(stream) = self.streams.get_mut(&prioritized_element_id) {\n            return stream.take_last_priority_update().ok_or(Error::Done);\n        }\n\n        Err(Error::Done)\n    }\n\n    /// Processes HTTP/3 data received from the peer.\n    ///\n    /// On success it returns an [`Event`] and an ID, or [`Done`] when there are\n    /// no events to report.\n    ///\n    /// Note that all events are edge-triggered, meaning that once reported they\n    /// will not be reported again by calling this method again, until the event\n    /// is re-armed.\n    ///\n    /// The events [`Headers`], [`Data`] and [`Finished`] return a stream ID,\n    /// which is used in methods [`recv_body()`], [`send_response()`] or\n    /// [`send_body()`].\n    ///\n    /// The event [`GoAway`] returns an ID that depends on the connection role.\n    /// A client receives the largest processed stream ID. A server receives the\n    /// the largest permitted push ID.\n    ///\n    /// The event [`PriorityUpdate`] only occurs at servers. It returns a\n    /// prioritized element ID that is used in the method\n    /// [`take_last_priority_update()`], which rearms the event for that ID.\n    ///\n    /// If an error occurs while processing data, the connection is closed with\n    /// the appropriate error code, using the transport's [`close()`] method.\n    ///\n    /// [`Event`]: enum.Event.html\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`Headers`]: enum.Event.html#variant.Headers\n    /// [`Data`]: enum.Event.html#variant.Data\n    /// [`Finished`]: enum.Event.html#variant.Finished\n    /// [`GoAway`]: enum.Event.html#variant.GoAWay\n    /// [`PriorityUpdate`]: enum.Event.html#variant.PriorityUpdate\n    /// [`recv_body()`]: struct.Connection.html#method.recv_body\n    /// [`send_response()`]: struct.Connection.html#method.send_response\n    /// [`send_body()`]: struct.Connection.html#method.send_body\n    /// [`recv_dgram()`]: struct.Connection.html#method.recv_dgram\n    /// [`take_last_priority_update()`]: struct.Connection.html#method.take_last_priority_update\n    /// [`close()`]: ../struct.Connection.html#method.close\n    pub fn poll<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>,\n    ) -> Result<(u64, Event)> {\n        // When connection close is initiated by the local application (e.g. due\n        // to a protocol error), the connection itself might be in a broken\n        // state, so return early.\n        if conn.local_error.is_some() {\n            return Err(Error::Done);\n        }\n\n        // Process control streams first.\n        if let Some(stream_id) = self.peer_control_stream_id {\n            match self.process_control_stream(conn, stream_id) {\n                Ok(ev) => return Ok(ev),\n\n                Err(Error::Done) => (),\n\n                Err(e) => return Err(e),\n            };\n        }\n\n        if let Some(stream_id) = self.peer_qpack_streams.encoder_stream_id {\n            match self.process_control_stream(conn, stream_id) {\n                Ok(ev) => return Ok(ev),\n\n                Err(Error::Done) => (),\n\n                Err(e) => return Err(e),\n            };\n        }\n\n        if let Some(stream_id) = self.peer_qpack_streams.decoder_stream_id {\n            match self.process_control_stream(conn, stream_id) {\n                Ok(ev) => return Ok(ev),\n\n                Err(Error::Done) => (),\n\n                Err(e) => return Err(e),\n            };\n        }\n\n        // Process finished streams list.\n        if let Some(finished) = self.finished_streams.pop_front() {\n            return Ok((finished, Event::Finished));\n        }\n\n        // Process HTTP/3 data from readable streams.\n        for s in conn.readable() {\n            trace!(\"{} stream id {} is readable\", conn.trace_id(), s);\n\n            let ev = match self.process_readable_stream(conn, s, true) {\n                Ok(v) => Some(v),\n\n                Err(Error::Done) => None,\n\n                // Return early if the stream was reset, to avoid returning\n                // a Finished event later as well.\n                Err(Error::TransportError(crate::Error::StreamReset(e))) =>\n                    return Ok((s, Event::Reset(e))),\n\n                Err(e) => return Err(e),\n            };\n\n            if conn.stream_finished(s) {\n                self.process_finished_stream(s);\n            }\n\n            // TODO: check if stream is completed so it can be freed\n            if let Some(ev) = ev {\n                return Ok(ev);\n            }\n        }\n\n        // Process finished streams list once again, to make sure `Finished`\n        // events are returned when receiving empty stream frames with the fin\n        // flag set.\n        if let Some(finished) = self.finished_streams.pop_front() {\n            if conn.stream_readable(finished) {\n                // The stream is finished, but is still readable, it may\n                // indicate that there is a pending error, such as reset.\n                if let Err(crate::Error::StreamReset(e)) =\n                    conn.stream_recv(finished, &mut [])\n                {\n                    return Ok((finished, Event::Reset(e)));\n                }\n            }\n            return Ok((finished, Event::Finished));\n        }\n\n        Err(Error::Done)\n    }\n\n    /// Sends a GOAWAY frame to initiate graceful connection closure.\n    ///\n    /// When quiche is used in the server role, the `id` parameter is the stream\n    /// ID of the highest processed request. This can be any valid ID between 0\n    /// and 2^62-4. However, the ID cannot be increased. Failure to satisfy\n    /// these conditions will return an error.\n    ///\n    /// This method does not close the QUIC connection. Applications are\n    /// required to call [`close()`] themselves.\n    ///\n    /// [`close()`]: ../struct.Connection.html#method.close\n    pub fn send_goaway<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, id: u64,\n    ) -> Result<()> {\n        let mut id = id;\n\n        // TODO: server push\n        //\n        // In the meantime always send 0 from client.\n        if !self.is_server {\n            id = 0;\n        }\n\n        if self.is_server && id % 4 != 0 {\n            return Err(Error::IdError);\n        }\n\n        if let Some(sent_id) = self.local_goaway_id {\n            if id > sent_id {\n                return Err(Error::IdError);\n            }\n        }\n\n        if let Some(stream_id) = self.control_stream_id {\n            let mut d = [42; 10];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            let frame = frame::Frame::GoAway { id };\n\n            let wire_len = frame.to_bytes(&mut b)?;\n            let stream_cap = conn.stream_capacity(stream_id)?;\n\n            if stream_cap < wire_len {\n                return Err(Error::StreamBlocked);\n            }\n\n            trace!(\"{} tx frm {:?}\", conn.trace_id(), frame);\n\n            qlog_with_type!(QLOG_FRAME_CREATED, conn.qlog, q, {\n                let ev_data = EventData::Http3FrameCreated(FrameCreated {\n                    stream_id,\n                    length: Some(octets::varint_len(id) as u64),\n                    frame: frame.to_qlog(),\n                    ..Default::default()\n                });\n\n                q.add_event_data_now(ev_data).ok();\n            });\n\n            let off = b.off();\n            conn.stream_send(stream_id, &d[..off], false)?;\n\n            self.local_goaway_id = Some(id);\n        }\n\n        Ok(())\n    }\n\n    /// Gets the raw settings from peer including unknown and reserved types.\n    ///\n    /// The order of settings is the same as received in the SETTINGS frame.\n    pub fn peer_settings_raw(&self) -> Option<&[(u64, u64)]> {\n        self.peer_settings.raw.as_deref()\n    }\n\n    fn open_uni_stream<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, ty: u64,\n    ) -> Result<u64> {\n        let stream_id = self.next_uni_stream_id;\n\n        let mut d = [0; 8];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        match ty {\n            // Control and QPACK streams are the most important to schedule.\n            stream::HTTP3_CONTROL_STREAM_TYPE_ID |\n            stream::QPACK_ENCODER_STREAM_TYPE_ID |\n            stream::QPACK_DECODER_STREAM_TYPE_ID => {\n                conn.stream_priority(stream_id, 0, false)?;\n            },\n\n            // TODO: Server push\n            stream::HTTP3_PUSH_STREAM_TYPE_ID => (),\n\n            // Anything else is a GREASE stream, so make it the least important.\n            _ => {\n                conn.stream_priority(stream_id, 255, false)?;\n            },\n        }\n\n        conn.stream_send(stream_id, b.put_varint(ty)?, false)?;\n\n        // To avoid skipping stream IDs, we only calculate the next available\n        // stream ID when data has been successfully buffered.\n        self.next_uni_stream_id = self\n            .next_uni_stream_id\n            .checked_add(4)\n            .ok_or(Error::IdError)?;\n\n        Ok(stream_id)\n    }\n\n    fn open_qpack_encoder_stream<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>,\n    ) -> Result<()> {\n        let stream_id =\n            self.open_uni_stream(conn, stream::QPACK_ENCODER_STREAM_TYPE_ID)?;\n\n        self.local_qpack_streams.encoder_stream_id = Some(stream_id);\n\n        qlog_with_type!(QLOG_STREAM_TYPE_SET, conn.qlog, q, {\n            let ev_data = EventData::Http3StreamTypeSet(StreamTypeSet {\n                stream_id,\n                initiator: Some(Initiator::Local),\n                stream_type: StreamType::QpackEncode,\n                ..Default::default()\n            });\n\n            q.add_event_data_now(ev_data).ok();\n        });\n\n        Ok(())\n    }\n\n    fn open_qpack_decoder_stream<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>,\n    ) -> Result<()> {\n        let stream_id =\n            self.open_uni_stream(conn, stream::QPACK_DECODER_STREAM_TYPE_ID)?;\n\n        self.local_qpack_streams.decoder_stream_id = Some(stream_id);\n\n        qlog_with_type!(QLOG_STREAM_TYPE_SET, conn.qlog, q, {\n            let ev_data = EventData::Http3StreamTypeSet(StreamTypeSet {\n                stream_id,\n                initiator: Some(Initiator::Local),\n                stream_type: StreamType::QpackDecode,\n                ..Default::default()\n            });\n\n            q.add_event_data_now(ev_data).ok();\n        });\n\n        Ok(())\n    }\n\n    /// Send GREASE frames on the provided stream ID.\n    fn send_grease_frames<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n    ) -> Result<()> {\n        let mut d = [0; 8];\n\n        let stream_cap = match conn.stream_capacity(stream_id) {\n            Ok(v) => v,\n\n            Err(e) => {\n                if conn.stream_finished(stream_id) {\n                    self.streams.remove(&stream_id);\n                }\n\n                return Err(e.into());\n            },\n        };\n\n        let grease_frame1 = grease_value();\n        let grease_frame2 = grease_value();\n        let grease_payload = b\"GREASE is the word\";\n\n        let overhead = octets::varint_len(grease_frame1) + // frame type\n            1 + // payload len\n            octets::varint_len(grease_frame2) + // frame type\n            1 + // payload len\n            grease_payload.len(); // payload\n\n        // Don't send GREASE if there is not enough capacity for it. Greasing\n        // will _not_ be attempted again later on.\n        if stream_cap < overhead {\n            return Ok(());\n        }\n\n        // Empty GREASE frame.\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        conn.stream_send(stream_id, b.put_varint(grease_frame1)?, false)?;\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        conn.stream_send(stream_id, b.put_varint(0)?, false)?;\n\n        trace!(\n            \"{} tx frm GREASE stream={} len=0\",\n            conn.trace_id(),\n            stream_id\n        );\n\n        qlog_with_type!(QLOG_FRAME_CREATED, conn.qlog, q, {\n            let frame = Http3Frame::Reserved { length: Some(0) };\n            let ev_data = EventData::Http3FrameCreated(FrameCreated {\n                stream_id,\n                length: Some(0),\n                frame,\n                ..Default::default()\n            });\n\n            q.add_event_data_now(ev_data).ok();\n        });\n\n        // GREASE frame with payload.\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        conn.stream_send(stream_id, b.put_varint(grease_frame2)?, false)?;\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        conn.stream_send(stream_id, b.put_varint(18)?, false)?;\n\n        conn.stream_send(stream_id, grease_payload, false)?;\n\n        trace!(\n            \"{} tx frm GREASE stream={} len={}\",\n            conn.trace_id(),\n            stream_id,\n            grease_payload.len()\n        );\n\n        qlog_with_type!(QLOG_FRAME_CREATED, conn.qlog, q, {\n            let frame = Http3Frame::Reserved {\n                length: Some(grease_payload.len() as u64),\n            };\n            let ev_data = EventData::Http3FrameCreated(FrameCreated {\n                stream_id,\n                length: Some(grease_payload.len() as u64),\n                frame,\n                ..Default::default()\n            });\n\n            q.add_event_data_now(ev_data).ok();\n        });\n\n        Ok(())\n    }\n\n    /// Opens a new unidirectional stream with a GREASE type and sends some\n    /// unframed payload.\n    fn open_grease_stream<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>,\n    ) -> Result<()> {\n        let ty = grease_value();\n        match self.open_uni_stream(conn, ty) {\n            Ok(stream_id) => {\n                conn.stream_send(stream_id, b\"GREASE is the word\", true)?;\n\n                trace!(\"{} open GREASE stream {}\", conn.trace_id(), stream_id);\n\n                qlog_with_type!(QLOG_STREAM_TYPE_SET, conn.qlog, q, {\n                    let ev_data = EventData::Http3StreamTypeSet(StreamTypeSet {\n                        stream_id,\n                        initiator: Some(Initiator::Local),\n                        stream_type: StreamType::Unknown,\n                        stream_type_bytes: Some(ty),\n                        ..Default::default()\n                    });\n\n                    q.add_event_data_now(ev_data).ok();\n                });\n            },\n\n            Err(Error::IdError) => {\n                trace!(\"{} GREASE stream blocked\", conn.trace_id(),);\n\n                return Ok(());\n            },\n\n            Err(e) => return Err(e),\n        };\n\n        Ok(())\n    }\n\n    /// Sends SETTINGS frame based on HTTP/3 configuration.\n    fn send_settings<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>,\n    ) -> Result<()> {\n        let stream_id = match self\n            .open_uni_stream(conn, stream::HTTP3_CONTROL_STREAM_TYPE_ID)\n        {\n            Ok(v) => v,\n\n            Err(e) => {\n                trace!(\"{} Control stream blocked\", conn.trace_id(),);\n\n                if e == Error::Done {\n                    return Err(Error::InternalError);\n                }\n\n                return Err(e);\n            },\n        };\n\n        self.control_stream_id = Some(stream_id);\n\n        qlog_with_type!(QLOG_STREAM_TYPE_SET, conn.qlog, q, {\n            let ev_data = EventData::Http3StreamTypeSet(StreamTypeSet {\n                stream_id,\n                initiator: Some(Initiator::Local),\n                stream_type: StreamType::Control,\n                ..Default::default()\n            });\n\n            q.add_event_data_now(ev_data).ok();\n        });\n\n        let grease = if conn.grease {\n            Some((grease_value(), grease_value()))\n        } else {\n            None\n        };\n\n        let frame = frame::Frame::Settings {\n            max_field_section_size: self.local_settings.max_field_section_size,\n            qpack_max_table_capacity: self\n                .local_settings\n                .qpack_max_table_capacity,\n            qpack_blocked_streams: self.local_settings.qpack_blocked_streams,\n            connect_protocol_enabled: self\n                .local_settings\n                .connect_protocol_enabled,\n            h3_datagram: self.local_settings.h3_datagram,\n            grease,\n            additional_settings: self.local_settings.additional_settings.clone(),\n            raw: Default::default(),\n        };\n\n        let mut d = [42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        frame.to_bytes(&mut b)?;\n\n        let off = b.off();\n\n        if let Some(id) = self.control_stream_id {\n            conn.stream_send(id, &d[..off], false)?;\n\n            trace!(\n                \"{} tx frm SETTINGS stream={} len={}\",\n                conn.trace_id(),\n                id,\n                off\n            );\n\n            qlog_with_type!(QLOG_FRAME_CREATED, conn.qlog, q, {\n                let frame = frame.to_qlog();\n                let ev_data = EventData::Http3FrameCreated(FrameCreated {\n                    stream_id: id,\n                    length: Some(off as u64),\n                    frame,\n                    ..Default::default()\n                });\n\n                q.add_event_data_now(ev_data).ok();\n            });\n        }\n\n        Ok(())\n    }\n\n    fn process_control_stream<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n    ) -> Result<(u64, Event)> {\n        close_conn_if_critical_stream_finished(conn, stream_id)?;\n\n        if !conn.stream_readable(stream_id) {\n            return Err(Error::Done);\n        }\n\n        match self.process_readable_stream(conn, stream_id, true) {\n            Ok(ev) => return Ok(ev),\n\n            Err(Error::Done) => (),\n\n            Err(e) => return Err(e),\n        };\n\n        close_conn_if_critical_stream_finished(conn, stream_id)?;\n\n        Err(Error::Done)\n    }\n\n    fn process_readable_stream<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64, polling: bool,\n    ) -> Result<(u64, Event)> {\n        self.streams\n            .entry(stream_id)\n            .or_insert_with(|| <stream::Stream>::new(stream_id, false));\n\n        // We need to get a fresh reference to the stream for each\n        // iteration, to avoid borrowing `self` for the entire duration\n        // of the loop, because we'll need to borrow it again in the\n        // `State::FramePayload` case below.\n        while let Some(stream) = self.streams.get_mut(&stream_id) {\n            match stream.state() {\n                stream::State::StreamType => {\n                    stream.try_fill_buffer(conn)?;\n\n                    let varint = match stream.try_consume_varint() {\n                        Ok(v) => v,\n\n                        Err(_) => continue,\n                    };\n\n                    let ty = stream::Type::deserialize(varint)?;\n\n                    if let Err(e) = stream.set_ty(ty) {\n                        conn.close(true, e.to_wire(), b\"\")?;\n                        return Err(e);\n                    }\n\n                    qlog_with_type!(QLOG_STREAM_TYPE_SET, conn.qlog, q, {\n                        let ty_val = if matches!(ty, stream::Type::Unknown) {\n                            Some(varint)\n                        } else {\n                            None\n                        };\n\n                        let ev_data =\n                            EventData::Http3StreamTypeSet(StreamTypeSet {\n                                stream_id,\n                                initiator: Some(Initiator::Remote),\n                                stream_type: ty.to_qlog(),\n                                stream_type_bytes: ty_val,\n                                ..Default::default()\n                            });\n\n                        q.add_event_data_now(ev_data).ok();\n                    });\n\n                    match &ty {\n                        stream::Type::Control => {\n                            // Only one control stream allowed.\n                            if self.peer_control_stream_id.is_some() {\n                                conn.close(\n                                    true,\n                                    Error::StreamCreationError.to_wire(),\n                                    b\"Received multiple control streams\",\n                                )?;\n\n                                return Err(Error::StreamCreationError);\n                            }\n\n                            trace!(\n                                \"{} open peer's control stream {}\",\n                                conn.trace_id(),\n                                stream_id\n                            );\n\n                            close_conn_if_critical_stream_finished(\n                                conn, stream_id,\n                            )?;\n\n                            self.peer_control_stream_id = Some(stream_id);\n                        },\n\n                        stream::Type::Push => {\n                            // Only clients can receive push stream.\n                            if self.is_server {\n                                conn.close(\n                                    true,\n                                    Error::StreamCreationError.to_wire(),\n                                    b\"Server received push stream.\",\n                                )?;\n\n                                return Err(Error::StreamCreationError);\n                            }\n                        },\n\n                        stream::Type::QpackEncoder => {\n                            // Only one qpack encoder stream allowed.\n                            if self.peer_qpack_streams.encoder_stream_id.is_some()\n                            {\n                                conn.close(\n                                    true,\n                                    Error::StreamCreationError.to_wire(),\n                                    b\"Received multiple QPACK encoder streams\",\n                                )?;\n\n                                return Err(Error::StreamCreationError);\n                            }\n\n                            close_conn_if_critical_stream_finished(\n                                conn, stream_id,\n                            )?;\n\n                            self.peer_qpack_streams.encoder_stream_id =\n                                Some(stream_id);\n                        },\n\n                        stream::Type::QpackDecoder => {\n                            // Only one qpack decoder allowed.\n                            if self.peer_qpack_streams.decoder_stream_id.is_some()\n                            {\n                                conn.close(\n                                    true,\n                                    Error::StreamCreationError.to_wire(),\n                                    b\"Received multiple QPACK decoder streams\",\n                                )?;\n\n                                return Err(Error::StreamCreationError);\n                            }\n\n                            close_conn_if_critical_stream_finished(\n                                conn, stream_id,\n                            )?;\n\n                            self.peer_qpack_streams.decoder_stream_id =\n                                Some(stream_id);\n                        },\n\n                        stream::Type::Unknown => {\n                            // Unknown stream types are ignored.\n                            // TODO: we MAY send STOP_SENDING\n                        },\n\n                        stream::Type::Request => unreachable!(),\n                    }\n                },\n\n                stream::State::PushId => {\n                    stream.try_fill_buffer(conn)?;\n\n                    let varint = match stream.try_consume_varint() {\n                        Ok(v) => v,\n\n                        Err(_) => continue,\n                    };\n\n                    if let Err(e) = stream.set_push_id(varint) {\n                        conn.close(true, e.to_wire(), b\"\")?;\n                        return Err(e);\n                    }\n                },\n\n                stream::State::FrameType => {\n                    stream.try_fill_buffer(conn)?;\n\n                    let varint = match stream.try_consume_varint() {\n                        Ok(v) => v,\n\n                        Err(_) => continue,\n                    };\n\n                    match stream.set_frame_type(varint) {\n                        Err(Error::FrameUnexpected) => {\n                            let msg = format!(\"Unexpected frame type {varint}\");\n\n                            conn.close(\n                                true,\n                                Error::FrameUnexpected.to_wire(),\n                                msg.as_bytes(),\n                            )?;\n\n                            return Err(Error::FrameUnexpected);\n                        },\n\n                        Err(e) => {\n                            conn.close(\n                                true,\n                                e.to_wire(),\n                                b\"Error handling frame.\",\n                            )?;\n\n                            return Err(e);\n                        },\n\n                        _ => (),\n                    }\n                },\n\n                stream::State::FramePayloadLen => {\n                    stream.try_fill_buffer(conn)?;\n\n                    let payload_len = match stream.try_consume_varint() {\n                        Ok(v) => v,\n\n                        Err(_) => continue,\n                    };\n\n                    // DATA frames are handled uniquely. After this point we lose\n                    // visibility of DATA framing, so just log here.\n                    if Some(frame::DATA_FRAME_TYPE_ID) == stream.frame_type() {\n                        trace!(\n                            \"{} rx frm DATA stream={} wire_payload_len={}\",\n                            conn.trace_id(),\n                            stream_id,\n                            payload_len\n                        );\n\n                        qlog_with_type!(QLOG_FRAME_PARSED, conn.qlog, q, {\n                            let frame = Http3Frame::Data { raw: None };\n\n                            let ev_data =\n                                EventData::Http3FrameParsed(FrameParsed {\n                                    stream_id,\n                                    length: Some(payload_len),\n                                    frame,\n                                    ..Default::default()\n                                });\n\n                            q.add_event_data_now(ev_data).ok();\n                        });\n                    }\n\n                    if let Err(e) = stream.set_frame_payload_len(payload_len) {\n                        conn.close(true, e.to_wire(), b\"\")?;\n                        return Err(e);\n                    }\n                },\n\n                stream::State::FramePayload => {\n                    // Do not emit events when not polling.\n                    if !polling {\n                        break;\n                    }\n\n                    stream.try_fill_buffer(conn)?;\n\n                    let (frame, payload_len) = match stream.try_consume_frame() {\n                        Ok(frame) => frame,\n\n                        Err(Error::Done) => return Err(Error::Done),\n\n                        Err(e) => {\n                            conn.close(\n                                true,\n                                e.to_wire(),\n                                b\"Error handling frame.\",\n                            )?;\n\n                            return Err(e);\n                        },\n                    };\n\n                    match self.process_frame(conn, stream_id, frame, payload_len)\n                    {\n                        Ok(ev) => return Ok(ev),\n\n                        Err(Error::Done) => {\n                            // This might be a frame that is processed internally\n                            // without needing to bubble up to the user as an\n                            // event. Check whether the frame has FIN'd by QUIC\n                            // to prevent trying to read again on a closed stream.\n                            if conn.stream_finished(stream_id) {\n                                break;\n                            }\n                        },\n\n                        Err(e) => return Err(e),\n                    };\n                },\n\n                stream::State::Data => {\n                    // Do not emit events when not polling.\n                    if !polling {\n                        break;\n                    }\n\n                    if !stream.try_trigger_data_event() {\n                        break;\n                    }\n\n                    return Ok((stream_id, Event::Data));\n                },\n\n                stream::State::QpackInstruction => {\n                    let mut d = [0; 4096];\n\n                    // Read data from the stream and discard immediately.\n                    loop {\n                        let (recv, fin) = conn.stream_recv(stream_id, &mut d)?;\n\n                        match stream.ty() {\n                            Some(stream::Type::QpackEncoder) =>\n                                self.peer_qpack_streams.encoder_stream_bytes +=\n                                    recv as u64,\n                            Some(stream::Type::QpackDecoder) =>\n                                self.peer_qpack_streams.decoder_stream_bytes +=\n                                    recv as u64,\n                            _ => unreachable!(),\n                        };\n\n                        if fin {\n                            close_conn_critical_stream(conn)?;\n                        }\n                    }\n                },\n\n                stream::State::Drain => {\n                    // Discard incoming data on the stream.\n                    conn.stream_shutdown(\n                        stream_id,\n                        crate::Shutdown::Read,\n                        0x100,\n                    )?;\n\n                    break;\n                },\n\n                stream::State::Finished => break,\n            }\n        }\n\n        Err(Error::Done)\n    }\n\n    fn process_finished_stream(&mut self, stream_id: u64) {\n        let stream = match self.streams.get_mut(&stream_id) {\n            Some(v) => v,\n\n            None => return,\n        };\n\n        if stream.state() == stream::State::Finished {\n            return;\n        }\n\n        match stream.ty() {\n            Some(stream::Type::Request) | Some(stream::Type::Push) => {\n                stream.finished();\n\n                self.finished_streams.push_back(stream_id);\n            },\n\n            _ => (),\n        };\n    }\n\n    fn process_frame<F: BufFactory>(\n        &mut self, conn: &mut super::Connection<F>, stream_id: u64,\n        frame: frame::Frame, payload_len: u64,\n    ) -> Result<(u64, Event)> {\n        trace!(\n            \"{} rx frm {:?} stream={} payload_len={}\",\n            conn.trace_id(),\n            frame,\n            stream_id,\n            payload_len\n        );\n\n        qlog_with_type!(QLOG_FRAME_PARSED, conn.qlog, q, {\n            // HEADERS frames are special case and will be logged below.\n            if !matches!(frame, frame::Frame::Headers { .. }) {\n                let frame = frame.to_qlog();\n                let ev_data = EventData::Http3FrameParsed(FrameParsed {\n                    stream_id,\n                    length: Some(payload_len),\n                    frame,\n                    ..Default::default()\n                });\n\n                q.add_event_data_now(ev_data).ok();\n            }\n        });\n\n        match frame {\n            frame::Frame::Settings {\n                max_field_section_size,\n                qpack_max_table_capacity,\n                qpack_blocked_streams,\n                connect_protocol_enabled,\n                h3_datagram,\n                additional_settings,\n                raw,\n                ..\n            } => {\n                self.peer_settings = ConnectionSettings {\n                    max_field_section_size,\n                    qpack_max_table_capacity,\n                    qpack_blocked_streams,\n                    connect_protocol_enabled,\n                    h3_datagram,\n                    additional_settings,\n                    raw,\n                };\n\n                if let Some(1) = h3_datagram {\n                    // The peer MUST have also enabled DATAGRAM with a TP\n                    if conn.dgram_max_writable_len().is_none() {\n                        conn.close(\n                            true,\n                            Error::SettingsError.to_wire(),\n                            b\"H3_DATAGRAM sent with value 1 but max_datagram_frame_size TP not set.\",\n                        )?;\n\n                        return Err(Error::SettingsError);\n                    }\n                }\n            },\n\n            frame::Frame::Headers { header_block } => {\n                // Servers reject too many HEADERS frames.\n                if let Some(s) = self.streams.get_mut(&stream_id) {\n                    if self.is_server && s.headers_received_count() == 2 {\n                        conn.close(\n                            true,\n                            Error::FrameUnexpected.to_wire(),\n                            b\"Too many HEADERS frames\",\n                        )?;\n                        return Err(Error::FrameUnexpected);\n                    }\n\n                    s.increment_headers_received();\n                }\n\n                // Use \"infinite\" as default value for max_field_section_size if\n                // it is not configured by the application.\n                let max_size = self\n                    .local_settings\n                    .max_field_section_size\n                    .unwrap_or(u64::MAX);\n\n                let headers = match self\n                    .qpack_decoder\n                    .decode(&header_block[..], max_size)\n                {\n                    Ok(v) => v,\n\n                    Err(e) => {\n                        let e = match e {\n                            qpack::Error::HeaderListTooLarge =>\n                                Error::ExcessiveLoad,\n\n                            _ => Error::QpackDecompressionFailed,\n                        };\n\n                        conn.close(true, e.to_wire(), b\"Error parsing headers.\")?;\n\n                        return Err(e);\n                    },\n                };\n\n                qlog_with_type!(QLOG_FRAME_PARSED, conn.qlog, q, {\n                    let qlog_headers = headers\n                        .iter()\n                        .map(|h| qlog::events::http3::HttpHeader {\n                            name: Some(\n                                String::from_utf8_lossy(h.name()).into_owned(),\n                            ),\n                            name_bytes: None,\n                            value: Some(\n                                String::from_utf8_lossy(h.value()).into_owned(),\n                            ),\n                            value_bytes: None,\n                        })\n                        .collect();\n\n                    let frame = Http3Frame::Headers {\n                        headers: qlog_headers,\n                    };\n\n                    let ev_data = EventData::Http3FrameParsed(FrameParsed {\n                        stream_id,\n                        length: Some(payload_len),\n                        frame,\n                        ..Default::default()\n                    });\n\n                    q.add_event_data_now(ev_data).ok();\n                });\n\n                let more_frames = !conn.stream_finished(stream_id);\n\n                return Ok((stream_id, Event::Headers {\n                    list: headers,\n                    more_frames,\n                }));\n            },\n\n            frame::Frame::Data { .. } => {\n                // Do nothing. The Data event is returned separately.\n            },\n\n            frame::Frame::GoAway { id } => {\n                if !self.is_server && id % 4 != 0 {\n                    conn.close(\n                        true,\n                        Error::FrameUnexpected.to_wire(),\n                        b\"GOAWAY received with ID of non-request stream\",\n                    )?;\n\n                    return Err(Error::IdError);\n                }\n\n                if let Some(received_id) = self.peer_goaway_id {\n                    if id > received_id {\n                        conn.close(\n                            true,\n                            Error::IdError.to_wire(),\n                            b\"GOAWAY received with ID larger than previously received\",\n                        )?;\n\n                        return Err(Error::IdError);\n                    }\n                }\n\n                self.peer_goaway_id = Some(id);\n\n                return Ok((id, Event::GoAway));\n            },\n\n            frame::Frame::MaxPushId { push_id } => {\n                if !self.is_server {\n                    conn.close(\n                        true,\n                        Error::FrameUnexpected.to_wire(),\n                        b\"MAX_PUSH_ID received by client\",\n                    )?;\n\n                    return Err(Error::FrameUnexpected);\n                }\n\n                if push_id < self.max_push_id {\n                    conn.close(\n                        true,\n                        Error::IdError.to_wire(),\n                        b\"MAX_PUSH_ID reduced limit\",\n                    )?;\n\n                    return Err(Error::IdError);\n                }\n\n                self.max_push_id = push_id;\n            },\n\n            frame::Frame::PushPromise { .. } => {\n                if self.is_server {\n                    conn.close(\n                        true,\n                        Error::FrameUnexpected.to_wire(),\n                        b\"PUSH_PROMISE received by server\",\n                    )?;\n\n                    return Err(Error::FrameUnexpected);\n                }\n\n                if stream_id % 4 != 0 {\n                    conn.close(\n                        true,\n                        Error::FrameUnexpected.to_wire(),\n                        b\"PUSH_PROMISE received on non-request stream\",\n                    )?;\n\n                    return Err(Error::FrameUnexpected);\n                }\n\n                // TODO: implement more checks and PUSH_PROMISE event\n            },\n\n            frame::Frame::CancelPush { .. } => {\n                // TODO: implement CANCEL_PUSH frame\n            },\n\n            frame::Frame::PriorityUpdateRequest {\n                prioritized_element_id,\n                priority_field_value,\n            } => {\n                if !self.is_server {\n                    conn.close(\n                        true,\n                        Error::FrameUnexpected.to_wire(),\n                        b\"PRIORITY_UPDATE received by client\",\n                    )?;\n\n                    return Err(Error::FrameUnexpected);\n                }\n\n                if prioritized_element_id % 4 != 0 {\n                    conn.close(\n                        true,\n                        Error::FrameUnexpected.to_wire(),\n                        b\"PRIORITY_UPDATE for request stream type with wrong ID\",\n                    )?;\n\n                    return Err(Error::FrameUnexpected);\n                }\n\n                if prioritized_element_id > conn.streams.max_streams_bidi() * 4 {\n                    conn.close(\n                        true,\n                        Error::IdError.to_wire(),\n                        b\"PRIORITY_UPDATE for request stream beyond max streams limit\",\n                    )?;\n\n                    return Err(Error::IdError);\n                }\n\n                // If the PRIORITY_UPDATE is valid, consider storing the latest\n                // contents. Due to reordering, it is possible that we might\n                // receive frames that reference streams that have not yet to\n                // been opened and that's OK because it's within our concurrency\n                // limit. However, we discard PRIORITY_UPDATE that refers to\n                // streams that we know have been collected.\n                if conn.streams.is_collected(prioritized_element_id) {\n                    return Err(Error::Done);\n                }\n\n                // If the stream did not yet exist, create it and store.\n                let stream =\n                    self.streams.entry(prioritized_element_id).or_insert_with(\n                        || <stream::Stream>::new(prioritized_element_id, false),\n                    );\n\n                let had_priority_update = stream.has_last_priority_update();\n                stream.set_last_priority_update(Some(priority_field_value));\n\n                // Only trigger the event when there wasn't already a stored\n                // PRIORITY_UPDATE.\n                if !had_priority_update {\n                    return Ok((prioritized_element_id, Event::PriorityUpdate));\n                } else {\n                    return Err(Error::Done);\n                }\n            },\n\n            frame::Frame::PriorityUpdatePush {\n                prioritized_element_id,\n                ..\n            } => {\n                if !self.is_server {\n                    conn.close(\n                        true,\n                        Error::FrameUnexpected.to_wire(),\n                        b\"PRIORITY_UPDATE received by client\",\n                    )?;\n\n                    return Err(Error::FrameUnexpected);\n                }\n\n                if prioritized_element_id % 3 != 0 {\n                    conn.close(\n                        true,\n                        Error::FrameUnexpected.to_wire(),\n                        b\"PRIORITY_UPDATE for push stream type with wrong ID\",\n                    )?;\n\n                    return Err(Error::FrameUnexpected);\n                }\n\n                // TODO: we only implement this if we implement server push\n            },\n\n            frame::Frame::Unknown { .. } => (),\n        }\n\n        Err(Error::Done)\n    }\n\n    /// Collects and returns statistics about the connection.\n    #[inline]\n    pub fn stats(&self) -> Stats {\n        Stats {\n            qpack_encoder_stream_recv_bytes: self\n                .peer_qpack_streams\n                .encoder_stream_bytes,\n            qpack_decoder_stream_recv_bytes: self\n                .peer_qpack_streams\n                .decoder_stream_bytes,\n        }\n    }\n}\n\n/// Generates an HTTP/3 GREASE variable length integer.\npub fn grease_value() -> u64 {\n    let n = super::rand::rand_u64_uniform(148_764_065_110_560_899);\n    31 * n + 33\n}\n\n#[doc(hidden)]\n#[cfg(any(test, feature = \"internal\"))]\npub mod testing {\n    use super::*;\n\n    use crate::test_utils;\n    use crate::DefaultBufFactory;\n\n    /// Session is an HTTP/3 test helper structure. It holds a client, server\n    /// and pipe that allows them to communicate.\n    ///\n    /// `default()` creates a session with some sensible default\n    /// configuration. `with_configs()` allows for providing a specific\n    /// configuration.\n    ///\n    /// `handshake()` performs all the steps needed to establish an HTTP/3\n    /// connection.\n    ///\n    /// Some utility functions are provided that make it less verbose to send\n    /// request, responses and individual headers. The full quiche API remains\n    /// available for any test that need to do unconventional things (such as\n    /// bad behaviour that triggers errors).\n    pub struct Session<F = DefaultBufFactory>\n    where\n        F: BufFactory,\n    {\n        pub pipe: test_utils::Pipe<F>,\n        pub client: Connection,\n        pub server: Connection,\n    }\n\n    impl Session {\n        pub fn new() -> Result<Session> {\n            Session::<DefaultBufFactory>::new_with_buf()\n        }\n\n        pub fn with_configs(\n            config: &mut crate::Config, h3_config: &Config,\n        ) -> Result<Session> {\n            Session::<DefaultBufFactory>::with_configs_and_buf(config, h3_config)\n        }\n    }\n\n    impl<F: BufFactory> Session<F> {\n        pub fn new_with_buf() -> Result<Session<F>> {\n            fn path_relative_to_manifest_dir(path: &str) -> String {\n                std::fs::canonicalize(\n                    std::path::Path::new(env!(\"CARGO_MANIFEST_DIR\")).join(path),\n                )\n                .unwrap()\n                .to_string_lossy()\n                .into_owned()\n            }\n\n            let mut config = crate::Config::new(crate::PROTOCOL_VERSION)?;\n            config.load_cert_chain_from_pem_file(\n                &path_relative_to_manifest_dir(\"examples/cert.crt\"),\n            )?;\n            config.load_priv_key_from_pem_file(\n                &path_relative_to_manifest_dir(\"examples/cert.key\"),\n            )?;\n            config.set_application_protos(&[b\"h3\"])?;\n            config.set_initial_max_data(1500);\n            config.set_initial_max_stream_data_bidi_local(150);\n            config.set_initial_max_stream_data_bidi_remote(150);\n            config.set_initial_max_stream_data_uni(150);\n            config.set_initial_max_streams_bidi(5);\n            config.set_initial_max_streams_uni(5);\n            config.verify_peer(false);\n            config.enable_dgram(true, 3, 3);\n            config.set_ack_delay_exponent(8);\n\n            let h3_config = Config::new()?;\n            Session::with_configs_and_buf(&mut config, &h3_config)\n        }\n\n        pub fn with_configs_and_buf(\n            config: &mut crate::Config, h3_config: &Config,\n        ) -> Result<Session<F>> {\n            let pipe = test_utils::Pipe::with_config_and_buf(config)?;\n            let client_dgram = pipe.client.dgram_enabled();\n            let server_dgram = pipe.server.dgram_enabled();\n            Ok(Session {\n                pipe,\n                client: Connection::new(h3_config, false, client_dgram)?,\n                server: Connection::new(h3_config, true, server_dgram)?,\n            })\n        }\n\n        /// Do the HTTP/3 handshake so both ends are in sane initial state.\n        pub fn handshake(&mut self) -> Result<()> {\n            self.pipe.handshake()?;\n\n            // Client streams.\n            self.client.send_settings(&mut self.pipe.client)?;\n            self.pipe.advance().ok();\n\n            self.client\n                .open_qpack_encoder_stream(&mut self.pipe.client)?;\n            self.pipe.advance().ok();\n\n            self.client\n                .open_qpack_decoder_stream(&mut self.pipe.client)?;\n            self.pipe.advance().ok();\n\n            if self.pipe.client.grease {\n                self.client.open_grease_stream(&mut self.pipe.client)?;\n            }\n\n            self.pipe.advance().ok();\n\n            // Server streams.\n            self.server.send_settings(&mut self.pipe.server)?;\n            self.pipe.advance().ok();\n\n            self.server\n                .open_qpack_encoder_stream(&mut self.pipe.server)?;\n            self.pipe.advance().ok();\n\n            self.server\n                .open_qpack_decoder_stream(&mut self.pipe.server)?;\n            self.pipe.advance().ok();\n\n            if self.pipe.server.grease {\n                self.server.open_grease_stream(&mut self.pipe.server)?;\n            }\n\n            self.advance().ok();\n\n            while self.client.poll(&mut self.pipe.client).is_ok() {\n                // Do nothing.\n            }\n\n            while self.server.poll(&mut self.pipe.server).is_ok() {\n                // Do nothing.\n            }\n\n            Ok(())\n        }\n\n        /// Advances the session pipe over the buffer.\n        pub fn advance(&mut self) -> crate::Result<()> {\n            self.pipe.advance()\n        }\n\n        /// Polls the client for events.\n        pub fn poll_client(&mut self) -> Result<(u64, Event)> {\n            self.client.poll(&mut self.pipe.client)\n        }\n\n        /// Polls the server for events.\n        pub fn poll_server(&mut self) -> Result<(u64, Event)> {\n            self.server.poll(&mut self.pipe.server)\n        }\n\n        /// Sends a request from client with default headers.\n        ///\n        /// On success it returns the newly allocated stream and the headers.\n        pub fn send_request(&mut self, fin: bool) -> Result<(u64, Vec<Header>)> {\n            let req = vec![\n                Header::new(b\":method\", b\"GET\"),\n                Header::new(b\":scheme\", b\"https\"),\n                Header::new(b\":authority\", b\"quic.tech\"),\n                Header::new(b\":path\", b\"/test\"),\n                Header::new(b\"user-agent\", b\"quiche-test\"),\n            ];\n\n            let stream =\n                self.client.send_request(&mut self.pipe.client, &req, fin)?;\n\n            self.advance().ok();\n\n            Ok((stream, req))\n        }\n\n        /// Sends a response from server with default headers.\n        ///\n        /// On success it returns the headers.\n        pub fn send_response(\n            &mut self, stream: u64, fin: bool,\n        ) -> Result<Vec<Header>> {\n            let resp = vec![\n                Header::new(b\":status\", b\"200\"),\n                Header::new(b\"server\", b\"quiche-test\"),\n            ];\n\n            self.server.send_response(\n                &mut self.pipe.server,\n                stream,\n                &resp,\n                fin,\n            )?;\n\n            self.advance().ok();\n\n            Ok(resp)\n        }\n\n        /// Sends some default payload from client.\n        ///\n        /// On success it returns the payload.\n        pub fn send_body_client(\n            &mut self, stream: u64, fin: bool,\n        ) -> Result<Vec<u8>> {\n            let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n\n            self.client\n                .send_body(&mut self.pipe.client, stream, &bytes, fin)?;\n\n            self.advance().ok();\n\n            Ok(bytes)\n        }\n\n        /// Fetches DATA payload from the server.\n        ///\n        /// On success it returns the number of bytes received.\n        pub fn recv_body_client(\n            &mut self, stream: u64, buf: &mut [u8],\n        ) -> Result<usize> {\n            self.client.recv_body(&mut self.pipe.client, stream, buf)\n        }\n\n        /// Sends some default payload from server.\n        ///\n        /// On success it returns the payload.\n        pub fn send_body_server(\n            &mut self, stream: u64, fin: bool,\n        ) -> Result<Vec<u8>> {\n            let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n\n            self.server\n                .send_body(&mut self.pipe.server, stream, &bytes, fin)?;\n\n            self.advance().ok();\n\n            Ok(bytes)\n        }\n\n        /// Fetches DATA payload from the client.\n        ///\n        /// On success it returns the number of bytes received.\n        pub fn recv_body_server(\n            &mut self, stream: u64, buf: &mut [u8],\n        ) -> Result<usize> {\n            self.server.recv_body(&mut self.pipe.server, stream, buf)\n        }\n\n        /// Sends a single HTTP/3 frame from the client.\n        pub fn send_frame_client(\n            &mut self, frame: frame::Frame, stream_id: u64, fin: bool,\n        ) -> Result<()> {\n            let mut d = [42; 65535];\n\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            frame.to_bytes(&mut b)?;\n\n            let off = b.off();\n            self.pipe.client.stream_send(stream_id, &d[..off], fin)?;\n\n            self.advance().ok();\n\n            Ok(())\n        }\n\n        /// Send an HTTP/3 DATAGRAM with default data from the client.\n        ///\n        /// On success it returns the data.\n        pub fn send_dgram_client(&mut self, flow_id: u64) -> Result<Vec<u8>> {\n            let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n            let len = octets::varint_len(flow_id) + bytes.len();\n            let mut d = vec![0; len];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            b.put_varint(flow_id)?;\n            b.put_bytes(&bytes)?;\n\n            self.pipe.client.dgram_send(&d)?;\n\n            self.advance().ok();\n\n            Ok(bytes)\n        }\n\n        /// Receives an HTTP/3 DATAGRAM from the server.\n        ///\n        /// On success it returns the DATAGRAM length, flow ID and flow ID\n        /// length.\n        pub fn recv_dgram_client(\n            &mut self, buf: &mut [u8],\n        ) -> Result<(usize, u64, usize)> {\n            let len = self.pipe.client.dgram_recv(buf)?;\n            let mut b = octets::Octets::with_slice(buf);\n            let flow_id = b.get_varint()?;\n\n            Ok((len, flow_id, b.off()))\n        }\n\n        /// Send an HTTP/3 DATAGRAM with default data from the server\n        ///\n        /// On success it returns the data.\n        pub fn send_dgram_server(&mut self, flow_id: u64) -> Result<Vec<u8>> {\n            let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n            let len = octets::varint_len(flow_id) + bytes.len();\n            let mut d = vec![0; len];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            b.put_varint(flow_id)?;\n            b.put_bytes(&bytes)?;\n\n            self.pipe.server.dgram_send(&d)?;\n\n            self.advance().ok();\n\n            Ok(bytes)\n        }\n\n        /// Receives an HTTP/3 DATAGRAM from the client.\n        ///\n        /// On success it returns the DATAGRAM length, flow ID and flow ID\n        /// length.\n        pub fn recv_dgram_server(\n            &mut self, buf: &mut [u8],\n        ) -> Result<(usize, u64, usize)> {\n            let len = self.pipe.server.dgram_recv(buf)?;\n            let mut b = octets::Octets::with_slice(buf);\n            let flow_id = b.get_varint()?;\n\n            Ok((len, flow_id, b.off()))\n        }\n\n        /// Sends a single HTTP/3 frame from the server.\n        pub fn send_frame_server(\n            &mut self, frame: frame::Frame, stream_id: u64, fin: bool,\n        ) -> Result<()> {\n            let mut d = [42; 65535];\n\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            frame.to_bytes(&mut b)?;\n\n            let off = b.off();\n            self.pipe.server.stream_send(stream_id, &d[..off], fin)?;\n\n            self.advance().ok();\n\n            Ok(())\n        }\n\n        /// Sends an arbitrary buffer of HTTP/3 stream data from the client.\n        pub fn send_arbitrary_stream_data_client(\n            &mut self, data: &[u8], stream_id: u64, fin: bool,\n        ) -> Result<()> {\n            self.pipe.client.stream_send(stream_id, data, fin)?;\n\n            self.advance().ok();\n\n            Ok(())\n        }\n\n        /// Sends an arbitrary buffer of HTTP/3 stream data from the server.\n        pub fn send_arbitrary_stream_data_server(\n            &mut self, data: &[u8], stream_id: u64, fin: bool,\n        ) -> Result<()> {\n            self.pipe.server.stream_send(stream_id, data, fin)?;\n\n            self.advance().ok();\n\n            Ok(())\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use super::testing::*;\n\n    #[test]\n    /// Make sure that random GREASE values is within the specified limit.\n    fn grease_value_in_varint_limit() {\n        assert!(grease_value() < 2u64.pow(62) - 1);\n    }\n\n    #[cfg(not(feature = \"openssl\"))] // 0-RTT not supported when using openssl/quictls\n    #[test]\n    fn h3_handshake_0rtt() {\n        let mut buf = [0; 65535];\n\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config\n            .set_application_protos(&[b\"proto1\", b\"proto2\"])\n            .unwrap();\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(15);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.enable_early_data();\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        // Perform initial handshake.\n        let mut pipe = crate::test_utils::Pipe::with_config(&mut config).unwrap();\n        assert_eq!(pipe.handshake(), Ok(()));\n\n        // Extract session,\n        let session = pipe.client.session().unwrap();\n\n        // Configure session on new connection.\n        let mut pipe = crate::test_utils::Pipe::with_config(&mut config).unwrap();\n        assert_eq!(pipe.client.set_session(session), Ok(()));\n\n        // Can't create an H3 connection until the QUIC connection is determined\n        // to have made sufficient early data progress.\n        assert!(matches!(\n            Connection::with_transport(&mut pipe.client, &h3_config),\n            Err(Error::InternalError)\n        ));\n\n        // Client sends initial flight.\n        let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n        // Now an H3 connection can be created.\n        assert!(Connection::with_transport(&mut pipe.client, &h3_config).is_ok());\n        assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n        // Client sends 0-RTT packet.\n        let pkt_type = crate::packet::Type::ZeroRTT;\n\n        let frames = [crate::frame::Frame::Stream {\n            stream_id: 6,\n            data: <crate::range_buf::RangeBuf>::from(b\"aaaaa\", 0, true),\n        }];\n\n        assert_eq!(\n            pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n            Ok(1200)\n        );\n\n        assert_eq!(pipe.server.undecryptable_pkts.len(), 0);\n\n        // 0-RTT stream data is readable.\n        let mut r = pipe.server.readable();\n        assert_eq!(r.next(), Some(6));\n        assert_eq!(r.next(), None);\n\n        let mut b = [0; 15];\n        assert_eq!(pipe.server.stream_recv(6, &mut b), Ok((5, true)));\n        assert_eq!(&b[..5], b\"aaaaa\");\n    }\n\n    #[test]\n    /// Send a request with no body, get a response with no body.\n    fn request_no_body_response_no_body() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        assert_eq!(stream, 0);\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let resp = s.send_response(stream, true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a request with no body, get a response with one DATA frame.\n    fn request_no_body_response_one_chunk() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n        assert_eq!(stream, 0);\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let resp = s.send_response(stream, false).unwrap();\n\n        let body = s.send_body_server(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(body.len()));\n\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a request with no body, get a response with multiple DATA frames.\n    fn request_no_body_response_many_chunks() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let total_data_frames = 4;\n\n        let resp = s.send_response(stream, false).unwrap();\n\n        for _ in 0..total_data_frames - 1 {\n            s.send_body_server(stream, false).unwrap();\n        }\n\n        let body = s.send_body_server(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        for _ in 0..total_data_frames {\n            assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(body.len()));\n        }\n\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a request with one DATA frame, get a response with no body.\n    fn request_one_chunk_response_no_body() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let body = s.send_body_client(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));\n\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let resp = s.send_response(stream, true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n    }\n\n    #[test]\n    /// Send a request with multiple DATA frames, get a response with no body.\n    fn request_many_chunks_response_no_body() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let total_data_frames = 4;\n\n        for _ in 0..total_data_frames - 1 {\n            s.send_body_client(stream, false).unwrap();\n        }\n\n        let body = s.send_body_client(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        for _ in 0..total_data_frames {\n            assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));\n        }\n\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let resp = s.send_response(stream, true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n    }\n\n    #[test]\n    /// Send a request with multiple DATA frames, get a response with one DATA\n    /// frame.\n    fn many_requests_many_chunks_response_one_chunk() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let mut reqs = Vec::new();\n\n        let (stream1, req1) = s.send_request(false).unwrap();\n        assert_eq!(stream1, 0);\n        reqs.push(req1);\n\n        let (stream2, req2) = s.send_request(false).unwrap();\n        assert_eq!(stream2, 4);\n        reqs.push(req2);\n\n        let (stream3, req3) = s.send_request(false).unwrap();\n        assert_eq!(stream3, 8);\n        reqs.push(req3);\n\n        let body = s.send_body_client(stream1, false).unwrap();\n        s.send_body_client(stream2, false).unwrap();\n        s.send_body_client(stream3, false).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        // Reverse order of writes.\n\n        s.send_body_client(stream3, true).unwrap();\n        s.send_body_client(stream2, true).unwrap();\n        s.send_body_client(stream1, true).unwrap();\n\n        let (_, ev) = s.poll_server().unwrap();\n        let ev_headers = Event::Headers {\n            list: reqs[0].clone(),\n            more_frames: true,\n        };\n        assert_eq!(ev, ev_headers);\n\n        let (_, ev) = s.poll_server().unwrap();\n        let ev_headers = Event::Headers {\n            list: reqs[1].clone(),\n            more_frames: true,\n        };\n        assert_eq!(ev, ev_headers);\n\n        let (_, ev) = s.poll_server().unwrap();\n        let ev_headers = Event::Headers {\n            list: reqs[2].clone(),\n            more_frames: true,\n        };\n        assert_eq!(ev, ev_headers);\n\n        assert_eq!(s.poll_server(), Ok((0, Event::Data)));\n        assert_eq!(s.recv_body_server(0, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_body_server(0, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_server(), Ok((0, Event::Finished)));\n\n        assert_eq!(s.poll_server(), Ok((4, Event::Data)));\n        assert_eq!(s.recv_body_server(4, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_body_server(4, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_server(), Ok((4, Event::Finished)));\n\n        assert_eq!(s.poll_server(), Ok((8, Event::Data)));\n        assert_eq!(s.recv_body_server(8, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_body_server(8, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_server(), Ok((8, Event::Finished)));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        let mut resps = Vec::new();\n\n        let resp1 = s.send_response(stream1, true).unwrap();\n        resps.push(resp1);\n\n        let resp2 = s.send_response(stream2, true).unwrap();\n        resps.push(resp2);\n\n        let resp3 = s.send_response(stream3, true).unwrap();\n        resps.push(resp3);\n\n        for _ in 0..resps.len() {\n            let (stream, ev) = s.poll_client().unwrap();\n            let ev_headers = Event::Headers {\n                list: resps[(stream / 4) as usize].clone(),\n                more_frames: false,\n            };\n            assert_eq!(ev, ev_headers);\n            assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        }\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a request with no body, get a response with one DATA frame and an\n    /// empty FIN after reception from the client.\n    fn request_no_body_response_one_chunk_empty_fin() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let resp = s.send_response(stream, false).unwrap();\n\n        let body = s.send_body_server(stream, false).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(body.len()));\n\n        assert_eq!(s.pipe.server.stream_send(stream, &[], true), Ok(0));\n        s.advance().ok();\n\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a request with no body, get a response with no body followed by\n    /// GREASE that is STREAM frame with a FIN.\n    fn request_no_body_response_no_body_with_grease() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        assert_eq!(stream, 0);\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let resp = s.send_response(stream, false).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        // Inject a GREASE frame\n        let mut d = [42; 10];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame_type = b.put_varint(148_764_065_110_560_899).unwrap();\n        s.pipe.server.stream_send(0, frame_type, false).unwrap();\n\n        let frame_len = b.put_varint(10).unwrap();\n        s.pipe.server.stream_send(0, frame_len, false).unwrap();\n\n        s.pipe.server.stream_send(0, &d, true).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Try to send DATA frames before HEADERS.\n    fn body_response_before_headers() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n        assert_eq!(stream, 0);\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        assert_eq!(\n            s.send_body_server(stream, true),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Try to send DATA frames on wrong streams, ensure the API returns an\n    /// error before anything hits the transport layer.\n    fn send_body_invalid_client_stream() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        assert_eq!(s.send_body_client(0, true), Err(Error::FrameUnexpected));\n\n        assert_eq!(\n            s.send_body_client(s.client.control_stream_id.unwrap(), true),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_client(\n                s.client.local_qpack_streams.encoder_stream_id.unwrap(),\n                true\n            ),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_client(\n                s.client.local_qpack_streams.decoder_stream_id.unwrap(),\n                true\n            ),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_client(s.client.peer_control_stream_id.unwrap(), true),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_client(\n                s.client.peer_qpack_streams.encoder_stream_id.unwrap(),\n                true\n            ),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_client(\n                s.client.peer_qpack_streams.decoder_stream_id.unwrap(),\n                true\n            ),\n            Err(Error::FrameUnexpected)\n        );\n    }\n\n    #[test]\n    /// Try to send DATA frames on wrong streams, ensure the API returns an\n    /// error before anything hits the transport layer.\n    fn send_body_invalid_server_stream() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        assert_eq!(s.send_body_server(0, true), Err(Error::FrameUnexpected));\n\n        assert_eq!(\n            s.send_body_server(s.server.control_stream_id.unwrap(), true),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_server(\n                s.server.local_qpack_streams.encoder_stream_id.unwrap(),\n                true\n            ),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_server(\n                s.server.local_qpack_streams.decoder_stream_id.unwrap(),\n                true\n            ),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_server(s.server.peer_control_stream_id.unwrap(), true),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_server(\n                s.server.peer_qpack_streams.encoder_stream_id.unwrap(),\n                true\n            ),\n            Err(Error::FrameUnexpected)\n        );\n\n        assert_eq!(\n            s.send_body_server(\n                s.server.peer_qpack_streams.decoder_stream_id.unwrap(),\n                true\n            ),\n            Err(Error::FrameUnexpected)\n        );\n    }\n\n    #[test]\n    /// Client sends request with body and trailers.\n    fn trailers() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let body = s.send_body_client(stream, false).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let req_trailers = vec![Header::new(b\"foo\", b\"bar\")];\n\n        s.client\n            .send_additional_headers(\n                &mut s.pipe.client,\n                stream,\n                &req_trailers,\n                true,\n                true,\n            )\n            .unwrap();\n\n        s.advance().ok();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        let ev_trailers = Event::Headers {\n            list: req_trailers,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_trailers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n    }\n\n    #[test]\n    /// Server responds with a 103, then a 200 with no body.\n    fn informational_response() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        assert_eq!(stream, 0);\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let info_resp = vec![\n            Header::new(b\":status\", b\"103\"),\n            Header::new(b\"link\", b\"<https://example.com>; rel=\\\"preconnect\\\"\"),\n        ];\n\n        let resp = vec![\n            Header::new(b\":status\", b\"200\"),\n            Header::new(b\"server\", b\"quiche-test\"),\n        ];\n\n        s.server\n            .send_response(&mut s.pipe.server, stream, &info_resp, false)\n            .unwrap();\n\n        s.server\n            .send_additional_headers(\n                &mut s.pipe.server,\n                stream,\n                &resp,\n                false,\n                true,\n            )\n            .unwrap();\n\n        s.advance().ok();\n\n        let ev_info_headers = Event::Headers {\n            list: info_resp,\n            more_frames: true,\n        };\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_info_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Server responds with a 103, then attempts to send a 200 using\n    /// send_response again, which should fail.\n    fn no_multiple_response() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        assert_eq!(stream, 0);\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let info_resp = vec![\n            Header::new(b\":status\", b\"103\"),\n            Header::new(b\"link\", b\"<https://example.com>; rel=\\\"preconnect\\\"\"),\n        ];\n\n        let resp = vec![\n            Header::new(b\":status\", b\"200\"),\n            Header::new(b\"server\", b\"quiche-test\"),\n        ];\n\n        s.server\n            .send_response(&mut s.pipe.server, stream, &info_resp, false)\n            .unwrap();\n\n        assert_eq!(\n            Err(Error::FrameUnexpected),\n            s.server\n                .send_response(&mut s.pipe.server, stream, &resp, true)\n        );\n\n        s.advance().ok();\n\n        let ev_info_headers = Event::Headers {\n            list: info_resp,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_info_headers)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Server attempts to use send_additional_headers before initial response.\n    fn no_send_additional_before_initial_response() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        assert_eq!(stream, 0);\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let info_resp = vec![\n            Header::new(b\":status\", b\"103\"),\n            Header::new(b\"link\", b\"<https://example.com>; rel=\\\"preconnect\\\"\"),\n        ];\n\n        assert_eq!(\n            Err(Error::FrameUnexpected),\n            s.server.send_additional_headers(\n                &mut s.pipe.server,\n                stream,\n                &info_resp,\n                false,\n                false\n            )\n        );\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Client sends multiple HEADERS before data.\n    fn additional_headers_before_data_client() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let req_trailer = vec![Header::new(b\"goodbye\", b\"world\")];\n\n        assert_eq!(\n            s.client.send_additional_headers(\n                &mut s.pipe.client,\n                stream,\n                &req_trailer,\n                true,\n                false\n            ),\n            Ok(())\n        );\n\n        s.advance().ok();\n\n        let ev_initial_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        let ev_trailing_headers = Event::Headers {\n            list: req_trailer,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_initial_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, ev_trailing_headers)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Client sends multiple HEADERS before data.\n    fn data_after_trailers_client() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let body = s.send_body_client(stream, false).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let req_trailers = vec![Header::new(b\"foo\", b\"bar\")];\n\n        s.client\n            .send_additional_headers(\n                &mut s.pipe.client,\n                stream,\n                &req_trailers,\n                true,\n                false,\n            )\n            .unwrap();\n\n        s.advance().ok();\n\n        s.send_frame_client(\n            frame::Frame::Data {\n                payload: vec![1, 2, 3, 4],\n            },\n            stream,\n            true,\n        )\n        .unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        let ev_trailers = Event::Headers {\n            list: req_trailers,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_server(), Ok((stream, ev_trailers)));\n        assert_eq!(s.poll_server(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a MAX_PUSH_ID frame from the client on a valid stream.\n    fn max_push_id_from_client_good() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_client(\n            frame::Frame::MaxPushId { push_id: 1 },\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a MAX_PUSH_ID frame from the client on an invalid stream.\n    fn max_push_id_from_client_bad_stream() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        s.send_frame_client(\n            frame::Frame::MaxPushId { push_id: 2 },\n            stream,\n            false,\n        )\n        .unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a sequence of MAX_PUSH_ID frames from the client that attempt to\n    /// reduce the limit.\n    fn max_push_id_from_client_limit_reduction() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_client(\n            frame::Frame::MaxPushId { push_id: 2 },\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        s.send_frame_client(\n            frame::Frame::MaxPushId { push_id: 1 },\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::IdError));\n    }\n\n    #[test]\n    /// Send a MAX_PUSH_ID frame from the server, which is forbidden.\n    fn max_push_id_from_server() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_server(\n            frame::Frame::MaxPushId { push_id: 1 },\n            s.server.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_client(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a PUSH_PROMISE frame from the client, which is forbidden.\n    fn push_promise_from_client() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let header_block = s.client.encode_header_block(&req).unwrap();\n\n        s.send_frame_client(\n            frame::Frame::PushPromise {\n                push_id: 1,\n                header_block,\n            },\n            stream,\n            false,\n        )\n        .unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a CANCEL_PUSH frame from the client.\n    fn cancel_push_from_client() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_client(\n            frame::Frame::CancelPush { push_id: 1 },\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a CANCEL_PUSH frame from the client on an invalid stream.\n    fn cancel_push_from_client_bad_stream() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        s.send_frame_client(\n            frame::Frame::CancelPush { push_id: 2 },\n            stream,\n            false,\n        )\n        .unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a CANCEL_PUSH frame from the client.\n    fn cancel_push_from_server() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_server(\n            frame::Frame::CancelPush { push_id: 1 },\n            s.server.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a GOAWAY frame from the client.\n    fn goaway_from_client_good() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.client.send_goaway(&mut s.pipe.client, 100).unwrap();\n\n        s.advance().ok();\n\n        // TODO: server push\n        assert_eq!(s.poll_server(), Ok((0, Event::GoAway)));\n    }\n\n    #[test]\n    /// Send a GOAWAY frame from the server.\n    fn goaway_from_server_good() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.server.send_goaway(&mut s.pipe.server, 4000).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_client(), Ok((4000, Event::GoAway)));\n    }\n\n    #[test]\n    /// A client MUST NOT send a request after it receives GOAWAY.\n    fn client_request_after_goaway() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.server.send_goaway(&mut s.pipe.server, 4000).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_client(), Ok((4000, Event::GoAway)));\n\n        assert_eq!(s.send_request(true), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a GOAWAY frame from the server, using an invalid goaway ID.\n    fn goaway_from_server_invalid_id() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_server(\n            frame::Frame::GoAway { id: 1 },\n            s.server.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_client(), Err(Error::IdError));\n    }\n\n    #[test]\n    /// Send multiple GOAWAY frames from the server, that increase the goaway\n    /// ID.\n    fn goaway_from_server_increase_id() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_server(\n            frame::Frame::GoAway { id: 0 },\n            s.server.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        s.send_frame_server(\n            frame::Frame::GoAway { id: 4 },\n            s.server.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_client(), Ok((0, Event::GoAway)));\n\n        assert_eq!(s.poll_client(), Err(Error::IdError));\n    }\n\n    #[test]\n    #[cfg(feature = \"sfv\")]\n    fn parse_priority_field_value() {\n        // Legal dicts\n        assert_eq!(\n            Ok(Priority::new(0, false)),\n            Priority::try_from(b\"u=0\".as_slice())\n        );\n        assert_eq!(\n            Ok(Priority::new(3, false)),\n            Priority::try_from(b\"u=3\".as_slice())\n        );\n        assert_eq!(\n            Ok(Priority::new(7, false)),\n            Priority::try_from(b\"u=7\".as_slice())\n        );\n\n        assert_eq!(\n            Ok(Priority::new(0, true)),\n            Priority::try_from(b\"u=0, i\".as_slice())\n        );\n        assert_eq!(\n            Ok(Priority::new(3, true)),\n            Priority::try_from(b\"u=3, i\".as_slice())\n        );\n        assert_eq!(\n            Ok(Priority::new(7, true)),\n            Priority::try_from(b\"u=7, i\".as_slice())\n        );\n\n        assert_eq!(\n            Ok(Priority::new(0, true)),\n            Priority::try_from(b\"u=0, i=?1\".as_slice())\n        );\n        assert_eq!(\n            Ok(Priority::new(3, true)),\n            Priority::try_from(b\"u=3, i=?1\".as_slice())\n        );\n        assert_eq!(\n            Ok(Priority::new(7, true)),\n            Priority::try_from(b\"u=7, i=?1\".as_slice())\n        );\n\n        assert_eq!(\n            Ok(Priority::new(3, false)),\n            Priority::try_from(b\"\".as_slice())\n        );\n\n        assert_eq!(\n            Ok(Priority::new(0, true)),\n            Priority::try_from(b\"u=0;foo, i;bar\".as_slice())\n        );\n        assert_eq!(\n            Ok(Priority::new(3, true)),\n            Priority::try_from(b\"u=3;hello, i;world\".as_slice())\n        );\n        assert_eq!(\n            Ok(Priority::new(7, true)),\n            Priority::try_from(b\"u=7;croeso, i;gymru\".as_slice())\n        );\n\n        assert_eq!(\n            Ok(Priority::new(0, true)),\n            Priority::try_from(b\"u=0, i, spinaltap=11\".as_slice())\n        );\n\n        // Illegal formats\n        assert_eq!(Err(Error::Done), Priority::try_from(b\"0\".as_slice()));\n        assert_eq!(\n            Ok(Priority::new(7, false)),\n            Priority::try_from(b\"u=-1\".as_slice())\n        );\n        assert_eq!(Err(Error::Done), Priority::try_from(b\"u=0.2\".as_slice()));\n        assert_eq!(\n            Ok(Priority::new(7, false)),\n            Priority::try_from(b\"u=100\".as_slice())\n        );\n        assert_eq!(\n            Err(Error::Done),\n            Priority::try_from(b\"u=3, i=true\".as_slice())\n        );\n\n        // Trailing comma in dict is malformed\n        assert_eq!(Err(Error::Done), Priority::try_from(b\"u=7, \".as_slice()));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for request stream from the client.\n    fn priority_update_request() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 3,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for request stream from the client.\n    fn priority_update_single_stream_rearm() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 3,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 5,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // There is only one PRIORITY_UPDATE frame to read. Once read, the event\n        // will rearm ready for more.\n        assert_eq!(s.server.take_last_priority_update(0), Ok(b\"u=5\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(0), Err(Error::Done));\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 7,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.server.take_last_priority_update(0), Ok(b\"u=7\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(0), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send multiple PRIORITY_UPDATE frames for different streams from the\n    /// client across multiple flights of exchange.\n    fn priority_update_request_multiple_stream_arm_multiple_flights() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 3,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 4, &Priority {\n                urgency: 1,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((4, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 8, &Priority {\n                urgency: 2,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((8, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.server.take_last_priority_update(0), Ok(b\"u=3\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(4), Ok(b\"u=1\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(8), Ok(b\"u=2\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(0), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send multiple PRIORITY_UPDATE frames for different streams from the\n    /// client across a single flight.\n    fn priority_update_request_multiple_stream_arm_single_flight() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let mut d = [42; 65535];\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let p1 = frame::Frame::PriorityUpdateRequest {\n            prioritized_element_id: 0,\n            priority_field_value: b\"u=3\".to_vec(),\n        };\n\n        let p2 = frame::Frame::PriorityUpdateRequest {\n            prioritized_element_id: 4,\n            priority_field_value: b\"u=3\".to_vec(),\n        };\n\n        let p3 = frame::Frame::PriorityUpdateRequest {\n            prioritized_element_id: 8,\n            priority_field_value: b\"u=3\".to_vec(),\n        };\n\n        p1.to_bytes(&mut b).unwrap();\n        p2.to_bytes(&mut b).unwrap();\n        p3.to_bytes(&mut b).unwrap();\n\n        let off = b.off();\n        s.pipe\n            .client\n            .stream_send(s.client.control_stream_id.unwrap(), &d[..off], false)\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Ok((4, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Ok((8, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.server.take_last_priority_update(0), Ok(b\"u=3\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(4), Ok(b\"u=3\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(8), Ok(b\"u=3\".to_vec()));\n\n        assert_eq!(s.server.take_last_priority_update(0), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for a request stream, before and after the stream\n    /// has been completed.\n    fn priority_update_request_collected_completed() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 3,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        let (stream, req) = s.send_request(true).unwrap();\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        // Priority event is generated before request headers.\n        assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.server.take_last_priority_update(0), Ok(b\"u=3\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(0), Err(Error::Done));\n\n        let resp = s.send_response(stream, true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        // Now send a PRIORITY_UPDATE for the completed request stream.\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 3,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        // No event generated at server\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for a request stream, before and after the stream\n    /// has been stopped.\n    fn priority_update_request_collected_stopped() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 3,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        let (stream, req) = s.send_request(false).unwrap();\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        // Priority event is generated before request headers.\n        assert_eq!(s.poll_server(), Ok((0, Event::PriorityUpdate)));\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.server.take_last_priority_update(0), Ok(b\"u=3\".to_vec()));\n        assert_eq!(s.server.take_last_priority_update(0), Err(Error::Done));\n\n        s.pipe\n            .client\n            .stream_shutdown(stream, crate::Shutdown::Write, 0x100)\n            .unwrap();\n        s.pipe\n            .client\n            .stream_shutdown(stream, crate::Shutdown::Read, 0x100)\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((0, Event::Reset(0x100))));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Now send a PRIORITY_UPDATE for the closed request stream.\n        s.client\n            .send_priority_update_for_request(&mut s.pipe.client, 0, &Priority {\n                urgency: 3,\n                incremental: false,\n            })\n            .unwrap();\n        s.advance().ok();\n\n        // No event generated at server\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert!(s.pipe.server.streams.is_collected(0));\n        assert!(s.pipe.client.streams.is_collected(0));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for push stream from the client.\n    fn priority_update_push() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_client(\n            frame::Frame::PriorityUpdatePush {\n                prioritized_element_id: 3,\n                priority_field_value: b\"u=3\".to_vec(),\n            },\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for request stream from the client but for an\n    /// incorrect stream type.\n    fn priority_update_request_bad_stream() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_client(\n            frame::Frame::PriorityUpdateRequest {\n                prioritized_element_id: 5,\n                priority_field_value: b\"u=3\".to_vec(),\n            },\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for push stream from the client but for an\n    /// incorrect stream type.\n    fn priority_update_push_bad_stream() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_client(\n            frame::Frame::PriorityUpdatePush {\n                prioritized_element_id: 5,\n                priority_field_value: b\"u=3\".to_vec(),\n            },\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for request stream from the server.\n    fn priority_update_request_from_server() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_server(\n            frame::Frame::PriorityUpdateRequest {\n                prioritized_element_id: 0,\n                priority_field_value: b\"u=3\".to_vec(),\n            },\n            s.server.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_client(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Send a PRIORITY_UPDATE for request stream from the server.\n    fn priority_update_push_from_server() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_server(\n            frame::Frame::PriorityUpdatePush {\n                prioritized_element_id: 0,\n                priority_field_value: b\"u=3\".to_vec(),\n            },\n            s.server.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.poll_client(), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Ensure quiche allocates streams for client and server roles as expected.\n    fn uni_stream_local_counting() {\n        let config = Config::new().unwrap();\n\n        let h3_cln = Connection::new(&config, false, false).unwrap();\n        assert_eq!(h3_cln.next_uni_stream_id, 2);\n\n        let h3_srv = Connection::new(&config, true, false).unwrap();\n        assert_eq!(h3_srv.next_uni_stream_id, 3);\n    }\n\n    #[test]\n    /// Client opens multiple control streams, which is forbidden.\n    fn open_multiple_control_streams() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let stream_id = s.client.next_uni_stream_id;\n\n        let mut d = [42; 8];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        s.pipe\n            .client\n            .stream_send(\n                stream_id,\n                b.put_varint(stream::HTTP3_CONTROL_STREAM_TYPE_ID).unwrap(),\n                false,\n            )\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Err(Error::StreamCreationError));\n    }\n\n    #[test]\n    /// Client closes the control stream, which is forbidden.\n    fn close_control_stream_after_type() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.pipe\n            .client\n            .stream_send(s.client.control_stream_id.unwrap(), &[], true)\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(\n            Err(Error::ClosedCriticalStream),\n            s.server.poll(&mut s.pipe.server)\n        );\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n    }\n\n    #[test]\n    /// Client closes the control stream after a frame is sent, which is\n    /// forbidden.\n    fn close_control_stream_after_frame() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_client(\n            frame::Frame::MaxPushId { push_id: 1 },\n            s.client.control_stream_id.unwrap(),\n            true,\n        )\n        .unwrap();\n\n        assert_eq!(\n            Err(Error::ClosedCriticalStream),\n            s.server.poll(&mut s.pipe.server)\n        );\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n    }\n\n    #[test]\n    /// Client resets the control stream, which is forbidden.\n    fn reset_control_stream_after_type() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.pipe\n            .client\n            .stream_shutdown(\n                s.client.control_stream_id.unwrap(),\n                crate::Shutdown::Write,\n                0,\n            )\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(\n            Err(Error::ClosedCriticalStream),\n            s.server.poll(&mut s.pipe.server)\n        );\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n    }\n\n    #[test]\n    /// Client resets the control stream after a frame is sent, which is\n    /// forbidden.\n    fn reset_control_stream_after_frame() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.send_frame_client(\n            frame::Frame::MaxPushId { push_id: 1 },\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n\n        s.pipe\n            .client\n            .stream_shutdown(\n                s.client.control_stream_id.unwrap(),\n                crate::Shutdown::Write,\n                0,\n            )\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(\n            Err(Error::ClosedCriticalStream),\n            s.server.poll(&mut s.pipe.server)\n        );\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n    }\n\n    #[test]\n    /// Client closes QPACK stream, which is forbidden.\n    fn close_qpack_stream_after_type() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.pipe\n            .client\n            .stream_send(\n                s.client.local_qpack_streams.encoder_stream_id.unwrap(),\n                &[],\n                true,\n            )\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(\n            Err(Error::ClosedCriticalStream),\n            s.server.poll(&mut s.pipe.server)\n        );\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n    }\n\n    #[test]\n    /// Client closes QPACK stream after sending some stuff, which is forbidden.\n    fn close_qpack_stream_after_data() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let stream_id = s.client.local_qpack_streams.encoder_stream_id.unwrap();\n        let d = [0; 1];\n\n        s.pipe.client.stream_send(stream_id, &d, false).unwrap();\n        s.pipe.client.stream_send(stream_id, &d, true).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(\n            Err(Error::ClosedCriticalStream),\n            s.server.poll(&mut s.pipe.server)\n        );\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n    }\n\n    #[test]\n    /// Client resets QPACK stream, which is forbidden.\n    fn reset_qpack_stream_after_type() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        s.pipe\n            .client\n            .stream_shutdown(\n                s.client.local_qpack_streams.encoder_stream_id.unwrap(),\n                crate::Shutdown::Write,\n                0,\n            )\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(\n            Err(Error::ClosedCriticalStream),\n            s.server.poll(&mut s.pipe.server)\n        );\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n    }\n\n    #[test]\n    /// Client resets QPACK stream after sending some stuff, which is forbidden.\n    fn reset_qpack_stream_after_data() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let stream_id = s.client.local_qpack_streams.encoder_stream_id.unwrap();\n        let d = [0; 1];\n\n        s.pipe.client.stream_send(stream_id, &d, false).unwrap();\n        s.pipe.client.stream_send(stream_id, &d, false).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n\n        s.pipe\n            .client\n            .stream_shutdown(stream_id, crate::Shutdown::Write, 0)\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(\n            Err(Error::ClosedCriticalStream),\n            s.server.poll(&mut s.pipe.server)\n        );\n        assert_eq!(Err(Error::Done), s.server.poll(&mut s.pipe.server));\n    }\n\n    #[test]\n    /// Client sends QPACK data.\n    fn qpack_data() {\n        // TODO: QPACK instructions are ignored until dynamic table support is\n        // added so we just test that the data is safely ignored.\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let e_stream_id = s.client.local_qpack_streams.encoder_stream_id.unwrap();\n        let d_stream_id = s.client.local_qpack_streams.decoder_stream_id.unwrap();\n        let d = [0; 20];\n\n        s.pipe.client.stream_send(e_stream_id, &d, false).unwrap();\n        s.advance().ok();\n\n        s.pipe.client.stream_send(d_stream_id, &d, false).unwrap();\n        s.advance().ok();\n\n        match s.server.poll(&mut s.pipe.server) {\n            Ok(_) => panic!(),\n\n            Err(Error::Done) => {\n                assert_eq!(s.server.peer_qpack_streams.encoder_stream_bytes, 20);\n                assert_eq!(s.server.peer_qpack_streams.decoder_stream_bytes, 20);\n            },\n\n            Err(_) => {\n                panic!();\n            },\n        }\n\n        let stats = s.server.stats();\n        assert_eq!(stats.qpack_encoder_stream_recv_bytes, 20);\n        assert_eq!(stats.qpack_decoder_stream_recv_bytes, 20);\n    }\n\n    #[test]\n    /// Tests limits for the stream state buffer maximum size.\n    fn max_state_buf_size() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let req = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"https\"),\n            Header::new(b\":authority\", b\"quic.tech\"),\n            Header::new(b\":path\", b\"/test\"),\n            Header::new(b\"user-agent\", b\"quiche-test\"),\n        ];\n\n        assert_eq!(\n            s.client.send_request(&mut s.pipe.client, &req, false),\n            Ok(0)\n        );\n\n        s.advance().ok();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.server.poll(&mut s.pipe.server), Ok((0, ev_headers)));\n\n        // DATA frames don't consume the state buffer, so can be of any size.\n        let mut d = [42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame_type = b.put_varint(frame::DATA_FRAME_TYPE_ID).unwrap();\n        s.pipe.client.stream_send(0, frame_type, false).unwrap();\n\n        let frame_len = b.put_varint(1 << 24).unwrap();\n        s.pipe.client.stream_send(0, frame_len, false).unwrap();\n\n        s.pipe.client.stream_send(0, &d, false).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.server.poll(&mut s.pipe.server), Ok((0, Event::Data)));\n\n        // GREASE frames consume the state buffer, so need to be limited.\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let mut d = [42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame_type = b.put_varint(148_764_065_110_560_899).unwrap();\n        s.pipe.client.stream_send(0, frame_type, false).unwrap();\n\n        let frame_len = b.put_varint(1 << 24).unwrap();\n        s.pipe.client.stream_send(0, frame_len, false).unwrap();\n\n        s.pipe.client.stream_send(0, &d, false).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.server.poll(&mut s.pipe.server), Err(Error::ExcessiveLoad));\n    }\n\n    #[test]\n    /// Tests that DATA frames are properly truncated depending on the request\n    /// stream's outgoing flow control capacity.\n    fn stream_backpressure() {\n        let bytes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let total_data_frames = 6;\n\n        for _ in 0..total_data_frames {\n            assert_eq!(\n                s.client\n                    .send_body(&mut s.pipe.client, stream, &bytes, false),\n                Ok(bytes.len())\n            );\n\n            s.advance().ok();\n        }\n\n        assert_eq!(\n            s.client.send_body(&mut s.pipe.client, stream, &bytes, true),\n            Ok(bytes.len() - 2)\n        );\n\n        s.advance().ok();\n\n        let mut recv_buf = vec![0; bytes.len()];\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        for _ in 0..total_data_frames {\n            assert_eq!(\n                s.recv_body_server(stream, &mut recv_buf),\n                Ok(bytes.len())\n            );\n        }\n\n        assert_eq!(\n            s.recv_body_server(stream, &mut recv_buf),\n            Ok(bytes.len() - 2)\n        );\n\n        // Fin flag from last send_body() call was not sent as the buffer was\n        // only partially written.\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.pipe.server.data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.server.stream_data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.server.data_blocked_recv_count, 0);\n        assert_eq!(s.pipe.server.stream_data_blocked_recv_count, 1);\n\n        assert_eq!(s.pipe.client.data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.client.stream_data_blocked_sent_count, 1);\n        assert_eq!(s.pipe.client.data_blocked_recv_count, 0);\n        assert_eq!(s.pipe.client.stream_data_blocked_recv_count, 0);\n    }\n\n    #[test]\n    /// Tests that the max header list size setting is enforced.\n    fn request_max_header_size_limit() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(1500);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(5);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let mut h3_config = Config::new().unwrap();\n        h3_config.set_max_field_section_size(65);\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n\n        s.handshake().unwrap();\n\n        let req = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"https\"),\n            Header::new(b\":authority\", b\"quic.tech\"),\n            Header::new(b\":path\", b\"/test\"),\n            Header::new(b\"aaaaaaa\", b\"aaaaaaaa\"),\n        ];\n\n        let stream = s\n            .client\n            .send_request(&mut s.pipe.client, &req, true)\n            .unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(stream, 0);\n\n        assert_eq!(s.poll_server(), Err(Error::ExcessiveLoad));\n\n        assert_eq!(\n            s.pipe.server.local_error.as_ref().unwrap().error_code,\n            Error::to_wire(Error::ExcessiveLoad)\n        );\n    }\n\n    #[test]\n    /// Tests that Error::TransportError contains a transport error.\n    fn transport_error() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let req = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"https\"),\n            Header::new(b\":authority\", b\"quic.tech\"),\n            Header::new(b\":path\", b\"/test\"),\n            Header::new(b\"user-agent\", b\"quiche-test\"),\n        ];\n\n        // We need to open all streams in the same flight, so we can't use the\n        // Session::send_request() method because it also calls advance(),\n        // otherwise the server would send a MAX_STREAMS frame and the client\n        // wouldn't hit the streams limit.\n        assert_eq!(s.client.send_request(&mut s.pipe.client, &req, true), Ok(0));\n        assert_eq!(s.client.send_request(&mut s.pipe.client, &req, true), Ok(4));\n        assert_eq!(s.client.send_request(&mut s.pipe.client, &req, true), Ok(8));\n        assert_eq!(\n            s.client.send_request(&mut s.pipe.client, &req, true),\n            Ok(12)\n        );\n        assert_eq!(\n            s.client.send_request(&mut s.pipe.client, &req, true),\n            Ok(16)\n        );\n\n        assert_eq!(\n            s.client.send_request(&mut s.pipe.client, &req, true),\n            Err(Error::TransportError(crate::Error::StreamLimit))\n        );\n    }\n\n    #[test]\n    /// Tests that sending DATA before HEADERS causes an error.\n    fn data_before_headers() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let mut d = [42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame_type = b.put_varint(frame::DATA_FRAME_TYPE_ID).unwrap();\n        s.pipe.client.stream_send(0, frame_type, false).unwrap();\n\n        let frame_len = b.put_varint(5).unwrap();\n        s.pipe.client.stream_send(0, frame_len, false).unwrap();\n\n        s.pipe.client.stream_send(0, b\"hello\", false).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(\n            s.server.poll(&mut s.pipe.server),\n            Err(Error::FrameUnexpected)\n        );\n    }\n\n    #[test]\n    /// Tests that calling poll() after an error occurred does nothing.\n    fn poll_after_error() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let mut d = [42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame_type = b.put_varint(148_764_065_110_560_899).unwrap();\n        s.pipe.client.stream_send(0, frame_type, false).unwrap();\n\n        let frame_len = b.put_varint(1 << 24).unwrap();\n        s.pipe.client.stream_send(0, frame_len, false).unwrap();\n\n        s.pipe.client.stream_send(0, &d, false).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.server.poll(&mut s.pipe.server), Err(Error::ExcessiveLoad));\n\n        // Try to call poll() again after an error occurred.\n        assert_eq!(s.server.poll(&mut s.pipe.server), Err(Error::Done));\n    }\n\n    #[test]\n    /// Tests that we limit sending HEADERS based on the stream capacity.\n    fn headers_blocked() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(70);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n\n        s.handshake().unwrap();\n\n        let req = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"https\"),\n            Header::new(b\":authority\", b\"quic.tech\"),\n            Header::new(b\":path\", b\"/test\"),\n        ];\n\n        assert_eq!(s.client.send_request(&mut s.pipe.client, &req, true), Ok(0));\n\n        assert_eq!(\n            s.client.send_request(&mut s.pipe.client, &req, true),\n            Err(Error::StreamBlocked)\n        );\n\n        // Clear the writable stream queue.\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(2));\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(6));\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(10));\n        assert_eq!(s.pipe.client.stream_writable_next(), None);\n\n        s.advance().ok();\n\n        // Once the server gives flow control credits back, we can send the\n        // request.\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(4));\n        assert_eq!(s.client.send_request(&mut s.pipe.client, &req, true), Ok(4));\n\n        assert_eq!(s.pipe.server.data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.server.stream_data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.server.data_blocked_recv_count, 1);\n        assert_eq!(s.pipe.server.stream_data_blocked_recv_count, 0);\n\n        assert_eq!(s.pipe.client.data_blocked_sent_count, 1);\n        assert_eq!(s.pipe.client.stream_data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.client.data_blocked_recv_count, 0);\n        assert_eq!(s.pipe.client.stream_data_blocked_recv_count, 0);\n    }\n\n    #[test]\n    /// Ensure StreamBlocked when connection flow control prevents headers.\n    fn headers_blocked_on_conn() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(70);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n\n        s.handshake().unwrap();\n\n        // After the HTTP handshake, some bytes of connection flow control have\n        // been consumed. Fill the connection with more grease data on the control\n        // stream.\n        let d = [42; 28];\n        assert_eq!(s.pipe.client.stream_send(2, &d, false), Ok(23));\n\n        let req = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"https\"),\n            Header::new(b\":authority\", b\"quic.tech\"),\n            Header::new(b\":path\", b\"/test\"),\n        ];\n\n        // There is 0 connection-level flow control, so sending a request is\n        // blocked.\n        assert_eq!(\n            s.client.send_request(&mut s.pipe.client, &req, true),\n            Err(Error::StreamBlocked)\n        );\n        assert_eq!(s.pipe.client.stream_writable_next(), None);\n\n        // Emit the control stream data and drain it at the server via poll() to\n        // consumes it via poll() and gives back flow control.\n        s.advance().ok();\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        s.advance().ok();\n\n        // Now we can send the request.\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(2));\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(6));\n        assert_eq!(s.client.send_request(&mut s.pipe.client, &req, true), Ok(0));\n\n        assert_eq!(s.pipe.server.data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.server.stream_data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.server.data_blocked_recv_count, 1);\n        assert_eq!(s.pipe.server.stream_data_blocked_recv_count, 0);\n\n        assert_eq!(s.pipe.client.data_blocked_sent_count, 1);\n        assert_eq!(s.pipe.client.stream_data_blocked_sent_count, 0);\n        assert_eq!(s.pipe.client.data_blocked_recv_count, 0);\n        assert_eq!(s.pipe.client.stream_data_blocked_recv_count, 0);\n    }\n\n    #[test]\n    /// Ensure STREAM_DATA_BLOCKED is not emitted multiple times with the same\n    /// offset when trying to send large bodies.\n    fn send_body_truncation_stream_blocked() {\n        use crate::test_utils::decode_pkt;\n\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(10000); // large connection-level flow control\n        config.set_initial_max_stream_data_bidi_local(80);\n        config.set_initial_max_stream_data_bidi_remote(80);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let _ = s.send_response(stream, false).unwrap();\n\n        assert_eq!(s.pipe.server.streams.blocked().len(), 0);\n\n        // The body must be larger than the stream window would allow\n        let d = [42; 500];\n        let mut off = 0;\n\n        let sent = s\n            .server\n            .send_body(&mut s.pipe.server, stream, &d, true)\n            .unwrap();\n        assert_eq!(sent, 25);\n        off += sent;\n\n        // send_body wrote as much as it could (sent < size of buff).\n        assert_eq!(s.pipe.server.streams.blocked().len(), 1);\n        assert_eq!(\n            s.server\n                .send_body(&mut s.pipe.server, stream, &d[off..], true),\n            Err(Error::Done)\n        );\n        assert_eq!(s.pipe.server.streams.blocked().len(), 1);\n\n        // Now read raw frames to see what the QUIC layer did\n        let mut buf = [0; 65535];\n        let (len, _) = s.pipe.server.send(&mut buf).unwrap();\n\n        let frames = decode_pkt(&mut s.pipe.client, &mut buf[..len]).unwrap();\n\n        let mut iter = frames.iter();\n\n        assert_eq!(\n            iter.next(),\n            Some(&crate::frame::Frame::StreamDataBlocked {\n                stream_id: 0,\n                limit: 80,\n            })\n        );\n\n        // At the server, after sending the STREAM_DATA_BLOCKED frame, we clear\n        // the mark.\n        assert_eq!(s.pipe.server.streams.blocked().len(), 0);\n\n        // Don't read any data from the client, so stream flow control is never\n        // given back in the form of changing the stream's max offset.\n        // Subsequent body send operations will still fail but no more\n        // STREAM_DATA_BLOCKED frames should be submitted since the limit didn't\n        // change. No frames means no packet to send.\n        assert_eq!(\n            s.server\n                .send_body(&mut s.pipe.server, stream, &d[off..], true),\n            Err(Error::Done)\n        );\n        assert_eq!(s.pipe.server.streams.blocked().len(), 0);\n        assert_eq!(s.pipe.server.send(&mut buf), Err(crate::Error::Done));\n\n        // Now update the client's max offset manually.\n        let frames = [crate::frame::Frame::MaxStreamData {\n            stream_id: 0,\n            max: 100,\n        }];\n\n        let pkt_type = crate::packet::Type::Short;\n        assert_eq!(\n            s.pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n            Ok(39),\n        );\n\n        let sent = s\n            .server\n            .send_body(&mut s.pipe.server, stream, &d[off..], true)\n            .unwrap();\n        assert_eq!(sent, 18);\n\n        // Same thing here...\n        assert_eq!(s.pipe.server.streams.blocked().len(), 1);\n        assert_eq!(\n            s.server\n                .send_body(&mut s.pipe.server, stream, &d[off..], true),\n            Err(Error::Done)\n        );\n        assert_eq!(s.pipe.server.streams.blocked().len(), 1);\n\n        let (len, _) = s.pipe.server.send(&mut buf).unwrap();\n\n        let frames = decode_pkt(&mut s.pipe.client, &mut buf[..len]).unwrap();\n\n        let mut iter = frames.iter();\n\n        assert_eq!(\n            iter.next(),\n            Some(&crate::frame::Frame::StreamDataBlocked {\n                stream_id: 0,\n                limit: 100,\n            })\n        );\n    }\n\n    #[test]\n    /// Ensure stream doesn't hang due to small cwnd.\n    fn send_body_stream_blocked_by_small_cwnd() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(100000); // large connection-level flow control\n        config.set_initial_max_stream_data_bidi_local(100000);\n        config.set_initial_max_stream_data_bidi_remote(50000);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let _ = s.send_response(stream, false).unwrap();\n\n        // Clear the writable stream queue.\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(3));\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(7));\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(11));\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(stream));\n        assert_eq!(s.pipe.server.stream_writable_next(), None);\n\n        // The body must be larger than the cwnd would allow.\n        let send_buf = [42; 80000];\n\n        let sent = s\n            .server\n            .send_body(&mut s.pipe.server, stream, &send_buf, true)\n            .unwrap();\n\n        // send_body wrote as much as it could (sent < size of buff).\n        assert_eq!(sent, 11995);\n\n        s.advance().ok();\n\n        // Client reads received headers and body.\n        let mut recv_buf = [42; 80000];\n        assert!(s.poll_client().is_ok());\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(11995));\n\n        s.advance().ok();\n\n        // Server send cap is smaller than remaining body buffer.\n        assert!(s.pipe.server.tx_cap < send_buf.len() - sent);\n\n        // Once the server cwnd opens up, we can send more body.\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(0));\n    }\n\n    #[test]\n    /// Ensure stream doesn't hang due to small cwnd.\n    fn send_body_stream_blocked_zero_length() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(100000); // large connection-level flow control\n        config.set_initial_max_stream_data_bidi_local(100000);\n        config.set_initial_max_stream_data_bidi_remote(50000);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        let _ = s.send_response(stream, false).unwrap();\n\n        // Clear the writable stream queue.\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(3));\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(7));\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(11));\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(stream));\n        assert_eq!(s.pipe.server.stream_writable_next(), None);\n\n        // The body is large enough to fill the cwnd, except for enough bytes\n        // for another DATA frame header (but no payload).\n        let send_buf = [42; 11994];\n\n        let sent = s\n            .server\n            .send_body(&mut s.pipe.server, stream, &send_buf, false)\n            .unwrap();\n\n        assert_eq!(sent, 11994);\n\n        // There is only enough capacity left for the DATA frame header, but\n        // no payload.\n        assert_eq!(s.pipe.server.stream_capacity(stream).unwrap(), 3);\n        assert_eq!(\n            s.server\n                .send_body(&mut s.pipe.server, stream, &send_buf, false),\n            Err(Error::Done)\n        );\n\n        s.advance().ok();\n\n        // Client reads received headers and body.\n        let mut recv_buf = [42; 80000];\n        assert!(s.poll_client().is_ok());\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(11994));\n\n        s.advance().ok();\n\n        // Once the server cwnd opens up, we can send more body.\n        assert_eq!(s.pipe.server.stream_writable_next(), Some(0));\n    }\n\n    #[test]\n    /// Test handling of 0-length DATA writes with and without fin.\n    fn zero_length_data() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n\n        assert_eq!(\n            s.client.send_body(&mut s.pipe.client, 0, b\"\", false),\n            Err(Error::Done)\n        );\n        assert_eq!(s.client.send_body(&mut s.pipe.client, 0, b\"\", true), Ok(0));\n\n        s.advance().ok();\n\n        let mut recv_buf = vec![0; 100];\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Err(Error::Done));\n\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        let resp = s.send_response(stream, false).unwrap();\n\n        assert_eq!(\n            s.server.send_body(&mut s.pipe.server, 0, b\"\", false),\n            Err(Error::Done)\n        );\n        assert_eq!(s.server.send_body(&mut s.pipe.server, 0, b\"\", true), Ok(0));\n\n        s.advance().ok();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_client(stream, &mut recv_buf), Err(Error::Done));\n\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Tests that blocked 0-length DATA writes are reported correctly.\n    fn zero_length_data_blocked() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(69);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n\n        s.handshake().unwrap();\n\n        let req = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"https\"),\n            Header::new(b\":authority\", b\"quic.tech\"),\n            Header::new(b\":path\", b\"/test\"),\n        ];\n\n        assert_eq!(\n            s.client.send_request(&mut s.pipe.client, &req, false),\n            Ok(0)\n        );\n\n        assert_eq!(\n            s.client.send_body(&mut s.pipe.client, 0, b\"\", true),\n            Err(Error::Done)\n        );\n\n        // Clear the writable stream queue.\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(2));\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(6));\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(10));\n        assert_eq!(s.pipe.client.stream_writable_next(), None);\n\n        s.advance().ok();\n\n        // Once the server gives flow control credits back, we can send the body.\n        assert_eq!(s.pipe.client.stream_writable_next(), Some(0));\n        assert_eq!(s.client.send_body(&mut s.pipe.client, 0, b\"\", true), Ok(0));\n    }\n\n    #[test]\n    /// Tests that receiving an empty SETTINGS frame is handled and reported.\n    fn empty_settings() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(1500);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(5);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n        config.set_ack_delay_exponent(8);\n        config.grease(false);\n\n        let h3_config = Config::new().unwrap();\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n\n        s.handshake().unwrap();\n\n        assert!(s.client.peer_settings_raw().is_some());\n        assert!(s.server.peer_settings_raw().is_some());\n    }\n\n    #[test]\n    /// Tests that receiving a H3_DATAGRAM setting is ok.\n    fn dgram_setting() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(70);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.enable_dgram(true, 1000, 1000);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n        assert_eq!(s.pipe.handshake(), Ok(()));\n\n        s.client.send_settings(&mut s.pipe.client).unwrap();\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        // Before processing SETTINGS (via poll), HTTP/3 DATAGRAMS are not\n        // enabled.\n        assert!(!s.server.dgram_enabled_by_peer(&s.pipe.server));\n\n        // When everything is ok, poll returns Done and DATAGRAM is enabled.\n        assert_eq!(s.server.poll(&mut s.pipe.server), Err(Error::Done));\n        assert!(s.server.dgram_enabled_by_peer(&s.pipe.server));\n\n        // Now detect things on the client\n        s.server.send_settings(&mut s.pipe.server).unwrap();\n        assert_eq!(s.pipe.advance(), Ok(()));\n        assert!(!s.client.dgram_enabled_by_peer(&s.pipe.client));\n        assert_eq!(s.client.poll(&mut s.pipe.client), Err(Error::Done));\n        assert!(s.client.dgram_enabled_by_peer(&s.pipe.client));\n    }\n\n    #[test]\n    /// Tests that receiving a H3_DATAGRAM setting when no TP is set generates\n    /// an error.\n    fn dgram_setting_no_tp() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(70);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n        assert_eq!(s.pipe.handshake(), Ok(()));\n\n        s.client.control_stream_id = Some(\n            s.client\n                .open_uni_stream(\n                    &mut s.pipe.client,\n                    stream::HTTP3_CONTROL_STREAM_TYPE_ID,\n                )\n                .unwrap(),\n        );\n\n        let settings = frame::Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: Some(1),\n            grease: None,\n            additional_settings: Default::default(),\n            raw: Default::default(),\n        };\n\n        s.send_frame_client(settings, s.client.control_stream_id.unwrap(), false)\n            .unwrap();\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        assert_eq!(s.server.poll(&mut s.pipe.server), Err(Error::SettingsError));\n    }\n\n    #[test]\n    /// Tests that receiving SETTINGS with prohibited values generates an error.\n    fn settings_h2_prohibited() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(70);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n\n        let h3_config = Config::new().unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n        assert_eq!(s.pipe.handshake(), Ok(()));\n\n        s.client.control_stream_id = Some(\n            s.client\n                .open_uni_stream(\n                    &mut s.pipe.client,\n                    stream::HTTP3_CONTROL_STREAM_TYPE_ID,\n                )\n                .unwrap(),\n        );\n\n        s.server.control_stream_id = Some(\n            s.server\n                .open_uni_stream(\n                    &mut s.pipe.server,\n                    stream::HTTP3_CONTROL_STREAM_TYPE_ID,\n                )\n                .unwrap(),\n        );\n\n        let frame_payload_len = 2u64;\n        let settings = [\n            frame::SETTINGS_FRAME_TYPE_ID as u8,\n            frame_payload_len as u8,\n            0x2, // 0x2 is a reserved setting type\n            1,\n        ];\n\n        s.send_arbitrary_stream_data_client(\n            &settings,\n            s.client.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        s.send_arbitrary_stream_data_server(\n            &settings,\n            s.server.control_stream_id.unwrap(),\n            false,\n        )\n        .unwrap();\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        assert_eq!(s.server.poll(&mut s.pipe.server), Err(Error::SettingsError));\n\n        assert_eq!(s.client.poll(&mut s.pipe.client), Err(Error::SettingsError));\n    }\n\n    #[test]\n    /// Tests that setting SETTINGS with prohibited values generates an error.\n    fn set_prohibited_additional_settings() {\n        let mut h3_config = Config::new().unwrap();\n        assert_eq!(\n            h3_config.set_additional_settings(vec![(\n                frame::SETTINGS_QPACK_MAX_TABLE_CAPACITY,\n                43\n            )]),\n            Err(Error::SettingsError)\n        );\n        assert_eq!(\n            h3_config.set_additional_settings(vec![(\n                frame::SETTINGS_MAX_FIELD_SECTION_SIZE,\n                43\n            )]),\n            Err(Error::SettingsError)\n        );\n        assert_eq!(\n            h3_config.set_additional_settings(vec![(\n                frame::SETTINGS_QPACK_BLOCKED_STREAMS,\n                43\n            )]),\n            Err(Error::SettingsError)\n        );\n        assert_eq!(\n            h3_config.set_additional_settings(vec![(\n                frame::SETTINGS_ENABLE_CONNECT_PROTOCOL,\n                43\n            )]),\n            Err(Error::SettingsError)\n        );\n        assert_eq!(\n            h3_config\n                .set_additional_settings(vec![(frame::SETTINGS_H3_DATAGRAM, 43)]),\n            Err(Error::SettingsError)\n        );\n    }\n\n    #[test]\n    /// Tests additional settings are actually exchanged by the peers.\n    fn set_additional_settings() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(70);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n        config.grease(false);\n\n        let mut h3_config = Config::new().unwrap();\n        h3_config\n            .set_additional_settings(vec![(42, 43), (44, 45)])\n            .unwrap();\n\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n        assert_eq!(s.pipe.handshake(), Ok(()));\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        s.client.send_settings(&mut s.pipe.client).unwrap();\n        assert_eq!(s.pipe.advance(), Ok(()));\n        assert_eq!(s.server.poll(&mut s.pipe.server), Err(Error::Done));\n\n        s.server.send_settings(&mut s.pipe.server).unwrap();\n        assert_eq!(s.pipe.advance(), Ok(()));\n        assert_eq!(s.client.poll(&mut s.pipe.client), Err(Error::Done));\n\n        assert_eq!(\n            s.server.peer_settings_raw(),\n            Some(&[(42, 43), (44, 45)][..])\n        );\n        assert_eq!(\n            s.client.peer_settings_raw(),\n            Some(&[(42, 43), (44, 45)][..])\n        );\n    }\n\n    #[test]\n    /// Send a single DATAGRAM.\n    fn single_dgram() {\n        let mut buf = [0; 65535];\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // We'll send default data of 10 bytes on flow ID 0.\n        let result = (11, 0, 1);\n\n        s.send_dgram_client(0).unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(result));\n\n        s.send_dgram_server(0).unwrap();\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(result));\n    }\n\n    #[test]\n    /// Send multiple DATAGRAMs.\n    fn multiple_dgram() {\n        let mut buf = [0; 65535];\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // We'll send default data of 10 bytes on flow ID 0.\n        let result = (11, 0, 1);\n\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_server(&mut buf), Err(Error::Done));\n\n        s.send_dgram_server(0).unwrap();\n        s.send_dgram_server(0).unwrap();\n        s.send_dgram_server(0).unwrap();\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_client(&mut buf), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send more DATAGRAMs than the send queue allows.\n    fn multiple_dgram_overflow() {\n        let mut buf = [0; 65535];\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // We'll send default data of 10 bytes on flow ID 0.\n        let result = (11, 0, 1);\n\n        // Five DATAGRAMs\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n\n        // Only 3 independent DATAGRAMs to read events will fire.\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(result));\n        assert_eq!(s.recv_dgram_server(&mut buf), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a single DATAGRAM and request.\n    fn poll_datagram_cycling_no_read() {\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(1500);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n        config.enable_dgram(true, 100, 100);\n\n        let h3_config = Config::new().unwrap();\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n        s.handshake().unwrap();\n\n        // Send request followed by DATAGRAM on client side.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        s.send_body_client(stream, true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        s.send_dgram_client(0).unwrap();\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send a single DATAGRAM and request.\n    fn poll_datagram_single_read() {\n        let mut buf = [0; 65535];\n\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(1500);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n        config.enable_dgram(true, 100, 100);\n\n        let h3_config = Config::new().unwrap();\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n        s.handshake().unwrap();\n\n        // We'll send default data of 10 bytes on flow ID 0.\n        let result = (11, 0, 1);\n\n        // Send request followed by DATAGRAM on client side.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let body = s.send_body_client(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        s.send_dgram_client(0).unwrap();\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(result));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Send response followed by DATAGRAM on server side\n        let resp = s.send_response(stream, false).unwrap();\n\n        let body = s.send_body_server(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        s.send_dgram_server(0).unwrap();\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(result));\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(body.len()));\n\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Send multiple DATAGRAMs and requests.\n    fn poll_datagram_multi_read() {\n        let mut buf = [0; 65535];\n\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(1500);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n        config.enable_dgram(true, 100, 100);\n\n        let h3_config = Config::new().unwrap();\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n        s.handshake().unwrap();\n\n        // 10 bytes on flow ID 0 and 2.\n        let flow_0_result = (11, 0, 1);\n        let flow_2_result = (11, 2, 1);\n\n        // Send requests followed by DATAGRAMs on client side.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let body = s.send_body_client(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(2).unwrap();\n        s.send_dgram_client(2).unwrap();\n        s.send_dgram_client(2).unwrap();\n        s.send_dgram_client(2).unwrap();\n        s.send_dgram_client(2).unwrap();\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Second cycle, start to read\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Third cycle.\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Send response followed by DATAGRAM on server side\n        let resp = s.send_response(stream, false).unwrap();\n\n        let body = s.send_body_server(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        s.send_dgram_server(0).unwrap();\n        s.send_dgram_server(0).unwrap();\n        s.send_dgram_server(0).unwrap();\n        s.send_dgram_server(0).unwrap();\n        s.send_dgram_server(0).unwrap();\n        s.send_dgram_server(2).unwrap();\n        s.send_dgram_server(2).unwrap();\n        s.send_dgram_server(2).unwrap();\n        s.send_dgram_server(2).unwrap();\n        s.send_dgram_server(2).unwrap();\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        // Second cycle, start to read\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        // Third cycle.\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_client(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Tests that the Finished event is not issued for streams of unknown type\n    /// (e.g. GREASE).\n    fn finished_is_for_requests() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.client.open_grease_stream(&mut s.pipe.client), Ok(()));\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Tests that streams are marked as finished only once.\n    fn finished_once() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (stream, req) = s.send_request(false).unwrap();\n        let body = s.send_body_client(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Err(Error::Done));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    /// Tests that the Data event is properly re-armed.\n    fn data_event_rearm() {\n        let bytes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        let (r1_id, r1_hdrs) = s.send_request(false).unwrap();\n\n        let mut recv_buf = vec![0; bytes.len()];\n\n        let r1_ev_headers = Event::Headers {\n            list: r1_hdrs,\n            more_frames: true,\n        };\n\n        // Manually send an incomplete DATA frame (i.e. the frame size is longer\n        // than the actual data sent).\n        {\n            let mut d = [42; 10];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            b.put_varint(frame::DATA_FRAME_TYPE_ID).unwrap();\n            b.put_varint(bytes.len() as u64).unwrap();\n            let off = b.off();\n            s.pipe.client.stream_send(r1_id, &d[..off], false).unwrap();\n\n            assert_eq!(\n                s.pipe.client.stream_send(r1_id, &bytes[..5], false),\n                Ok(5)\n            );\n\n            s.advance().ok();\n        }\n\n        assert_eq!(s.poll_server(), Ok((r1_id, r1_ev_headers)));\n        assert_eq!(s.poll_server(), Ok((r1_id, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Read the available body data.\n        assert_eq!(s.recv_body_server(r1_id, &mut recv_buf), Ok(5));\n\n        // Send the remaining DATA payload.\n        assert_eq!(s.pipe.client.stream_send(r1_id, &bytes[5..], false), Ok(5));\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((r1_id, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Read the rest of the body data.\n        assert_eq!(s.recv_body_server(r1_id, &mut recv_buf), Ok(5));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Send more data.\n        let r1_body = s.send_body_client(r1_id, false).unwrap();\n\n        assert_eq!(s.poll_server(), Ok((r1_id, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_server(r1_id, &mut recv_buf), Ok(r1_body.len()));\n\n        // Send a new request to ensure cross-stream events don't break rearming.\n        let (r2_id, r2_hdrs) = s.send_request(false).unwrap();\n        let r2_ev_headers = Event::Headers {\n            list: r2_hdrs,\n            more_frames: true,\n        };\n        let r2_body = s.send_body_client(r2_id, false).unwrap();\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((r2_id, r2_ev_headers)));\n        assert_eq!(s.poll_server(), Ok((r2_id, Event::Data)));\n        assert_eq!(s.recv_body_server(r2_id, &mut recv_buf), Ok(r2_body.len()));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Send more data on request 1, then trailing HEADERS.\n        let r1_body = s.send_body_client(r1_id, false).unwrap();\n\n        let trailers = vec![Header::new(b\"hello\", b\"world\")];\n\n        s.client\n            .send_headers(&mut s.pipe.client, r1_id, &trailers, true)\n            .unwrap();\n\n        let r1_ev_trailers = Event::Headers {\n            list: trailers.clone(),\n            more_frames: false,\n        };\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((r1_id, Event::Data)));\n        assert_eq!(s.recv_body_server(r1_id, &mut recv_buf), Ok(r1_body.len()));\n\n        assert_eq!(s.poll_server(), Ok((r1_id, r1_ev_trailers)));\n        assert_eq!(s.poll_server(), Ok((r1_id, Event::Finished)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Send more data on request 2, then trailing HEADERS.\n        let r2_body = s.send_body_client(r2_id, false).unwrap();\n\n        s.client\n            .send_headers(&mut s.pipe.client, r2_id, &trailers, false)\n            .unwrap();\n\n        let r2_ev_trailers = Event::Headers {\n            list: trailers,\n            more_frames: true,\n        };\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((r2_id, Event::Data)));\n        assert_eq!(s.recv_body_server(r2_id, &mut recv_buf), Ok(r2_body.len()));\n        assert_eq!(s.poll_server(), Ok((r2_id, r2_ev_trailers)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        let (r3_id, r3_hdrs) = s.send_request(false).unwrap();\n\n        let r3_ev_headers = Event::Headers {\n            list: r3_hdrs,\n            more_frames: true,\n        };\n\n        // Manually send an incomplete DATA frame (i.e. only the header is sent).\n        {\n            let mut d = [42; 10];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            b.put_varint(frame::DATA_FRAME_TYPE_ID).unwrap();\n            b.put_varint(bytes.len() as u64).unwrap();\n            let off = b.off();\n            s.pipe.client.stream_send(r3_id, &d[..off], false).unwrap();\n\n            s.advance().ok();\n        }\n\n        assert_eq!(s.poll_server(), Ok((r3_id, r3_ev_headers)));\n        assert_eq!(s.poll_server(), Ok((r3_id, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_server(r3_id, &mut recv_buf), Err(Error::Done));\n\n        assert_eq!(s.pipe.client.stream_send(r3_id, &bytes[..5], false), Ok(5));\n\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((r3_id, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_server(r3_id, &mut recv_buf), Ok(5));\n\n        assert_eq!(s.pipe.client.stream_send(r3_id, &bytes[5..], false), Ok(5));\n        s.advance().ok();\n\n        assert_eq!(s.poll_server(), Ok((r3_id, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_server(r3_id, &mut recv_buf), Ok(5));\n\n        // Buffer multiple data frames.\n        let body = s.send_body_client(r3_id, false).unwrap();\n        s.send_body_client(r3_id, false).unwrap();\n        s.send_body_client(r3_id, false).unwrap();\n\n        assert_eq!(s.poll_server(), Ok((r3_id, Event::Data)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        {\n            let mut d = [42; 10];\n            let mut b = octets::OctetsMut::with_slice(&mut d);\n\n            b.put_varint(frame::DATA_FRAME_TYPE_ID).unwrap();\n            b.put_varint(0).unwrap();\n            let off = b.off();\n            s.pipe.client.stream_send(r3_id, &d[..off], true).unwrap();\n\n            s.advance().ok();\n        }\n\n        let mut recv_buf = vec![0; bytes.len() * 3];\n\n        assert_eq!(s.recv_body_server(r3_id, &mut recv_buf), Ok(body.len() * 3));\n    }\n\n    #[test]\n    /// Tests that the Datagram event is properly re-armed.\n    fn dgram_event_rearm() {\n        let mut buf = [0; 65535];\n\n        let mut config = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config\n            .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n            .unwrap();\n        config\n            .load_priv_key_from_pem_file(\"examples/cert.key\")\n            .unwrap();\n        config.set_application_protos(&[b\"h3\"]).unwrap();\n        config.set_initial_max_data(1500);\n        config.set_initial_max_stream_data_bidi_local(150);\n        config.set_initial_max_stream_data_bidi_remote(150);\n        config.set_initial_max_stream_data_uni(150);\n        config.set_initial_max_streams_bidi(100);\n        config.set_initial_max_streams_uni(5);\n        config.verify_peer(false);\n        config.enable_dgram(true, 100, 100);\n\n        let h3_config = Config::new().unwrap();\n        let mut s = Session::with_configs(&mut config, &h3_config).unwrap();\n        s.handshake().unwrap();\n\n        // 10 bytes on flow ID 0 and 2.\n        let flow_0_result = (11, 0, 1);\n        let flow_2_result = (11, 2, 1);\n\n        // Send requests followed by DATAGRAMs on client side.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let body = s.send_body_client(stream, true).unwrap();\n\n        let mut recv_buf = vec![0; body.len()];\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(2).unwrap();\n        s.send_dgram_client(2).unwrap();\n\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_0_result));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_0_result));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_2_result));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_2_result));\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        s.send_dgram_client(0).unwrap();\n        s.send_dgram_client(2).unwrap();\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_0_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_dgram_server(&mut buf), Ok(flow_2_result));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n\n        // Verify that dgram counts are incremented.\n        assert_eq!(s.pipe.client.dgram_sent_count, 6);\n        assert_eq!(s.pipe.client.dgram_recv_count, 0);\n        assert_eq!(s.pipe.server.dgram_sent_count, 0);\n        assert_eq!(s.pipe.server.dgram_recv_count, 6);\n\n        let server_path = s.pipe.server.paths.get_active().expect(\"no active\");\n        let client_path = s.pipe.client.paths.get_active().expect(\"no active\");\n        assert_eq!(client_path.dgram_sent_count, 6);\n        assert_eq!(client_path.dgram_recv_count, 0);\n        assert_eq!(server_path.dgram_sent_count, 0);\n        assert_eq!(server_path.dgram_recv_count, 6);\n    }\n\n    #[test]\n    fn reset_stream() {\n        let mut buf = [0; 65535];\n\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // Client sends request.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        // Server sends response and closes stream.\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        let resp = s.send_response(stream, true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        // Client sends RESET_STREAM, closing stream.\n        let frames = [crate::frame::Frame::ResetStream {\n            stream_id: stream,\n            error_code: 42,\n            final_size: 68,\n        }];\n\n        let pkt_type = crate::packet::Type::Short;\n        assert_eq!(\n            s.pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n            Ok(39)\n        );\n\n        // Server issues Reset event for the stream.\n        assert_eq!(s.poll_server(), Ok((stream, Event::Reset(42))));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Sending RESET_STREAM again shouldn't trigger another Reset event.\n        assert_eq!(\n            s.pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n            Ok(39)\n        );\n\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    /// The client shuts down the stream's write direction, the server\n    /// shuts down its side with fin\n    #[test]\n    fn client_shutdown_write_server_fin() {\n        let mut buf = [0; 65535];\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // Client sends request.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        // Server sends response and closes stream.\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        let resp = s.send_response(stream, true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: false,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        // Client shuts down stream ==> sends RESET_STREAM\n        assert_eq!(\n            s.pipe\n                .client\n                .stream_shutdown(stream, crate::Shutdown::Write, 42),\n            Ok(())\n        );\n        assert_eq!(s.advance(), Ok(()));\n\n        // Server sees the Reset event for the stream.\n        assert_eq!(s.poll_server(), Ok((stream, Event::Reset(42))));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Streams have been collected by quiche\n        assert!(s.pipe.server.streams.is_collected(stream));\n        assert!(s.pipe.client.streams.is_collected(stream));\n\n        // Client sends another request, server sends response without fin\n        //\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        // Check that server has received the request.\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Server sends reponse without closing the stream.\n        let resp = s.send_response(stream, false).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n\n        // Client shuts down stream ==> sends RESET_STREAM\n        assert_eq!(\n            s.pipe\n                .client\n                .stream_shutdown(stream, crate::Shutdown::Write, 42),\n            Ok(())\n        );\n        assert_eq!(s.advance(), Ok(()));\n\n        // Server sees the Reset event for the stream.\n        assert_eq!(s.poll_server(), Ok((stream, Event::Reset(42))));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Server sends body and closes the stream.\n        s.send_body_server(stream, true).unwrap();\n\n        // Stream has been collected on server by quiche\n        assert!(s.pipe.server.streams.is_collected(stream));\n        // Client stream has not been collected, the client needs to\n        // read the fin from the stream first.\n        assert!(!s.pipe.client.streams.is_collected(stream));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Data)));\n        s.recv_body_client(stream, &mut buf).unwrap();\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        assert!(s.pipe.client.streams.is_collected(stream));\n    }\n\n    #[test]\n    fn client_shutdown_read() {\n        let mut buf = [0; 65535];\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // Client sends request and leaves stream open.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        // Server sends response and leaves stream open.\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        let resp = s.send_response(stream, false).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: true,\n        };\n\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n        // Client shuts down read\n        assert_eq!(\n            s.pipe\n                .client\n                .stream_shutdown(stream, crate::Shutdown::Read, 42),\n            Ok(())\n        );\n        assert_eq!(s.advance(), Ok(()));\n\n        // Stream is writable on server side, but returns StreamStopped\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        let writables: Vec<u64> = s.pipe.server.writable().collect();\n        assert!(writables.contains(&stream));\n        assert_eq!(\n            s.send_body_server(stream, false),\n            Err(Error::TransportError(crate::Error::StreamStopped(42)))\n        );\n\n        // Client needs to finish its side by sending a fin\n        assert_eq!(\n            s.client.send_body(&mut s.pipe.client, stream, &[], true),\n            Ok(0)\n        );\n        assert_eq!(s.advance(), Ok(()));\n        // Note, we get an Event::Data for an empty buffer today. But it\n        // would also be fine to not get it.\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n        assert_eq!(s.recv_body_server(stream, &mut buf), Err(Error::Done));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Since the client has already send a fin, the stream is collected\n        // on both client and server\n        assert!(s.pipe.client.streams.is_collected(stream));\n        assert!(s.pipe.server.streams.is_collected(stream));\n    }\n\n    #[test]\n    fn reset_finished_at_server() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // Client sends HEADERS and doesn't fin\n        let (stream, _req) = s.send_request(false).unwrap();\n\n        // ..then Client sends RESET_STREAM\n        assert_eq!(\n            s.pipe.client.stream_shutdown(0, crate::Shutdown::Write, 0),\n            Ok(())\n        );\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        // Server receives just a reset\n        assert_eq!(s.poll_server(), Ok((stream, Event::Reset(0))));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Client sends HEADERS and fin\n        let (stream, req) = s.send_request(true).unwrap();\n\n        // ..then Client sends RESET_STREAM\n        assert_eq!(\n            s.pipe.client.stream_shutdown(4, crate::Shutdown::Write, 0),\n            Ok(())\n        );\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        // Server receives headers and fin.\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n    }\n\n    #[test]\n    fn reset_finished_at_server_with_data_pending() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // Client sends HEADERS and doesn't fin.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        assert!(s.send_body_client(stream, false).is_ok());\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        // Server receives headers and data...\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n\n        // ..then Client sends RESET_STREAM.\n        assert_eq!(\n            s.pipe\n                .client\n                .stream_shutdown(stream, crate::Shutdown::Write, 0),\n            Ok(())\n        );\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        // The server does *not* attempt to read from the stream,\n        // but polls and receives the reset and there are no more\n        // readable streams.\n        assert_eq!(s.poll_server(), Ok((stream, Event::Reset(0))));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.pipe.server.readable().len(), 0);\n    }\n\n    #[test]\n    fn reset_finished_at_server_with_data_pending_2() {\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // Client sends HEADERS and doesn't fin.\n        let (stream, req) = s.send_request(false).unwrap();\n\n        assert!(s.send_body_client(stream, false).is_ok());\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        // Server receives headers and data...\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Data)));\n\n        // ..then Client sends RESET_STREAM.\n        assert_eq!(\n            s.pipe\n                .client\n                .stream_shutdown(stream, crate::Shutdown::Write, 0),\n            Ok(())\n        );\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        // Server reads from the stream and receives the reset while\n        // attempting to read.\n        assert_eq!(\n            s.recv_body_server(stream, &mut [0; 100]),\n            Err(Error::TransportError(crate::Error::StreamReset(0)))\n        );\n\n        // No more events and there are no more readable streams.\n        assert_eq!(s.poll_server(), Err(Error::Done));\n        assert_eq!(s.pipe.server.readable().len(), 0);\n    }\n\n    #[test]\n    fn reset_finished_at_client() {\n        let mut buf = [0; 65535];\n        let mut s = Session::new().unwrap();\n        s.handshake().unwrap();\n\n        // Client sends HEADERS and doesn't fin\n        let (stream, req) = s.send_request(false).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: true,\n        };\n\n        // Server receives headers.\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Server sends response and doesn't fin\n        s.send_response(stream, false).unwrap();\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        // .. then Server sends RESET_STREAM\n        assert_eq!(\n            s.pipe\n                .server\n                .stream_shutdown(stream, crate::Shutdown::Write, 0),\n            Ok(())\n        );\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        // Client receives Reset only\n        assert_eq!(s.poll_client(), Ok((stream, Event::Reset(0))));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Client sends headers and fin.\n        let (stream, req) = s.send_request(true).unwrap();\n\n        let ev_headers = Event::Headers {\n            list: req,\n            more_frames: false,\n        };\n\n        // Server receives headers and fin.\n        assert_eq!(s.poll_server(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_server(), Err(Error::Done));\n\n        // Server sends response and fin\n        let resp = s.send_response(stream, true).unwrap();\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        // ..then Server sends RESET_STREAM\n        let frames = [crate::frame::Frame::ResetStream {\n            stream_id: stream,\n            error_code: 42,\n            final_size: 68,\n        }];\n\n        let pkt_type = crate::packet::Type::Short;\n        assert_eq!(\n            s.pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n            Ok(39)\n        );\n\n        assert_eq!(s.pipe.advance(), Ok(()));\n\n        let ev_headers = Event::Headers {\n            list: resp,\n            more_frames: false,\n        };\n\n        // Client receives headers and fin.\n        assert_eq!(s.poll_client(), Ok((stream, ev_headers)));\n        assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));\n        assert_eq!(s.poll_client(), Err(Error::Done));\n    }\n}\n\n#[cfg(feature = \"ffi\")]\nmod ffi;\n#[cfg(feature = \"internal\")]\n#[doc(hidden)]\npub mod frame;\n#[cfg(not(feature = \"internal\"))]\nmod frame;\n#[doc(hidden)]\npub mod qpack;\nmod stream;\n"
  },
  {
    "path": "quiche/src/h3/qpack/decoder.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::Error;\nuse super::Result;\n\nuse crate::h3::Header;\n\nuse super::INDEXED;\nuse super::INDEXED_WITH_POST_BASE;\nuse super::LITERAL;\nuse super::LITERAL_WITH_NAME_REF;\n\n#[derive(Clone, Copy, Debug, PartialEq)]\nenum Representation {\n    Indexed,\n    IndexedWithPostBase,\n    Literal,\n    LiteralWithNameRef,\n    LiteralWithPostBase,\n}\n\nimpl Representation {\n    pub fn from_byte(b: u8) -> Representation {\n        if b & INDEXED == INDEXED {\n            return Representation::Indexed;\n        }\n\n        if b & LITERAL_WITH_NAME_REF == LITERAL_WITH_NAME_REF {\n            return Representation::LiteralWithNameRef;\n        }\n\n        if b & LITERAL == LITERAL {\n            return Representation::Literal;\n        }\n\n        if b & INDEXED_WITH_POST_BASE == INDEXED_WITH_POST_BASE {\n            return Representation::IndexedWithPostBase;\n        }\n\n        Representation::LiteralWithPostBase\n    }\n}\n\n/// A QPACK decoder.\n#[derive(Default)]\npub struct Decoder {}\n\nimpl Decoder {\n    /// Creates a new QPACK decoder.\n    pub fn new() -> Decoder {\n        Decoder::default()\n    }\n\n    /// Processes control instructions from the encoder.\n    pub fn control(&mut self, _buf: &mut [u8]) -> Result<()> {\n        // TODO: process control instructions\n        Ok(())\n    }\n\n    /// Decodes a QPACK header block into a list of headers.\n    pub fn decode(&mut self, buf: &[u8], max_size: u64) -> Result<Vec<Header>> {\n        let mut b = octets::Octets::with_slice(buf);\n\n        let mut out = Vec::new();\n\n        let mut left = max_size;\n\n        let req_insert_count = decode_int(&mut b, 8)?;\n        let base = decode_int(&mut b, 7)?;\n\n        trace!(\"Header count={req_insert_count} base={base}\");\n\n        while b.cap() > 0 {\n            let first = b.peek_u8()?;\n\n            match Representation::from_byte(first) {\n                Representation::Indexed => {\n                    const STATIC: u8 = 0x40;\n\n                    let s = first & STATIC == STATIC;\n                    let index = decode_int(&mut b, 6)?;\n\n                    trace!(\"Indexed index={index} static={s}\");\n\n                    if !s {\n                        // TODO: implement dynamic table\n                        return Err(Error::InvalidHeaderValue);\n                    }\n\n                    let (name, value) = lookup_static(index)?;\n\n                    left = left\n                        .checked_sub((name.len() + value.len()) as u64)\n                        .ok_or(Error::HeaderListTooLarge)?;\n\n                    let hdr = Header::new(name, value);\n                    out.push(hdr);\n                },\n\n                Representation::IndexedWithPostBase => {\n                    let index = decode_int(&mut b, 4)?;\n\n                    trace!(\"Indexed With Post Base index={index}\");\n\n                    // TODO: implement dynamic table\n                    return Err(Error::InvalidHeaderValue);\n                },\n\n                Representation::Literal => {\n                    let name_huff = b.as_ref()[0] & 0x08 == 0x08;\n                    let name_len = decode_int(&mut b, 3)? as usize;\n\n                    let mut name = b.get_bytes(name_len)?;\n\n                    let name = if name_huff {\n                        name.get_huffman_decoded()?\n                    } else {\n                        name.to_vec()\n                    };\n\n                    let name = name.to_vec();\n                    let value = decode_str(&mut b)?;\n\n                    trace!(\n                        \"Literal Without Name Reference name={name:?} value={value:?}\",\n                    );\n\n                    left = left\n                        .checked_sub((name.len() + value.len()) as u64)\n                        .ok_or(Error::HeaderListTooLarge)?;\n\n                    // Instead of calling Header::new(), create Header directly\n                    // from `name` and `value`, which are already String.\n                    let hdr = Header(name, value);\n                    out.push(hdr);\n                },\n\n                Representation::LiteralWithNameRef => {\n                    const STATIC: u8 = 0x10;\n\n                    let s = first & STATIC == STATIC;\n                    let name_idx = decode_int(&mut b, 4)?;\n                    let value = decode_str(&mut b)?;\n\n                    trace!(\n                        \"Literal name_idx={name_idx} static={s} value={value:?}\"\n                    );\n\n                    if !s {\n                        // TODO: implement dynamic table\n                        return Err(Error::InvalidHeaderValue);\n                    }\n\n                    let (name, _) = lookup_static(name_idx)?;\n\n                    left = left\n                        .checked_sub((name.len() + value.len()) as u64)\n                        .ok_or(Error::HeaderListTooLarge)?;\n\n                    // Instead of calling Header::new(), create Header directly\n                    // from `value`, which is already String, but clone `name`\n                    // as it is just a reference.\n                    let hdr = Header(name.to_vec(), value);\n                    out.push(hdr);\n                },\n\n                Representation::LiteralWithPostBase => {\n                    trace!(\"Literal With Post Base\");\n\n                    // TODO: implement dynamic table\n                    return Err(Error::InvalidHeaderValue);\n                },\n            }\n        }\n\n        Ok(out)\n    }\n}\n\nfn lookup_static(idx: u64) -> Result<(&'static [u8], &'static [u8])> {\n    if idx >= super::static_table::STATIC_DECODE_TABLE.len() as u64 {\n        return Err(Error::InvalidStaticTableIndex);\n    }\n\n    Ok(super::static_table::STATIC_DECODE_TABLE[idx as usize])\n}\n\nfn decode_int(b: &mut octets::Octets, prefix: usize) -> Result<u64> {\n    let mask = 2u64.pow(prefix as u32) - 1;\n\n    let mut val = u64::from(b.get_u8()?);\n    val &= mask;\n\n    if val < mask {\n        return Ok(val);\n    }\n\n    let mut shift = 0;\n\n    while b.cap() > 0 {\n        let byte = b.get_u8()?;\n\n        let inc = u64::from(byte & 0x7f)\n            .checked_shl(shift)\n            .ok_or(Error::BufferTooShort)?;\n\n        val = val.checked_add(inc).ok_or(Error::BufferTooShort)?;\n\n        shift += 7;\n\n        if byte & 0x80 == 0 {\n            return Ok(val);\n        }\n    }\n\n    Err(Error::BufferTooShort)\n}\n\nfn decode_str(b: &mut octets::Octets) -> Result<Vec<u8>> {\n    let first = b.peek_u8()?;\n\n    let huff = first & 0x80 == 0x80;\n\n    let len = decode_int(b, 7)? as usize;\n\n    let mut val = b.get_bytes(len)?;\n\n    let val = if huff {\n        val.get_huffman_decoded()?\n    } else {\n        val.to_vec()\n    };\n\n    Ok(val)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn decode_int1() {\n        let encoded = [0b01010, 0x02];\n        let mut b = octets::Octets::with_slice(&encoded);\n\n        assert_eq!(decode_int(&mut b, 5), Ok(10));\n    }\n\n    #[test]\n    fn decode_int2() {\n        let encoded = [0b11111, 0b10011010, 0b00001010];\n        let mut b = octets::Octets::with_slice(&encoded);\n\n        assert_eq!(decode_int(&mut b, 5), Ok(1337));\n    }\n\n    #[test]\n    fn decode_int3() {\n        let encoded = [0b101010];\n        let mut b = octets::Octets::with_slice(&encoded);\n\n        assert_eq!(decode_int(&mut b, 8), Ok(42));\n    }\n}\n"
  },
  {
    "path": "quiche/src/h3/qpack/encoder.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::Result;\n\nuse crate::h3::NameValue;\n\nuse super::INDEXED;\nuse super::LITERAL;\nuse super::LITERAL_WITH_NAME_REF;\n\n/// A QPACK encoder.\n#[derive(Default)]\npub struct Encoder {}\n\nimpl Encoder {\n    /// Creates a new QPACK encoder.\n    pub fn new() -> Encoder {\n        Encoder::default()\n    }\n\n    /// Encodes a list of headers into a QPACK header block.\n    pub fn encode<T: NameValue>(\n        &mut self, headers: &[T], out: &mut [u8],\n    ) -> Result<usize> {\n        let mut b = octets::OctetsMut::with_slice(out);\n\n        // Required Insert Count.\n        encode_int(0, 0, 8, &mut b)?;\n\n        // Base.\n        encode_int(0, 0, 7, &mut b)?;\n\n        for h in headers {\n            match lookup_static(h) {\n                Some((idx, true)) => {\n                    const STATIC: u8 = 0x40;\n\n                    // Encode as statically indexed.\n                    encode_int(idx, INDEXED | STATIC, 6, &mut b)?;\n                },\n\n                Some((idx, false)) => {\n                    const STATIC: u8 = 0x10;\n\n                    // Encode value as literal with static name reference.\n                    encode_int(idx, LITERAL_WITH_NAME_REF | STATIC, 4, &mut b)?;\n                    encode_str::<false>(h.value(), 0, 7, &mut b)?;\n                },\n\n                None => {\n                    // Encode as fully literal.\n\n                    encode_str::<true>(h.name(), LITERAL, 3, &mut b)?;\n                    encode_str::<false>(h.value(), 0, 7, &mut b)?;\n                },\n            };\n        }\n\n        Ok(b.off())\n    }\n}\n\nfn lookup_static<T: NameValue>(h: &T) -> Option<(u64, bool)> {\n    // Fetch the right encoding table for this header length.\n    let table_for_len =\n        super::static_table::STATIC_ENCODE_TABLE.get(h.name().len())?;\n\n    // Similar to [`eq_ignore_ascii_case`], but only lowercases the second\n    // operand, as the entries in the table are already lower cased.\n    let cmp_lowercase = |a: &[u8], b: &[u8]| {\n        std::iter::zip(a, b).all(|(a, b)| a.eq(&b.to_ascii_lowercase()))\n    };\n\n    for (name, values) in table_for_len.iter() {\n        // Match header name first.\n        if cmp_lowercase(name, h.name()) {\n            // Second iterate over possible values for the header.\n            for (value, enc) in values.iter() {\n                // Match header value.\n                if value.is_empty() {\n                    return Some((*enc, false));\n                }\n\n                if h.value() == *value {\n                    return Some((*enc, true));\n                }\n            }\n            // Only matched the header, not the value.\n            return Some((values.first()?.1, false));\n        }\n    }\n\n    None\n}\n\npub fn encode_int(\n    mut v: u64, first: u8, prefix: usize, b: &mut octets::OctetsMut,\n) -> Result<()> {\n    let mask = 2u64.pow(prefix as u32) - 1;\n\n    // Encode I on N bits.\n    if v < mask {\n        b.put_u8(first | v as u8)?;\n        return Ok(());\n    }\n\n    // Encode (2^N - 1) on N bits.\n    b.put_u8(first | mask as u8)?;\n\n    v -= mask;\n\n    while v >= 128 {\n        // Encode (I % 128 + 128) on 8 bits.\n        b.put_u8((v % 128 + 128) as u8)?;\n\n        v >>= 7;\n    }\n\n    // Encode I on 8 bits.\n    b.put_u8(v as u8)?;\n\n    Ok(())\n}\n\n#[inline]\npub fn encode_str<const LOWER_CASE: bool>(\n    v: &[u8], first: u8, prefix: usize, b: &mut octets::OctetsMut,\n) -> Result<()> {\n    // Huffman-encoding generally saves space but in some cases it doesn't, for\n    // those just encode the literal string.\n    match octets::huffman_encoding_len::<LOWER_CASE>(v) {\n        Ok(len) => {\n            encode_int(len as u64, first | (1 << prefix), prefix, b)?;\n            b.put_huffman_encoded::<LOWER_CASE>(v)?;\n        },\n\n        Err(_) => {\n            encode_int(v.len() as u64, first, prefix, b)?;\n            if LOWER_CASE {\n                b.put_bytes(&v.to_ascii_lowercase())?;\n            } else {\n                b.put_bytes(v)?;\n            }\n        },\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn encode_int1() {\n        let expected = [0b01010];\n        let mut encoded = [0; 1];\n        let mut b = octets::OctetsMut::with_slice(&mut encoded);\n\n        assert!(encode_int(10, 0, 5, &mut b).is_ok());\n\n        assert_eq!(expected, encoded);\n    }\n\n    #[test]\n    fn encode_int2() {\n        let expected = [0b11111, 0b10011010, 0b00001010];\n        let mut encoded = [0; 3];\n        let mut b = octets::OctetsMut::with_slice(&mut encoded);\n\n        assert!(encode_int(1337, 0, 5, &mut b).is_ok());\n\n        assert_eq!(expected, encoded);\n    }\n\n    #[test]\n    fn encode_int3() {\n        let expected = [0b101010];\n        let mut encoded = [0; 1];\n        let mut b = octets::OctetsMut::with_slice(&mut encoded);\n\n        assert!(encode_int(42, 0, 8, &mut b).is_ok());\n\n        assert_eq!(expected, encoded);\n    }\n\n    #[test]\n    fn encode_static_header() {\n        let mut encoded = [0; 3];\n        Encoder::default()\n            .encode(&[(b\":method\", b\"GET\")], &mut encoded)\n            .unwrap();\n        assert_eq!(encoded, [0, 0, INDEXED | 0x40 | 17]);\n    }\n\n    #[test]\n    fn encode_static_header_name_only() {\n        let mut encoded = [0; 11];\n        let mut expected = [0; 11];\n        let mut buf = octets::OctetsMut::with_slice(&mut expected[..]);\n        buf.put_u16(0).unwrap();\n        buf.put_u8(LITERAL_WITH_NAME_REF | 0x10 | 15).unwrap();\n        buf.put_u8(0).unwrap();\n        encode_str::<false>(b\"FORGET\", 0, 7, &mut buf).unwrap();\n\n        Encoder::default()\n            .encode(&[(b\":method\", b\"FORGET\")], &mut encoded)\n            .unwrap();\n        assert_eq!(encoded, expected);\n    }\n}\n"
  },
  {
    "path": "quiche/src/h3/qpack/mod.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! HTTP/3 header compression (QPACK).\n\npub use encoder::encode_int;\npub use encoder::encode_str;\n\npub const INDEXED: u8 = 0b1000_0000;\npub const INDEXED_WITH_POST_BASE: u8 = 0b0001_0000;\npub const LITERAL: u8 = 0b0010_0000;\npub const LITERAL_WITH_NAME_REF: u8 = 0b0100_0000;\n\n/// A specialized [`Result`] type for quiche QPACK operations.\n///\n/// This type is used throughout quiche's QPACK public API for any operation\n/// that can produce an error.\n///\n/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html\npub type Result<T> = std::result::Result<T, Error>;\n\n/// A QPACK error.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum Error {\n    /// The provided buffer is too short.\n    BufferTooShort,\n\n    /// The QPACK header block's huffman encoding is invalid.\n    InvalidHuffmanEncoding,\n\n    /// The QPACK static table index provided doesn't exist.\n    InvalidStaticTableIndex,\n\n    /// The decoded QPACK header name or value is not valid.\n    InvalidHeaderValue,\n\n    /// The decoded header list exceeded the size limit.\n    HeaderListTooLarge,\n}\n\nimpl std::fmt::Display for Error {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{self:?}\")\n    }\n}\n\nimpl std::error::Error for Error {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        None\n    }\n}\n\nimpl From<octets::BufferTooShortError> for Error {\n    fn from(_err: octets::BufferTooShortError) -> Self {\n        Error::BufferTooShort\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::*;\n\n    use super::*;\n\n    #[test]\n    fn encode_decode() {\n        let mut encoded = [0u8; 240];\n\n        let headers = vec![\n            h3::Header::new(b\":path\", b\"/rsrc.php/v3/yn/r/rIPZ9Qkrdd9.png\"),\n            h3::Header::new(b\"accept-encoding\", b\"gzip, deflate, br\"),\n            h3::Header::new(b\"accept-language\", b\"en-US,en;q=0.9\"),\n            h3::Header::new(b\"user-agent\", b\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.70 Safari/537.36\"),\n            h3::Header::new(b\"accept\", b\"image/webp,image/apng,image/*,*/*;q=0.8\"),\n            h3::Header::new(b\"referer\", b\"https://static.xx.fbcdn.net/rsrc.php/v3/yT/l/0,cross/dzXGESIlGQQ.css\"),\n            h3::Header::new(b\":authority\", b\"static.xx.fbcdn.net\"),\n            h3::Header::new(b\":scheme\", b\"https\"),\n            h3::Header::new(b\":method\", b\"GET\"),\n        ];\n\n        let mut enc = Encoder::new();\n        assert_eq!(enc.encode(&headers, &mut encoded), Ok(240));\n\n        let mut dec = Decoder::new();\n        assert_eq!(dec.decode(&encoded, u64::MAX), Ok(headers));\n    }\n\n    #[test]\n    fn lower_case() {\n        let mut encoded = [0u8; 35];\n\n        let headers_expected = vec![\n            h3::Header::new(b\":status\", b\"200\"),\n            h3::Header::new(b\":path\", b\"/HeLlO\"),\n            h3::Header::new(b\"woot\", b\"woot\"),\n            h3::Header::new(b\"hello\", b\"WorlD\"),\n            h3::Header::new(b\"foo\", b\"BaR\"),\n        ];\n\n        // Header.\n        let headers_in = vec![\n            h3::Header::new(b\":StAtUs\", b\"200\"),\n            h3::Header::new(b\":PaTh\", b\"/HeLlO\"),\n            h3::Header::new(b\"WooT\", b\"woot\"),\n            h3::Header::new(b\"hello\", b\"WorlD\"),\n            h3::Header::new(b\"fOo\", b\"BaR\"),\n        ];\n\n        let mut enc = Encoder::new();\n        assert_eq!(enc.encode(&headers_in, &mut encoded), Ok(35));\n\n        let mut dec = Decoder::new();\n        let headers_out = dec.decode(&encoded, u64::MAX).unwrap();\n\n        assert_eq!(headers_expected, headers_out);\n\n        // HeaderRef.\n        let headers_in = vec![\n            h3::HeaderRef::new(b\":StAtUs\", b\"200\"),\n            h3::HeaderRef::new(b\":PaTh\", b\"/HeLlO\"),\n            h3::HeaderRef::new(b\"WooT\", b\"woot\"),\n            h3::HeaderRef::new(b\"hello\", b\"WorlD\"),\n            h3::HeaderRef::new(b\"fOo\", b\"BaR\"),\n        ];\n\n        let mut enc = Encoder::new();\n        assert_eq!(enc.encode(&headers_in, &mut encoded), Ok(35));\n\n        let mut dec = Decoder::new();\n        let headers_out = dec.decode(&encoded, u64::MAX).unwrap();\n\n        assert_eq!(headers_expected, headers_out);\n    }\n\n    #[test]\n    fn lower_ascii_range() {\n        let mut encoded = [0u8; 50];\n        let mut enc = Encoder::new();\n\n        // Indexed name with literal value\n        let headers1 = vec![h3::Header::new(b\"location\", b\"\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\")];\n        assert_eq!(enc.encode(&headers1, &mut encoded), Ok(19));\n\n        // Literal name and value\n        let headers2 = vec![h3::Header::new(b\"a\", b\"\u001e\u001e\u001e\u001e\u001e\u001e\u001e\u001e\u001e\u001e\u001e\u001e\u001e\u001e\u001e\")];\n        assert_eq!(enc.encode(&headers2, &mut encoded), Ok(20));\n\n        let headers3 = vec![h3::Header::new(b\"\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\", b\"hello\")];\n        assert_eq!(enc.encode(&headers3, &mut encoded), Ok(24));\n    }\n\n    #[test]\n    fn extended_ascii_range() {\n        let mut encoded = [0u8; 50];\n        let mut enc = Encoder::new();\n\n        let name = b\"location\";\n        let value = \"£££££££££££££££\";\n\n        // Indexed name with literal value\n        let headers1 = vec![h3::Header::new(name, value.as_bytes())];\n        assert_eq!(enc.encode(&headers1, &mut encoded), Ok(34));\n\n        // Literal name and value\n        let value = \"ððððððððððððððð\";\n        let headers2 = vec![h3::Header::new(b\"a\", value.as_bytes())];\n        assert_eq!(enc.encode(&headers2, &mut encoded), Ok(35));\n\n        let headers3 = vec![h3::Header::new(value.as_bytes(), b\"hello\")];\n        assert_eq!(enc.encode(&headers3, &mut encoded), Ok(39));\n    }\n}\n\npub use decoder::Decoder;\npub use encoder::Encoder;\n\nmod decoder;\nmod encoder;\nmod static_table;\n"
  },
  {
    "path": "quiche/src/h3/qpack/static_table.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n/// This table maps the statically encoded QPACK entries to their\n/// index. The mapping is from name length to a list of names of this\n/// length, with the list of possible values for that name and the proper\n/// encoding for this name: value pair.\ntype HeaderName = &'static [u8];\ntype HeaderValue = &'static [u8];\ntype HeaderValueEncPairs = &'static [(HeaderValue, u64)];\npub const STATIC_ENCODE_TABLE: &[&[(HeaderName, HeaderValueEncPairs)]] = &[\n    // Headers of len 0\n    &[],\n    // Headers of len 1\n    &[],\n    // Headers of len 2\n    &[],\n    // Headers of len 3\n    &[(b\"age\", &[(b\"0\", 2)])],\n    // Headers of len 4\n    &[\n        (b\"etag\", &[(b\"\", 7)]),\n        (b\"date\", &[(b\"\", 6)]),\n        (b\"link\", &[(b\"\", 11)]),\n        (b\"vary\", &[(b\"accept-encoding\", 59), (b\"origin\", 60)]),\n    ],\n    // Headers of len 5\n    &[(b\"range\", &[(b\"bytes=0-\", 55)]), (b\":path\", &[(b\"/\", 1)])],\n    // Headers of len 6\n    &[\n        (b\"cookie\", &[(b\"\", 5)]),\n        (b\"origin\", &[(b\"\", 90)]),\n        (b\"server\", &[(b\"\", 92)]),\n        (b\"accept\", &[(b\"*/*\", 29), (b\"application/dns-message\", 30)]),\n    ],\n    // Headers of len 7\n    &[\n        (b\"purpose\", &[(b\"prefetch\", 91)]),\n        (b\"referer\", &[(b\"\", 13)]),\n        (b\"alt-svc\", &[(b\"clear\", 83)]),\n        (b\":status\", &[\n            (b\"103\", 24),\n            (b\"200\", 25),\n            (b\"304\", 26),\n            (b\"404\", 27),\n            (b\"503\", 28),\n            (b\"100\", 63),\n            (b\"204\", 64),\n            (b\"206\", 65),\n            (b\"302\", 66),\n            (b\"400\", 67),\n            (b\"403\", 68),\n            (b\"421\", 69),\n            (b\"425\", 70),\n            (b\"500\", 71),\n        ]),\n        (b\":scheme\", &[(b\"http\", 22), (b\"https\", 23)]),\n        (b\":method\", &[\n            (b\"CONNECT\", 15),\n            (b\"DELETE\", 16),\n            (b\"GET\", 17),\n            (b\"HEAD\", 18),\n            (b\"OPTIONS\", 19),\n            (b\"POST\", 20),\n            (b\"PUT\", 21),\n        ]),\n    ],\n    // Headers of len 8\n    &[(b\"location\", &[(b\"\", 12)]), (b\"if-range\", &[(b\"\", 89)])],\n    // Headers of len 9\n    &[(b\"expect-ct\", &[(b\"\", 87)]), (b\"forwarded\", &[(b\"\", 88)])],\n    // Headers of len 10\n    &[\n        (b\"user-agent\", &[(b\"\", 95)]),\n        (b\":authority\", &[(b\"\", 0)]),\n        (b\"set-cookie\", &[(b\"\", 14)]),\n        (b\"early-data\", &[(b\"1\", 86)]),\n    ],\n    // Headers of len 11\n    &[],\n    // Headers of len 12\n    &[(b\"content-type\", &[\n        (b\"application/dns-message\", 44),\n        (b\"application/javascript\", 45),\n        (b\"application/json\", 46),\n        (b\"application/x-www-form-urlencoded\", 47),\n        (b\"image/gif\", 48),\n        (b\"image/jpeg\", 49),\n        (b\"image/png\", 50),\n        (b\"text/css\", 51),\n        (b\"text/html; charset=utf-8\", 52),\n        (b\"text/plain\", 53),\n        (b\"text/plain;charset=utf-8\", 54),\n    ])],\n    // Headers of len 13\n    &[\n        (b\"last-modified\", &[(b\"\", 10)]),\n        (b\"accept-ranges\", &[(b\"bytes\", 32)]),\n        (b\"authorization\", &[(b\"\", 84)]),\n        (b\"if-none-match\", &[(b\"\", 9)]),\n        (b\"cache-control\", &[\n            (b\"max-age=0\", 36),\n            (b\"max-age=2592000\", 37),\n            (b\"max-age=604800\", 38),\n            (b\"no-cache\", 39),\n            (b\"no-store\", 40),\n            (b\"public, max-age=31536000\", 41),\n        ]),\n    ],\n    // Headers of len 14\n    &[(b\"content-length\", &[(b\"0\", 4)])],\n    // Headers of len 15\n    &[\n        (b\"accept-encoding\", &[(b\"gzip, deflate, br\", 31)]),\n        (b\"x-forwarded-for\", &[(b\"\", 96)]),\n        (b\"accept-language\", &[(b\"\", 72)]),\n        (b\"x-frame-options\", &[(b\"deny\", 97), (b\"sameorigin\", 98)]),\n    ],\n    // Headers of len 16\n    &[\n        (b\"content-encoding\", &[(b\"br\", 42), (b\"gzip\", 43)]),\n        (b\"x-xss-protection\", &[(b\"1; mode=block\", 62)]),\n    ],\n    // Headers of len 17\n    &[(b\"if-modified-since\", &[(b\"\", 8)])],\n    // Headers of len 18\n    &[],\n    // Headers of len 19\n    &[\n        (b\"content-disposition\", &[(b\"\", 3)]),\n        (b\"timing-allow-origin\", &[(b\"*\", 93)]),\n    ],\n    // Headers of len 20\n    &[],\n    // Headers of len 21\n    &[],\n    // Headers of len 22\n    &[(b\"x-content-type-options\", &[(b\"nosniff\", 61)])],\n    // Headers of len 23\n    &[(b\"content-security-policy\", &[(\n        b\"script-src 'none'; object-src 'none'; base-uri 'none'\",\n        85,\n    )])],\n    // Headers of len 24\n    &[],\n    // Headers of len 25\n    &[\n        (b\"upgrade-insecure-requests\", &[(b\"1\", 94)]),\n        (b\"strict-transport-security\", &[\n            (b\"max-age=31536000\", 56),\n            (b\"max-age=31536000; includesubdomains\", 57),\n            (b\"max-age=31536000; includesubdomains; preload\", 58),\n        ]),\n    ],\n    // Headers of len 26\n    &[],\n    // Headers of len 27\n    &[(b\"access-control-allow-origin\", &[(b\"*\", 35)])],\n    // Headers of len 28\n    &[\n        (b\"access-control-allow-methods\", &[\n            (b\"get\", 76),\n            (b\"get, post, options\", 77),\n            (b\"options\", 78),\n        ]),\n        (b\"access-control-allow-headers\", &[\n            (b\"cache-control\", 33),\n            (b\"content-type\", 34),\n            (b\"*\", 75),\n        ]),\n    ],\n    // Headers of len 29\n    &[\n        (b\"access-control-expose-headers\", &[(b\"content-length\", 79)]),\n        (b\"access-control-request-method\", &[\n            (b\"get\", 81),\n            (b\"post\", 82),\n        ]),\n    ],\n    // Headers of len 30\n    &[(b\"access-control-request-headers\", &[(b\"content-type\", 80)])],\n    // Headers of len 31\n    &[],\n    // Headers of len 32\n    &[(b\"access-control-allow-credentials\", &[\n        (b\"FALSE\", 73),\n        (b\"TRUE\", 74),\n    ])],\n];\n\npub const STATIC_DECODE_TABLE: [(&[u8], &[u8]); 99] = [\n    (b\":authority\", b\"\"),\n    (b\":path\", b\"/\"),\n    (b\"age\", b\"0\"),\n    (b\"content-disposition\", b\"\"),\n    (b\"content-length\", b\"0\"),\n    (b\"cookie\", b\"\"),\n    (b\"date\", b\"\"),\n    (b\"etag\", b\"\"),\n    (b\"if-modified-since\", b\"\"),\n    (b\"if-none-match\", b\"\"),\n    (b\"last-modified\", b\"\"),\n    (b\"link\", b\"\"),\n    (b\"location\", b\"\"),\n    (b\"referer\", b\"\"),\n    (b\"set-cookie\", b\"\"),\n    (b\":method\", b\"CONNECT\"),\n    (b\":method\", b\"DELETE\"),\n    (b\":method\", b\"GET\"),\n    (b\":method\", b\"HEAD\"),\n    (b\":method\", b\"OPTIONS\"),\n    (b\":method\", b\"POST\"),\n    (b\":method\", b\"PUT\"),\n    (b\":scheme\", b\"http\"),\n    (b\":scheme\", b\"https\"),\n    (b\":status\", b\"103\"),\n    (b\":status\", b\"200\"),\n    (b\":status\", b\"304\"),\n    (b\":status\", b\"404\"),\n    (b\":status\", b\"503\"),\n    (b\"accept\", b\"*/*\"),\n    (b\"accept\", b\"application/dns-message\"),\n    (b\"accept-encoding\", b\"gzip, deflate, br\"),\n    (b\"accept-ranges\", b\"bytes\"),\n    (b\"access-control-allow-headers\", b\"cache-control\"),\n    (b\"access-control-allow-headers\", b\"content-type\"),\n    (b\"access-control-allow-origin\", b\"*\"),\n    (b\"cache-control\", b\"max-age=0\"),\n    (b\"cache-control\", b\"max-age=2592000\"),\n    (b\"cache-control\", b\"max-age=604800\"),\n    (b\"cache-control\", b\"no-cache\"),\n    (b\"cache-control\", b\"no-store\"),\n    (b\"cache-control\", b\"public, max-age=31536000\"),\n    (b\"content-encoding\", b\"br\"),\n    (b\"content-encoding\", b\"gzip\"),\n    (b\"content-type\", b\"application/dns-message\"),\n    (b\"content-type\", b\"application/javascript\"),\n    (b\"content-type\", b\"application/json\"),\n    (b\"content-type\", b\"application/x-www-form-urlencoded\"),\n    (b\"content-type\", b\"image/gif\"),\n    (b\"content-type\", b\"image/jpeg\"),\n    (b\"content-type\", b\"image/png\"),\n    (b\"content-type\", b\"text/css\"),\n    (b\"content-type\", b\"text/html; charset=utf-8\"),\n    (b\"content-type\", b\"text/plain\"),\n    (b\"content-type\", b\"text/plain;charset=utf-8\"),\n    (b\"range\", b\"bytes=0-\"),\n    (b\"strict-transport-security\", b\"max-age=31536000\"),\n    (\n        b\"strict-transport-security\",\n        b\"max-age=31536000; includesubdomains\",\n    ),\n    (\n        b\"strict-transport-security\",\n        b\"max-age=31536000; includesubdomains; preload\",\n    ),\n    (b\"vary\", b\"accept-encoding\"),\n    (b\"vary\", b\"origin\"),\n    (b\"x-content-type-options\", b\"nosniff\"),\n    (b\"x-xss-protection\", b\"1; mode=block\"),\n    (b\":status\", b\"100\"),\n    (b\":status\", b\"204\"),\n    (b\":status\", b\"206\"),\n    (b\":status\", b\"302\"),\n    (b\":status\", b\"400\"),\n    (b\":status\", b\"403\"),\n    (b\":status\", b\"421\"),\n    (b\":status\", b\"425\"),\n    (b\":status\", b\"500\"),\n    (b\"accept-language\", b\"\"),\n    (b\"access-control-allow-credentials\", b\"FALSE\"),\n    (b\"access-control-allow-credentials\", b\"TRUE\"),\n    (b\"access-control-allow-headers\", b\"*\"),\n    (b\"access-control-allow-methods\", b\"get\"),\n    (b\"access-control-allow-methods\", b\"get, post, options\"),\n    (b\"access-control-allow-methods\", b\"options\"),\n    (b\"access-control-expose-headers\", b\"content-length\"),\n    (b\"access-control-request-headers\", b\"content-type\"),\n    (b\"access-control-request-method\", b\"get\"),\n    (b\"access-control-request-method\", b\"post\"),\n    (b\"alt-svc\", b\"clear\"),\n    (b\"authorization\", b\"\"),\n    (\n        b\"content-security-policy\",\n        b\"script-src 'none'; object-src 'none'; base-uri 'none'\",\n    ),\n    (b\"early-data\", b\"1\"),\n    (b\"expect-ct\", b\"\"),\n    (b\"forwarded\", b\"\"),\n    (b\"if-range\", b\"\"),\n    (b\"origin\", b\"\"),\n    (b\"purpose\", b\"prefetch\"),\n    (b\"server\", b\"\"),\n    (b\"timing-allow-origin\", b\"*\"),\n    (b\"upgrade-insecure-requests\", b\"1\"),\n    (b\"user-agent\", b\"\"),\n    (b\"x-forwarded-for\", b\"\"),\n    (b\"x-frame-options\", b\"deny\"),\n    (b\"x-frame-options\", b\"sameorigin\"),\n];\n"
  },
  {
    "path": "quiche/src/h3/stream.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::buffers::BufFactory;\n\nuse super::Error;\nuse super::Result;\n\nuse super::frame;\n\npub const HTTP3_CONTROL_STREAM_TYPE_ID: u64 = 0x0;\npub const HTTP3_PUSH_STREAM_TYPE_ID: u64 = 0x1;\npub const QPACK_ENCODER_STREAM_TYPE_ID: u64 = 0x2;\npub const QPACK_DECODER_STREAM_TYPE_ID: u64 = 0x3;\n\nconst MAX_STATE_BUF_SIZE: usize = (1 << 24) - 1;\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum Type {\n    Control,\n    Request,\n    Push,\n    QpackEncoder,\n    QpackDecoder,\n    Unknown,\n}\n\nimpl Type {\n    #[cfg(feature = \"qlog\")]\n    pub fn to_qlog(self) -> qlog::events::http3::StreamType {\n        match self {\n            Type::Control => qlog::events::http3::StreamType::Control,\n            Type::Request => qlog::events::http3::StreamType::Request,\n            Type::Push => qlog::events::http3::StreamType::Push,\n            Type::QpackEncoder => qlog::events::http3::StreamType::QpackEncode,\n            Type::QpackDecoder => qlog::events::http3::StreamType::QpackDecode,\n            Type::Unknown => qlog::events::http3::StreamType::Unknown,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum State {\n    /// Reading the stream's type.\n    StreamType,\n\n    /// Reading the stream's current frame's type.\n    FrameType,\n\n    /// Reading the stream's current frame's payload length.\n    FramePayloadLen,\n\n    /// Reading the stream's current frame's payload.\n    FramePayload,\n\n    /// Reading DATA payload.\n    Data,\n\n    /// Reading the push ID.\n    PushId,\n\n    /// Reading a QPACK instruction.\n    QpackInstruction,\n\n    /// Reading and discarding data.\n    Drain,\n\n    /// All data has been read.\n    Finished,\n}\n\nimpl Type {\n    pub fn deserialize(v: u64) -> Result<Type> {\n        match v {\n            HTTP3_CONTROL_STREAM_TYPE_ID => Ok(Type::Control),\n            HTTP3_PUSH_STREAM_TYPE_ID => Ok(Type::Push),\n            QPACK_ENCODER_STREAM_TYPE_ID => Ok(Type::QpackEncoder),\n            QPACK_DECODER_STREAM_TYPE_ID => Ok(Type::QpackDecoder),\n\n            _ => Ok(Type::Unknown),\n        }\n    }\n}\n\n/// An HTTP/3 stream.\n///\n/// This maintains the HTTP/3 state for streams of any type (control, request,\n/// QPACK, ...).\n///\n/// A number of bytes, depending on the current stream's state, is read from the\n/// transport stream into the HTTP/3 stream's \"state buffer\". This intermediate\n/// buffering is required due to the fact that data read from the transport\n/// might not be complete (e.g. a varint might be split across multiple QUIC\n/// packets).\n///\n/// When enough data to complete the current state has been buffered, it is\n/// consumed from the state buffer and the stream is transitioned to the next\n/// state (see `State` for a list of possible states).\n#[derive(Debug)]\npub struct Stream {\n    /// The corresponding transport stream's ID.\n    id: u64,\n\n    /// The stream's type (if known).\n    ty: Option<Type>,\n\n    /// The current stream state.\n    state: State,\n\n    /// The buffer holding partial data for the current state.\n    state_buf: Vec<u8>,\n\n    /// The expected amount of bytes required to complete the state.\n    state_len: usize,\n\n    /// The write offset in the state buffer, that is, how many bytes have\n    /// already been read from the transport for the current state. When\n    /// it reaches `stream_len` the state can be completed.\n    state_off: usize,\n\n    /// The type of the frame currently being parsed.\n    frame_type: Option<u64>,\n\n    /// Whether the stream was created locally, or by the peer.\n    is_local: bool,\n\n    /// Whether the stream has been remotely initialized.\n    remote_initialized: bool,\n\n    /// Whether the stream has been locally initialized.\n    local_initialized: bool,\n\n    /// Whether a `Data` event has been triggered for this stream.\n    data_event_triggered: bool,\n\n    /// The last `PRIORITY_UPDATE` frame encoded field value, if any.\n    last_priority_update: Option<Vec<u8>>,\n\n    /// The count of HEADERS frames that have been received.\n    headers_received_count: usize,\n\n    /// Whether a DATA frame has been received.\n    data_received: bool,\n\n    /// Whether a trailing HEADER field has been sent.\n    trailers_sent: bool,\n\n    /// Whether a trailing HEADER field has been received.\n    trailers_received: bool,\n}\n\nimpl Stream {\n    /// Creates a new HTTP/3 stream.\n    ///\n    /// The `is_local` parameter indicates whether the stream was created by the\n    /// local endpoint, or by the peer.\n    pub fn new(id: u64, is_local: bool) -> Stream {\n        let (ty, state) = if crate::stream::is_bidi(id) {\n            // All bidirectional streams are \"request\" streams, so we don't\n            // need to read the stream type.\n            (Some(Type::Request), State::FrameType)\n        } else {\n            // The stream's type is yet to be determined.\n            (None, State::StreamType)\n        };\n\n        Stream {\n            id,\n            ty,\n\n            state,\n\n            // Pre-allocate a buffer to avoid multiple tiny early allocations.\n            state_buf: vec![0; 16],\n\n            // Expect one byte for the initial state, to parse the initial\n            // varint length.\n            state_len: 1,\n            state_off: 0,\n\n            frame_type: None,\n\n            is_local,\n            remote_initialized: false,\n            local_initialized: false,\n\n            data_event_triggered: false,\n\n            last_priority_update: None,\n\n            headers_received_count: 0,\n\n            data_received: false,\n\n            trailers_sent: false,\n            trailers_received: false,\n        }\n    }\n\n    pub fn ty(&self) -> Option<Type> {\n        self.ty\n    }\n\n    pub fn state(&self) -> State {\n        self.state\n    }\n\n    /// Sets the stream's type and transitions to the next state.\n    pub fn set_ty(&mut self, ty: Type) -> Result<()> {\n        assert_eq!(self.state, State::StreamType);\n\n        self.ty = Some(ty);\n\n        let state = match ty {\n            Type::Control | Type::Request => State::FrameType,\n\n            Type::Push => State::PushId,\n\n            Type::QpackEncoder | Type::QpackDecoder => {\n                self.remote_initialized = true;\n\n                State::QpackInstruction\n            },\n\n            Type::Unknown => State::Drain,\n        };\n\n        self.state_transition(state, 1, true)?;\n\n        Ok(())\n    }\n\n    /// Sets the push ID and transitions to the next state.\n    pub fn set_push_id(&mut self, _id: u64) -> Result<()> {\n        assert_eq!(self.state, State::PushId);\n\n        // TODO: implement push ID.\n\n        self.state_transition(State::FrameType, 1, true)?;\n\n        Ok(())\n    }\n\n    /// Sets the frame type and transitions to the next state.\n    pub fn set_frame_type(&mut self, ty: u64) -> Result<()> {\n        assert_eq!(self.state, State::FrameType);\n\n        // Only expect frames on Control, Request and Push streams.\n        match self.ty {\n            Some(Type::Control) => {\n                // Control stream starts uninitialized and only SETTINGS is\n                // accepted in that state. Other frames cause an error. Once\n                // initialized, no more SETTINGS are permitted.\n                match (ty, self.remote_initialized) {\n                    // Initialize control stream.\n                    (frame::SETTINGS_FRAME_TYPE_ID, false) =>\n                        self.remote_initialized = true,\n\n                    // Non-SETTINGS frames not allowed on control stream\n                    // before initialization.\n                    (_, false) => return Err(Error::MissingSettings),\n\n                    // Additional SETTINGS frame.\n                    (frame::SETTINGS_FRAME_TYPE_ID, true) =>\n                        return Err(Error::FrameUnexpected),\n\n                    // Frames that can't be received on control stream\n                    // after initialization.\n                    (frame::DATA_FRAME_TYPE_ID, true) =>\n                        return Err(Error::FrameUnexpected),\n\n                    (frame::HEADERS_FRAME_TYPE_ID, true) =>\n                        return Err(Error::FrameUnexpected),\n\n                    (frame::PUSH_PROMISE_FRAME_TYPE_ID, true) =>\n                        return Err(Error::FrameUnexpected),\n\n                    // All other frames are ignored after initialization.\n                    (_, true) => (),\n                }\n            },\n\n            Some(Type::Request) => {\n                // Request stream starts uninitialized and only HEADERS is\n                // accepted. After initialization, DATA and HEADERS frames may\n                // be acceptable, depending on the role and HTTP message phase.\n                //\n                // Receiving some other types of known frames on the request\n                // stream is always an error.\n                if !self.is_local {\n                    match (ty, self.remote_initialized) {\n                        (frame::HEADERS_FRAME_TYPE_ID, false) => {\n                            self.remote_initialized = true;\n                        },\n\n                        (frame::DATA_FRAME_TYPE_ID, false) =>\n                            return Err(Error::FrameUnexpected),\n\n                        (frame::HEADERS_FRAME_TYPE_ID, true) => {\n                            if self.trailers_received {\n                                return Err(Error::FrameUnexpected);\n                            }\n\n                            if self.data_received {\n                                self.trailers_received = true;\n                            }\n                        },\n\n                        (frame::DATA_FRAME_TYPE_ID, true) => {\n                            if self.trailers_received {\n                                return Err(Error::FrameUnexpected);\n                            }\n\n                            self.data_received = true;\n                        },\n\n                        (frame::CANCEL_PUSH_FRAME_TYPE_ID, _) =>\n                            return Err(Error::FrameUnexpected),\n\n                        (frame::SETTINGS_FRAME_TYPE_ID, _) =>\n                            return Err(Error::FrameUnexpected),\n\n                        (frame::GOAWAY_FRAME_TYPE_ID, _) =>\n                            return Err(Error::FrameUnexpected),\n\n                        (frame::MAX_PUSH_FRAME_TYPE_ID, _) =>\n                            return Err(Error::FrameUnexpected),\n\n                        (frame::PRIORITY_UPDATE_FRAME_REQUEST_TYPE_ID, _) =>\n                            return Err(Error::FrameUnexpected),\n\n                        (frame::PRIORITY_UPDATE_FRAME_PUSH_TYPE_ID, _) =>\n                            return Err(Error::FrameUnexpected),\n\n                        // All other frames can be ignored regardless of stream\n                        // state.\n                        _ => (),\n                    }\n                }\n            },\n\n            Some(Type::Push) => {\n                match ty {\n                    // Frames that can never be received on request streams.\n                    frame::CANCEL_PUSH_FRAME_TYPE_ID =>\n                        return Err(Error::FrameUnexpected),\n\n                    frame::SETTINGS_FRAME_TYPE_ID =>\n                        return Err(Error::FrameUnexpected),\n\n                    frame::PUSH_PROMISE_FRAME_TYPE_ID =>\n                        return Err(Error::FrameUnexpected),\n\n                    frame::GOAWAY_FRAME_TYPE_ID =>\n                        return Err(Error::FrameUnexpected),\n\n                    frame::MAX_PUSH_FRAME_TYPE_ID =>\n                        return Err(Error::FrameUnexpected),\n\n                    _ => (),\n                }\n            },\n\n            _ => return Err(Error::FrameUnexpected),\n        }\n\n        self.frame_type = Some(ty);\n\n        self.state_transition(State::FramePayloadLen, 1, true)?;\n\n        Ok(())\n    }\n\n    // Returns the stream's current frame type, if any\n    pub fn frame_type(&self) -> Option<u64> {\n        self.frame_type\n    }\n\n    /// Sets the frame's payload length and transitions to the next state.\n    pub fn set_frame_payload_len(&mut self, len: u64) -> Result<()> {\n        assert_eq!(self.state, State::FramePayloadLen);\n\n        // Only expect frames on Control, Request and Push streams.\n        if matches!(self.ty, Some(Type::Control | Type::Request | Type::Push)) {\n            let (state, resize) = match self.frame_type {\n                Some(frame::DATA_FRAME_TYPE_ID) => (State::Data, false),\n\n                // These frame types can never have 0 payload length because\n                // they always have fields that must be populated.\n                Some(\n                    frame::GOAWAY_FRAME_TYPE_ID |\n                    frame::PUSH_PROMISE_FRAME_TYPE_ID |\n                    frame::CANCEL_PUSH_FRAME_TYPE_ID |\n                    frame::MAX_PUSH_FRAME_TYPE_ID,\n                ) => {\n                    if len == 0 {\n                        return Err(Error::FrameError);\n                    }\n\n                    (State::FramePayload, true)\n                },\n\n                _ => (State::FramePayload, true),\n            };\n\n            self.state_transition(state, len as usize, resize)?;\n\n            return Ok(());\n        }\n\n        Err(Error::InternalError)\n    }\n\n    /// Tries to fill the state buffer by reading data from the corresponding\n    /// transport stream.\n    ///\n    /// When not enough data can be read to complete the state, this returns\n    /// `Error::Done`.\n    pub fn try_fill_buffer<F: BufFactory>(\n        &mut self, conn: &mut crate::Connection<F>,\n    ) -> Result<()> {\n        // If no bytes are required to be read, return early.\n        if self.state_buffer_complete() {\n            return Ok(());\n        }\n\n        let buf = &mut self.state_buf[self.state_off..self.state_len];\n\n        let read = match conn.stream_recv(self.id, buf) {\n            Ok((len, fin)) => {\n                // Check whether one of the critical stream was closed.\n                if fin &&\n                    matches!(\n                        self.ty,\n                        Some(Type::Control) |\n                            Some(Type::QpackEncoder) |\n                            Some(Type::QpackDecoder)\n                    )\n                {\n                    super::close_conn_critical_stream(conn)?;\n                }\n\n                len\n            },\n\n            Err(e @ crate::Error::StreamReset(_)) => {\n                // Check whether one of the critical stream was closed.\n                if matches!(\n                    self.ty,\n                    Some(Type::Control) |\n                        Some(Type::QpackEncoder) |\n                        Some(Type::QpackDecoder)\n                ) {\n                    super::close_conn_critical_stream(conn)?;\n                }\n\n                return Err(e.into());\n            },\n\n            Err(e) => {\n                // The stream is not readable anymore, so re-arm the Data event.\n                if e == crate::Error::Done {\n                    self.reset_data_event();\n                }\n\n                return Err(e.into());\n            },\n        };\n\n        trace!(\n            \"{} read {} bytes on stream {}\",\n            conn.trace_id(),\n            read,\n            self.id,\n        );\n\n        self.state_off += read;\n\n        if !self.state_buffer_complete() {\n            self.reset_data_event();\n\n            return Err(Error::Done);\n        }\n\n        Ok(())\n    }\n\n    /// Initialize the local part of the stream.\n    pub fn initialize_local(&mut self) {\n        self.local_initialized = true\n    }\n\n    /// Whether the stream has been locally initialized.\n    pub fn local_initialized(&self) -> bool {\n        self.local_initialized\n    }\n\n    pub fn increment_headers_received(&mut self) {\n        self.headers_received_count =\n            self.headers_received_count.saturating_add(1);\n    }\n\n    pub fn headers_received_count(&self) -> usize {\n        self.headers_received_count\n    }\n\n    pub fn mark_trailers_sent(&mut self) {\n        self.trailers_sent = true;\n    }\n\n    pub fn trailers_sent(&self) -> bool {\n        self.trailers_sent\n    }\n\n    /// Tries to fill the state buffer by reading data from the given cursor.\n    ///\n    /// This is intended to replace `try_fill_buffer()` in tests, in order to\n    /// avoid having to setup a transport connection.\n    #[cfg(test)]\n    fn try_fill_buffer_for_tests(\n        &mut self, stream: &mut std::io::Cursor<Vec<u8>>,\n    ) -> Result<()> {\n        // If no bytes are required to be read, return early\n        if self.state_buffer_complete() {\n            return Ok(());\n        }\n\n        let buf = &mut self.state_buf[self.state_off..self.state_len];\n\n        let read = std::io::Read::read(stream, buf).unwrap();\n\n        self.state_off += read;\n\n        if !self.state_buffer_complete() {\n            return Err(Error::Done);\n        }\n\n        Ok(())\n    }\n\n    /// Tries to parse a varint (including length) from the state buffer.\n    pub fn try_consume_varint(&mut self) -> Result<u64> {\n        if self.state_off == 1 {\n            self.state_len = octets::varint_parse_len(self.state_buf[0]);\n            self.state_buf.resize(self.state_len, 0);\n        }\n\n        // Return early if we don't have enough data in the state buffer to\n        // parse the whole varint.\n        if !self.state_buffer_complete() {\n            return Err(Error::Done);\n        }\n\n        let varint = octets::Octets::with_slice(&self.state_buf).get_varint()?;\n\n        Ok(varint)\n    }\n\n    /// Tries to parse a frame from the state buffer.\n    ///\n    /// If successful, returns the `frame::Frame` and the payload length.\n    pub fn try_consume_frame(&mut self) -> Result<(frame::Frame, u64)> {\n        debug_assert_eq!(self.state, State::FramePayload);\n        // Processing a frame other than DATA, so re-arm the Data event.\n        self.reset_data_event();\n\n        let payload_len = self.state_len as u64;\n\n        // TODO: properly propagate frame parsing errors.\n        let frame = frame::Frame::from_bytes(\n            self.frame_type.unwrap(),\n            payload_len,\n            &self.state_buf,\n        )?;\n\n        self.state_transition(State::FrameType, 1, true)?;\n\n        Ok((frame, payload_len))\n    }\n\n    /// Tries to read DATA payload from the transport stream.\n    pub fn try_consume_data<F: BufFactory>(\n        &mut self, conn: &mut crate::Connection<F>, out: &mut [u8],\n    ) -> Result<(usize, bool)> {\n        debug_assert_eq!(self.state, State::Data);\n        let left = std::cmp::min(out.len(), self.state_len - self.state_off);\n\n        let (len, fin) = match conn.stream_recv(self.id, &mut out[..left]) {\n            Ok(v) => v,\n\n            Err(e) => {\n                // The stream is not readable anymore, so re-arm the Data event.\n                if e == crate::Error::Done {\n                    self.reset_data_event();\n                }\n\n                return Err(e.into());\n            },\n        };\n\n        self.state_off += len;\n        debug_assert!(self.state_len >= self.state_off);\n\n        // The stream is not readable anymore, so re-arm the Data event.\n        if !conn.stream_readable(self.id) {\n            self.reset_data_event();\n        }\n\n        if self.state_buffer_complete() {\n            self.state_transition(State::FrameType, 1, true)?;\n        }\n\n        Ok((len, fin))\n    }\n\n    /// Marks the stream as finished.\n    pub fn finished(&mut self) {\n        let _ = self.state_transition(State::Finished, 0, false);\n    }\n\n    /// Tries to read DATA payload from the given cursor.\n    ///\n    /// This is intended to replace `try_consume_data()` in tests, in order to\n    /// avoid having to setup a transport connection.\n    #[cfg(test)]\n    fn try_consume_data_for_tests(\n        &mut self, stream: &mut std::io::Cursor<Vec<u8>>, out: &mut [u8],\n    ) -> Result<usize> {\n        let left = std::cmp::min(out.len(), self.state_len - self.state_off);\n\n        let len = std::io::Read::read(stream, &mut out[..left]).unwrap();\n\n        self.state_off += len;\n\n        if self.state_buffer_complete() {\n            self.state_transition(State::FrameType, 1, true)?;\n        }\n\n        Ok(len)\n    }\n\n    /// Tries to update the data triggered state for the stream.\n    ///\n    /// This returns `true` if a Data event was not already triggered before\n    /// the last reset, and updates the state. Returns `false` otherwise.\n    pub fn try_trigger_data_event(&mut self) -> bool {\n        if self.data_event_triggered {\n            return false;\n        }\n\n        self.data_event_triggered = true;\n\n        true\n    }\n\n    /// Resets the data triggered state.\n    fn reset_data_event(&mut self) {\n        self.data_event_triggered = false;\n    }\n\n    /// Set the last priority update for the stream.\n    pub fn set_last_priority_update(&mut self, priority_update: Option<Vec<u8>>) {\n        self.last_priority_update = priority_update;\n    }\n\n    /// Take the last priority update and leave `None` in its place.\n    pub fn take_last_priority_update(&mut self) -> Option<Vec<u8>> {\n        self.last_priority_update.take()\n    }\n\n    /// Returns `true` if there is a priority update.\n    pub fn has_last_priority_update(&self) -> bool {\n        self.last_priority_update.is_some()\n    }\n\n    /// Returns true if the state buffer has enough data to complete the state.\n    fn state_buffer_complete(&self) -> bool {\n        self.state_off == self.state_len\n    }\n\n    /// Transitions the stream to a new state, and optionally resets the state\n    /// buffer.\n    fn state_transition(\n        &mut self, new_state: State, expected_len: usize, resize: bool,\n    ) -> Result<()> {\n        // Some states don't need the state buffer, so don't resize it if not\n        // necessary.\n        if resize {\n            // A peer can influence the size of the state buffer (e.g. with the\n            // payload size of a GREASE frame), so we need to limit the maximum\n            // size to avoid DoS.\n            if expected_len > MAX_STATE_BUF_SIZE {\n                return Err(Error::ExcessiveLoad);\n            }\n\n            self.state_buf.resize(expected_len, 0);\n        }\n\n        self.state = new_state;\n        self.state_off = 0;\n        self.state_len = expected_len;\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::h3::frame::*;\n\n    use super::*;\n\n    fn open_uni(b: &mut octets::OctetsMut, ty: u64) -> Result<Stream> {\n        let stream = <Stream>::new(2, false);\n        assert_eq!(stream.state, State::StreamType);\n\n        b.put_varint(ty)?;\n\n        Ok(stream)\n    }\n\n    fn parse_uni(\n        stream: &mut Stream, ty: u64, cursor: &mut std::io::Cursor<Vec<u8>>,\n    ) -> Result<()> {\n        stream.try_fill_buffer_for_tests(cursor)?;\n\n        let stream_ty = stream.try_consume_varint()?;\n        assert_eq!(stream_ty, ty);\n        stream.set_ty(Type::deserialize(stream_ty).unwrap())?;\n\n        Ok(())\n    }\n\n    fn parse_skip_frame(\n        stream: &mut Stream, cursor: &mut std::io::Cursor<Vec<u8>>,\n    ) -> Result<()> {\n        // Parse the frame type.\n        stream.try_fill_buffer_for_tests(cursor)?;\n\n        let frame_ty = stream.try_consume_varint()?;\n\n        stream.set_frame_type(frame_ty)?;\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the frame payload length.\n        stream.try_fill_buffer_for_tests(cursor)?;\n\n        let frame_payload_len = stream.try_consume_varint()?;\n        stream.set_frame_payload_len(frame_payload_len)?;\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the frame payload.\n        stream.try_fill_buffer_for_tests(cursor)?;\n\n        stream.try_consume_frame()?;\n        assert_eq!(stream.state, State::FrameType);\n\n        Ok(())\n    }\n\n    #[test]\n    /// Process incoming SETTINGS frame on control stream.\n    fn control_good() {\n        let mut d = vec![42; 40];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let raw_settings = vec![\n            (SETTINGS_MAX_FIELD_SECTION_SIZE, 0),\n            (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),\n            (SETTINGS_QPACK_BLOCKED_STREAMS, 0),\n        ];\n\n        let frame = Frame::Settings {\n            max_field_section_size: Some(0),\n            qpack_max_table_capacity: Some(0),\n            qpack_blocked_streams: Some(0),\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            additional_settings: None,\n            raw: Some(raw_settings),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap();\n        frame.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor)\n            .unwrap();\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the SETTINGS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, SETTINGS_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the SETTINGS frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 6);\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the SETTINGS frame payload.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert_eq!(stream.try_consume_frame(), Ok((frame, 6)));\n        assert_eq!(stream.state, State::FrameType);\n    }\n\n    #[test]\n    /// Process incoming empty SETTINGS frame on control stream.\n    fn control_empty_settings() {\n        let mut d = vec![42; 40];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            additional_settings: None,\n            raw: Some(vec![]),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap();\n        frame.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor)\n            .unwrap();\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the SETTINGS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, SETTINGS_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the SETTINGS frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 0);\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the SETTINGS frame payload.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert_eq!(stream.try_consume_frame(), Ok((frame, 0)));\n        assert_eq!(stream.state, State::FrameType);\n    }\n\n    #[test]\n    /// Process duplicate SETTINGS frame on control stream.\n    fn control_bad_multiple_settings() {\n        let mut d = vec![42; 40];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let raw_settings = vec![\n            (SETTINGS_MAX_FIELD_SECTION_SIZE, 0),\n            (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),\n            (SETTINGS_QPACK_BLOCKED_STREAMS, 0),\n        ];\n\n        let frame = Frame::Settings {\n            max_field_section_size: Some(0),\n            qpack_max_table_capacity: Some(0),\n            qpack_blocked_streams: Some(0),\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            additional_settings: None,\n            raw: Some(raw_settings),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap();\n        frame.to_bytes(&mut b).unwrap();\n        frame.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor)\n            .unwrap();\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the SETTINGS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, SETTINGS_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the SETTINGS frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 6);\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the SETTINGS frame payload.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert_eq!(stream.try_consume_frame(), Ok((frame, 6)));\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the second SETTINGS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(stream.set_frame_type(frame_ty), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    /// Process other frame before SETTINGS frame on control stream.\n    fn control_bad_late_settings() {\n        let mut d = vec![42; 40];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let goaway = Frame::GoAway { id: 0 };\n\n        let raw_settings = vec![\n            (SETTINGS_MAX_FIELD_SECTION_SIZE, 0),\n            (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),\n            (SETTINGS_QPACK_BLOCKED_STREAMS, 0),\n        ];\n\n        let settings = Frame::Settings {\n            max_field_section_size: Some(0),\n            qpack_max_table_capacity: Some(0),\n            qpack_blocked_streams: Some(0),\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            additional_settings: None,\n            raw: Some(raw_settings),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap();\n        goaway.to_bytes(&mut b).unwrap();\n        settings.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor)\n            .unwrap();\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse GOAWAY.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(stream.set_frame_type(frame_ty), Err(Error::MissingSettings));\n    }\n\n    #[test]\n    /// Process not-allowed frame on control stream.\n    fn control_bad_frame() {\n        let mut d = vec![42; 40];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let header_block = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let hdrs = Frame::Headers { header_block };\n\n        let raw_settings = vec![\n            (SETTINGS_MAX_FIELD_SECTION_SIZE, 0),\n            (SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0),\n            (SETTINGS_QPACK_BLOCKED_STREAMS, 0),\n            (33, 33),\n        ];\n\n        let settings = Frame::Settings {\n            max_field_section_size: Some(0),\n            qpack_max_table_capacity: Some(0),\n            qpack_blocked_streams: Some(0),\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            additional_settings: None,\n            raw: Some(raw_settings),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap();\n        settings.to_bytes(&mut b).unwrap();\n        hdrs.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor)\n            .unwrap();\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse first SETTINGS frame.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        stream.set_frame_type(frame_ty).unwrap();\n\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert!(stream.try_consume_frame().is_ok());\n\n        // Parse HEADERS.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(stream.set_frame_type(frame_ty), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    fn request_no_data() {\n        let mut stream = <Stream>::new(0, false);\n\n        assert_eq!(stream.ty, Some(Type::Request));\n        assert_eq!(stream.state, State::FrameType);\n\n        assert_eq!(stream.try_consume_varint(), Err(Error::Done));\n    }\n\n    #[test]\n    fn request_good() {\n        let mut stream = <Stream>::new(0, false);\n\n        let mut d = vec![42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let header_block = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let payload = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let hdrs = Frame::Headers { header_block };\n        let data = Frame::Data {\n            payload: payload.clone(),\n        };\n\n        hdrs.to_bytes(&mut b).unwrap();\n        data.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        // Parse the HEADERS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, HEADERS_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the HEADERS frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 12);\n\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the HEADERS frame.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert_eq!(stream.try_consume_frame(), Ok((hdrs, 12)));\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the DATA frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, DATA_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the DATA frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 12);\n\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::Data);\n\n        // Parse the DATA payload.\n        let mut recv_buf = vec![0; payload.len()];\n        assert_eq!(\n            stream.try_consume_data_for_tests(&mut cursor, &mut recv_buf),\n            Ok(payload.len())\n        );\n        assert_eq!(payload, recv_buf);\n\n        assert_eq!(stream.state, State::FrameType);\n    }\n\n    #[test]\n    fn push_good() {\n        let mut d = vec![42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let header_block = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let payload = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let hdrs = Frame::Headers { header_block };\n        let data = Frame::Data {\n            payload: payload.clone(),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_PUSH_STREAM_TYPE_ID).unwrap();\n        b.put_varint(1).unwrap();\n        hdrs.to_bytes(&mut b).unwrap();\n        data.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_PUSH_STREAM_TYPE_ID, &mut cursor).unwrap();\n        assert_eq!(stream.state, State::PushId);\n\n        // Parse push ID.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let push_id = stream.try_consume_varint().unwrap();\n        assert_eq!(push_id, 1);\n\n        stream.set_push_id(push_id).unwrap();\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the HEADERS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, HEADERS_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the HEADERS frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 12);\n\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the HEADERS frame.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert_eq!(stream.try_consume_frame(), Ok((hdrs, 12)));\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the DATA frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, DATA_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the DATA frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 12);\n\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::Data);\n\n        // Parse the DATA payload.\n        let mut recv_buf = vec![0; payload.len()];\n        assert_eq!(\n            stream.try_consume_data_for_tests(&mut cursor, &mut recv_buf),\n            Ok(payload.len())\n        );\n        assert_eq!(payload, recv_buf);\n\n        assert_eq!(stream.state, State::FrameType);\n    }\n\n    #[test]\n    fn grease() {\n        let mut d = vec![42; 20];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let mut stream = open_uni(&mut b, 33).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        // Parse stream type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let stream_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(stream_ty, 33);\n        stream\n            .set_ty(Type::deserialize(stream_ty).unwrap())\n            .unwrap();\n        assert_eq!(stream.state, State::Drain);\n    }\n\n    #[test]\n    fn data_before_headers() {\n        let mut stream = <Stream>::new(0, false);\n\n        let mut d = vec![42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let data = Frame::Data {\n            payload: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],\n        };\n\n        data.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        // Parse the DATA frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, DATA_FRAME_TYPE_ID);\n\n        assert_eq!(stream.set_frame_type(frame_ty), Err(Error::FrameUnexpected));\n    }\n\n    #[test]\n    fn additional_headers() {\n        let mut stream = Stream::new(0, false);\n\n        let mut d = vec![42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let header_block = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let payload = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];\n        let info_hdrs = Frame::Headers {\n            header_block: header_block.clone(),\n        };\n        let non_info_hdrs = Frame::Headers {\n            header_block: header_block.clone(),\n        };\n        let trailers = Frame::Headers { header_block };\n        let data = Frame::Data {\n            payload: payload.clone(),\n        };\n\n        info_hdrs.to_bytes(&mut b).unwrap();\n        non_info_hdrs.to_bytes(&mut b).unwrap();\n        data.to_bytes(&mut b).unwrap();\n        trailers.to_bytes(&mut b).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        // Parse the HEADERS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, HEADERS_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the HEADERS frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 12);\n\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the HEADERS frame.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert_eq!(stream.try_consume_frame(), Ok((info_hdrs, 12)));\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the non-info HEADERS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, HEADERS_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the HEADERS frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 12);\n\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the HEADERS frame.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert_eq!(stream.try_consume_frame(), Ok((non_info_hdrs, 12)));\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the DATA frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, DATA_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the DATA frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 12);\n\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::Data);\n\n        // Parse the DATA payload.\n        let mut recv_buf = vec![0; payload.len()];\n        assert_eq!(\n            stream.try_consume_data_for_tests(&mut cursor, &mut recv_buf),\n            Ok(payload.len())\n        );\n        assert_eq!(payload, recv_buf);\n\n        assert_eq!(stream.state, State::FrameType);\n\n        // Parse the trailing HEADERS frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, HEADERS_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse the HEADERS frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_payload_len, 12);\n\n        stream.set_frame_payload_len(frame_payload_len).unwrap();\n        assert_eq!(stream.state, State::FramePayload);\n\n        // Parse the HEADERS frame.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n\n        assert_eq!(stream.try_consume_frame(), Ok((trailers, 12)));\n        assert_eq!(stream.state, State::FrameType);\n    }\n\n    #[test]\n    fn zero_length_goaway() {\n        let mut d = vec![42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            additional_settings: None,\n            raw: Some(vec![]),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap();\n        frame.to_bytes(&mut b).unwrap();\n\n        // Write a 0-length payload frame.\n        b.put_varint(GOAWAY_FRAME_TYPE_ID).unwrap();\n        b.put_varint(0).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor)\n            .unwrap();\n\n        // Skip SETTINGS frame type.\n        parse_skip_frame(&mut stream, &mut cursor).unwrap();\n\n        // Parse frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, GOAWAY_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(\n            Err(Error::FrameError),\n            stream.set_frame_payload_len(frame_payload_len)\n        );\n    }\n\n    #[test]\n    fn zero_length_push_promise() {\n        let mut d = vec![42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let mut stream = <Stream>::new(0, false);\n\n        assert_eq!(stream.ty, Some(Type::Request));\n        assert_eq!(stream.state, State::FrameType);\n\n        // Write a 0-length payload frame.\n        b.put_varint(PUSH_PROMISE_FRAME_TYPE_ID).unwrap();\n        b.put_varint(0).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        // Parse frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, PUSH_PROMISE_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(\n            Err(Error::FrameError),\n            stream.set_frame_payload_len(frame_payload_len)\n        );\n    }\n\n    #[test]\n    fn zero_length_cancel_push() {\n        let mut d = vec![42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            additional_settings: None,\n            raw: Some(vec![]),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap();\n        frame.to_bytes(&mut b).unwrap();\n\n        // Write a 0-length payload frame.\n        b.put_varint(CANCEL_PUSH_FRAME_TYPE_ID).unwrap();\n        b.put_varint(0).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor)\n            .unwrap();\n\n        // Skip SETTINGS frame type.\n        parse_skip_frame(&mut stream, &mut cursor).unwrap();\n\n        // Parse frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, CANCEL_PUSH_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(\n            Err(Error::FrameError),\n            stream.set_frame_payload_len(frame_payload_len)\n        );\n    }\n\n    #[test]\n    fn zero_length_max_push_id() {\n        let mut d = vec![42; 128];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n\n        let frame = Frame::Settings {\n            max_field_section_size: None,\n            qpack_max_table_capacity: None,\n            qpack_blocked_streams: None,\n            connect_protocol_enabled: None,\n            h3_datagram: None,\n            grease: None,\n            additional_settings: None,\n            raw: Some(vec![]),\n        };\n\n        let mut stream = open_uni(&mut b, HTTP3_CONTROL_STREAM_TYPE_ID).unwrap();\n        frame.to_bytes(&mut b).unwrap();\n\n        // Write a 0-length payload frame.\n        b.put_varint(MAX_PUSH_FRAME_TYPE_ID).unwrap();\n        b.put_varint(0).unwrap();\n\n        let mut cursor = std::io::Cursor::new(d);\n\n        parse_uni(&mut stream, HTTP3_CONTROL_STREAM_TYPE_ID, &mut cursor)\n            .unwrap();\n\n        // Skip SETTINGS frame type.\n        parse_skip_frame(&mut stream, &mut cursor).unwrap();\n\n        // Parse frame type.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n        let frame_ty = stream.try_consume_varint().unwrap();\n        assert_eq!(frame_ty, MAX_PUSH_FRAME_TYPE_ID);\n\n        stream.set_frame_type(frame_ty).unwrap();\n        assert_eq!(stream.state, State::FramePayloadLen);\n\n        // Parse frame payload length.\n        stream.try_fill_buffer_for_tests(&mut cursor).unwrap();\n        let frame_payload_len = stream.try_consume_varint().unwrap();\n        assert_eq!(\n            Err(Error::FrameError),\n            stream.set_frame_payload_len(frame_payload_len)\n        );\n    }\n}\n"
  },
  {
    "path": "quiche/src/lib.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! 🥧 Savoury implementation of the QUIC transport protocol and HTTP/3.\n//!\n//! [quiche] is an implementation of the QUIC transport protocol and HTTP/3 as\n//! specified by the [IETF]. It provides a low level API for processing QUIC\n//! packets and handling connection state. The application is responsible for\n//! providing I/O (e.g. sockets handling) as well as an event loop with support\n//! for timers.\n//!\n//! [quiche]: https://github.com/cloudflare/quiche/\n//! [ietf]: https://quicwg.org/\n//!\n//! ## Configuring connections\n//!\n//! The first step in establishing a QUIC connection using quiche is creating a\n//! [`Config`] object:\n//!\n//! ```\n//! let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! config.set_application_protos(&[b\"example-proto\"]);\n//!\n//! // Additional configuration specific to application and use case...\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! The [`Config`] object controls important aspects of the QUIC connection such\n//! as QUIC version, ALPN IDs, flow control, congestion control, idle timeout\n//! and other properties or features.\n//!\n//! QUIC is a general-purpose transport protocol and there are several\n//! configuration properties where there is no reasonable default value. For\n//! example, the permitted number of concurrent streams of any particular type\n//! is dependent on the application running over QUIC, and other use-case\n//! specific concerns.\n//!\n//! quiche defaults several properties to zero, applications most likely need\n//! to set these to something else to satisfy their needs using the following:\n//!\n//! - [`set_initial_max_streams_bidi()`]\n//! - [`set_initial_max_streams_uni()`]\n//! - [`set_initial_max_data()`]\n//! - [`set_initial_max_stream_data_bidi_local()`]\n//! - [`set_initial_max_stream_data_bidi_remote()`]\n//! - [`set_initial_max_stream_data_uni()`]\n//!\n//! [`Config`] also holds TLS configuration. This can be changed by mutators on\n//! the an existing object, or by constructing a TLS context manually and\n//! creating a configuration using [`with_boring_ssl_ctx_builder()`].\n//!\n//! A configuration object can be shared among multiple connections.\n//!\n//! ### Connection setup\n//!\n//! On the client-side the [`connect()`] utility function can be used to create\n//! a new connection, while [`accept()`] is for servers:\n//!\n//! ```\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! # let server_name = \"quic.tech\";\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! // Client connection.\n//! let conn =\n//!     quiche::connect(Some(&server_name), &scid, local, peer, &mut config)?;\n//!\n//! // Server connection.\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! let conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! In both cases, the application is responsible for generating a new source\n//! connection ID that will be used to identify the new connection.\n//!\n//! The application also need to pass the address of the remote peer of the\n//! connection: in the case of a client that would be the address of the server\n//! it is trying to connect to, and for a server that is the address of the\n//! client that initiated the connection.\n//!\n//! ## Handling incoming packets\n//!\n//! Using the connection's [`recv()`] method the application can process\n//! incoming packets that belong to that connection from the network:\n//!\n//! ```no_run\n//! # let mut buf = [0; 512];\n//! # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n//! let to = socket.local_addr().unwrap();\n//!\n//! loop {\n//!     let (read, from) = socket.recv_from(&mut buf).unwrap();\n//!\n//!     let recv_info = quiche::RecvInfo { from, to };\n//!\n//!     let read = match conn.recv(&mut buf[..read], recv_info) {\n//!         Ok(v) => v,\n//!\n//!         Err(quiche::Error::Done) => {\n//!             // Done reading.\n//!             break;\n//!         },\n//!\n//!         Err(e) => {\n//!             // An error occurred, handle it.\n//!             break;\n//!         },\n//!     };\n//! }\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! The application has to pass a [`RecvInfo`] structure in order to provide\n//! additional information about the received packet (such as the address it\n//! was received from).\n//!\n//! ## Generating outgoing packets\n//!\n//! Outgoing packet are generated using the connection's [`send()`] method\n//! instead:\n//!\n//! ```no_run\n//! # let mut out = [0; 512];\n//! # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n//! loop {\n//!     let (write, send_info) = match conn.send(&mut out) {\n//!         Ok(v) => v,\n//!\n//!         Err(quiche::Error::Done) => {\n//!             // Done writing.\n//!             break;\n//!         },\n//!\n//!         Err(e) => {\n//!             // An error occurred, handle it.\n//!             break;\n//!         },\n//!     };\n//!\n//!     socket.send_to(&out[..write], &send_info.to).unwrap();\n//! }\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! The application will be provided with a [`SendInfo`] structure providing\n//! additional information about the newly created packet (such as the address\n//! the packet should be sent to).\n//!\n//! When packets are sent, the application is responsible for maintaining a\n//! timer to react to time-based connection events. The timer expiration can be\n//! obtained using the connection's [`timeout()`] method.\n//!\n//! ```\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n//! let timeout = conn.timeout();\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! The application is responsible for providing a timer implementation, which\n//! can be specific to the operating system or networking framework used. When\n//! a timer expires, the connection's [`on_timeout()`] method should be called,\n//! after which additional packets might need to be sent on the network:\n//!\n//! ```no_run\n//! # let mut out = [0; 512];\n//! # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n//! // Timeout expired, handle it.\n//! conn.on_timeout();\n//!\n//! // Send more packets as needed after timeout.\n//! loop {\n//!     let (write, send_info) = match conn.send(&mut out) {\n//!         Ok(v) => v,\n//!\n//!         Err(quiche::Error::Done) => {\n//!             // Done writing.\n//!             break;\n//!         },\n//!\n//!         Err(e) => {\n//!             // An error occurred, handle it.\n//!             break;\n//!         },\n//!     };\n//!\n//!     socket.send_to(&out[..write], &send_info.to).unwrap();\n//! }\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! ### Pacing\n//!\n//! It is recommended that applications [pace] sending of outgoing packets to\n//! avoid creating packet bursts that could cause short-term congestion and\n//! losses in the network.\n//!\n//! quiche exposes pacing hints for outgoing packets through the [`at`] field\n//! of the [`SendInfo`] structure that is returned by the [`send()`] method.\n//! This field represents the time when a specific packet should be sent into\n//! the network.\n//!\n//! Applications can use these hints by artificially delaying the sending of\n//! packets through platform-specific mechanisms (such as the [`SO_TXTIME`]\n//! socket option on Linux), or custom methods (for example by using user-space\n//! timers).\n//!\n//! [pace]: https://datatracker.ietf.org/doc/html/rfc9002#section-7.7\n//! [`SO_TXTIME`]: https://man7.org/linux/man-pages/man8/tc-etf.8.html\n//!\n//! ## Sending and receiving stream data\n//!\n//! After some back and forth, the connection will complete its handshake and\n//! will be ready for sending or receiving application data.\n//!\n//! Data can be sent on a stream by using the [`stream_send()`] method:\n//!\n//! ```no_run\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n//! if conn.is_established() {\n//!     // Handshake completed, send some data on stream 0.\n//!     conn.stream_send(0, b\"hello\", true)?;\n//! }\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! The application can check whether there are any readable streams by using\n//! the connection's [`readable()`] method, which returns an iterator over all\n//! the streams that have outstanding data to read.\n//!\n//! The [`stream_recv()`] method can then be used to retrieve the application\n//! data from the readable stream:\n//!\n//! ```no_run\n//! # let mut buf = [0; 512];\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n//! # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n//! # let peer = \"127.0.0.1:1234\".parse().unwrap();\n//! # let local = \"127.0.0.1:4321\".parse().unwrap();\n//! # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n//! if conn.is_established() {\n//!     // Iterate over readable streams.\n//!     for stream_id in conn.readable() {\n//!         // Stream is readable, read until there's no more data.\n//!         while let Ok((read, fin)) = conn.stream_recv(stream_id, &mut buf) {\n//!             println!(\"Got {} bytes on stream {}\", read, stream_id);\n//!         }\n//!     }\n//! }\n//! # Ok::<(), quiche::Error>(())\n//! ```\n//!\n//! ## HTTP/3\n//!\n//! The quiche [HTTP/3 module] provides a high level API for sending and\n//! receiving HTTP requests and responses on top of the QUIC transport protocol.\n//!\n//! [`Config`]: https://docs.quic.tech/quiche/struct.Config.html\n//! [`set_initial_max_streams_bidi()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi\n//! [`set_initial_max_streams_uni()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_uni\n//! [`set_initial_max_data()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data\n//! [`set_initial_max_stream_data_bidi_local()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_local\n//! [`set_initial_max_stream_data_bidi_remote()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_bidi_remote\n//! [`set_initial_max_stream_data_uni()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_stream_data_uni\n//! [`with_boring_ssl_ctx_builder()`]: https://docs.quic.tech/quiche/struct.Config.html#method.with_boring_ssl_ctx_builder\n//! [`connect()`]: fn.connect.html\n//! [`accept()`]: fn.accept.html\n//! [`recv()`]: struct.Connection.html#method.recv\n//! [`RecvInfo`]: struct.RecvInfo.html\n//! [`send()`]: struct.Connection.html#method.send\n//! [`SendInfo`]: struct.SendInfo.html\n//! [`at`]: struct.SendInfo.html#structfield.at\n//! [`timeout()`]: struct.Connection.html#method.timeout\n//! [`on_timeout()`]: struct.Connection.html#method.on_timeout\n//! [`stream_send()`]: struct.Connection.html#method.stream_send\n//! [`readable()`]: struct.Connection.html#method.readable\n//! [`stream_recv()`]: struct.Connection.html#method.stream_recv\n//! [HTTP/3 module]: h3/index.html\n//!\n//! ## Congestion Control\n//!\n//! The quiche library provides a high-level API for configuring which\n//! congestion control algorithm to use throughout the QUIC connection.\n//!\n//! When a QUIC connection is created, the application can optionally choose\n//! which CC algorithm to use. See [`CongestionControlAlgorithm`] for currently\n//! available congestion control algorithms.\n//!\n//! For example:\n//!\n//! ```\n//! let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! config.set_cc_algorithm(quiche::CongestionControlAlgorithm::Reno);\n//! ```\n//!\n//! Alternatively, you can configure the congestion control algorithm to use\n//! by its name.\n//!\n//! ```\n//! let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! config.set_cc_algorithm_name(\"reno\").unwrap();\n//! ```\n//!\n//! Note that the CC algorithm should be configured before calling [`connect()`]\n//! or [`accept()`]. Otherwise the connection will use a default CC algorithm.\n//!\n//! [`CongestionControlAlgorithm`]: enum.CongestionControlAlgorithm.html\n//!\n//! ## Feature flags\n//!\n//! quiche defines a number of [feature flags] to reduce the amount of compiled\n//! code and dependencies:\n//!\n//! * `boringssl-vendored` (default): Build the vendored BoringSSL library.\n//!\n//! * `boringssl-boring-crate`: Use the BoringSSL library provided by the\n//!   [boring] crate. It takes precedence over `boringssl-vendored` if both\n//!   features are enabled.\n//!\n//! * `pkg-config-meta`: Generate pkg-config metadata file for libquiche.\n//!\n//! * `ffi`: Build and expose the FFI API.\n//!\n//! * `qlog`: Enable support for the [qlog] logging format.\n//!\n//! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section\n//! [boring]: https://crates.io/crates/boring\n//! [qlog]: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema\n\n#![allow(clippy::upper_case_acronyms)]\n#![warn(missing_docs)]\n#![warn(unused_qualifications)]\n#![cfg_attr(docsrs, feature(doc_cfg))]\n\n#[macro_use]\nextern crate log;\n\nuse std::cmp;\n\nuse std::collections::VecDeque;\n\nuse std::net::SocketAddr;\n\nuse std::str::FromStr;\n\nuse std::sync::Arc;\n\nuse std::time::Duration;\nuse std::time::Instant;\n\n#[cfg(feature = \"qlog\")]\nuse qlog::events::quic::DataMovedAdditionalInfo;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::quic::QuicEventType;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::quic::TransportInitiator;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::DataRecipient;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::Event;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventData;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventImportance;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventType;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::RawInfo;\n\nuse smallvec::SmallVec;\n\nuse crate::buffers::DefaultBufFactory;\n\nuse crate::recovery::OnAckReceivedOutcome;\nuse crate::recovery::OnLossDetectionTimeoutOutcome;\nuse crate::recovery::RecoveryOps;\nuse crate::recovery::ReleaseDecision;\n\nuse crate::stream::RecvAction;\nuse crate::stream::StreamPriorityKey;\n\n/// The current QUIC wire version.\npub const PROTOCOL_VERSION: u32 = PROTOCOL_VERSION_V1;\n\n/// Supported QUIC versions.\nconst PROTOCOL_VERSION_V1: u32 = 0x0000_0001;\n\n/// The maximum length of a connection ID.\npub const MAX_CONN_ID_LEN: usize = packet::MAX_CID_LEN as usize;\n\n/// The minimum length of Initial packets sent by a client.\npub const MIN_CLIENT_INITIAL_LEN: usize = 1200;\n\n/// The default initial RTT.\nconst DEFAULT_INITIAL_RTT: Duration = Duration::from_millis(333);\n\nconst PAYLOAD_MIN_LEN: usize = 4;\n\n// PATH_CHALLENGE (9 bytes) + AEAD tag (16 bytes).\nconst MIN_PROBING_SIZE: usize = 25;\n\nconst MAX_AMPLIFICATION_FACTOR: usize = 3;\n\n// The maximum number of tracked packet number ranges that need to be acked.\n//\n// This represents more or less how many ack blocks can fit in a typical packet.\nconst MAX_ACK_RANGES: usize = 68;\n\n// The highest possible stream ID allowed.\nconst MAX_STREAM_ID: u64 = 1 << 60;\n\n// The default max_datagram_size used in congestion control.\nconst MAX_SEND_UDP_PAYLOAD_SIZE: usize = 1200;\n\n// The default length of DATAGRAM queues.\nconst DEFAULT_MAX_DGRAM_QUEUE_LEN: usize = 0;\n\n// The default length of PATH_CHALLENGE receive queue.\nconst DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN: usize = 3;\n\n// The DATAGRAM standard recommends either none or 65536 as maximum DATAGRAM\n// frames size. We enforce the recommendation for forward compatibility.\nconst MAX_DGRAM_FRAME_SIZE: u64 = 65536;\n\n// The length of the payload length field.\nconst PAYLOAD_LENGTH_LEN: usize = 2;\n\n// The number of undecryptable that can be buffered.\nconst MAX_UNDECRYPTABLE_PACKETS: usize = 10;\n\nconst RESERVED_VERSION_MASK: u32 = 0xfafafafa;\n\n// The default size of the receiver connection flow control window.\nconst DEFAULT_CONNECTION_WINDOW: u64 = 48 * 1024;\n\n// The maximum size of the receiver connection flow control window.\nconst MAX_CONNECTION_WINDOW: u64 = 24 * 1024 * 1024;\n\n// How much larger the connection flow control window need to be larger than\n// the stream flow control window.\nconst CONNECTION_WINDOW_FACTOR: f64 = 1.5;\n\n// How many probing packet timeouts do we tolerate before considering the path\n// validation as failed.\nconst MAX_PROBING_TIMEOUTS: usize = 3;\n\n// The default initial congestion window size in terms of packet count.\nconst DEFAULT_INITIAL_CONGESTION_WINDOW_PACKETS: usize = 10;\n\n// The maximum data offset that can be stored in a crypto stream.\nconst MAX_CRYPTO_STREAM_OFFSET: u64 = 1 << 16;\n\n// The send capacity factor.\nconst TX_CAP_FACTOR: f64 = 1.0;\n\n/// Ancillary information about incoming packets.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct RecvInfo {\n    /// The remote address the packet was received from.\n    pub from: SocketAddr,\n\n    /// The local address the packet was received on.\n    pub to: SocketAddr,\n}\n\n/// Ancillary information about outgoing packets.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct SendInfo {\n    /// The local address the packet should be sent from.\n    pub from: SocketAddr,\n\n    /// The remote address the packet should be sent to.\n    pub to: SocketAddr,\n\n    /// The time to send the packet out.\n    ///\n    /// See [Pacing] for more details.\n    ///\n    /// [Pacing]: index.html#pacing\n    pub at: Instant,\n}\n\n/// The side of the stream to be shut down.\n///\n/// This should be used when calling [`stream_shutdown()`].\n///\n/// [`stream_shutdown()`]: struct.Connection.html#method.stream_shutdown\n#[repr(C)]\n#[derive(PartialEq, Eq)]\npub enum Shutdown {\n    /// Stop receiving stream data.\n    Read  = 0,\n\n    /// Stop sending stream data.\n    Write = 1,\n}\n\n/// Qlog logging level.\n#[repr(C)]\n#[cfg(feature = \"qlog\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"qlog\")))]\npub enum QlogLevel {\n    /// Logs any events of Core importance.\n    Core  = 0,\n\n    /// Logs any events of Core and Base importance.\n    Base  = 1,\n\n    /// Logs any events of Core, Base and Extra importance\n    Extra = 2,\n}\n\n/// Stores configuration shared between multiple connections.\npub struct Config {\n    local_transport_params: TransportParams,\n\n    version: u32,\n\n    tls_ctx: tls::Context,\n\n    application_protos: Vec<Vec<u8>>,\n\n    grease: bool,\n\n    cc_algorithm: CongestionControlAlgorithm,\n    custom_bbr_params: Option<BbrParams>,\n    initial_congestion_window_packets: usize,\n    enable_relaxed_loss_threshold: bool,\n    enable_cubic_idle_restart_fix: bool,\n    enable_send_streams_blocked: bool,\n\n    pmtud: bool,\n    pmtud_max_probes: u8,\n\n    hystart: bool,\n\n    pacing: bool,\n    /// Send rate limit in Mbps\n    max_pacing_rate: Option<u64>,\n\n    tx_cap_factor: f64,\n\n    dgram_recv_max_queue_len: usize,\n    dgram_send_max_queue_len: usize,\n\n    path_challenge_recv_max_queue_len: usize,\n\n    max_send_udp_payload_size: usize,\n\n    max_connection_window: u64,\n    max_stream_window: u64,\n\n    max_amplification_factor: usize,\n\n    disable_dcid_reuse: bool,\n\n    track_unknown_transport_params: Option<usize>,\n\n    initial_rtt: Duration,\n}\n\n// See https://quicwg.org/base-drafts/rfc9000.html#section-15\nfn is_reserved_version(version: u32) -> bool {\n    version & RESERVED_VERSION_MASK == version\n}\n\nimpl Config {\n    /// Creates a config object with the given version.\n    ///\n    /// ## Examples:\n    ///\n    /// ```\n    /// let config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn new(version: u32) -> Result<Config> {\n        Self::with_tls_ctx(version, tls::Context::new()?)\n    }\n\n    /// Creates a config object with the given version and\n    /// [`SslContextBuilder`].\n    ///\n    /// This is useful for applications that wish to manually configure\n    /// [`SslContextBuilder`].\n    ///\n    /// [`SslContextBuilder`]: https://docs.rs/boring/latest/boring/ssl/struct.SslContextBuilder.html\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn with_boring_ssl_ctx_builder(\n        version: u32, tls_ctx_builder: boring::ssl::SslContextBuilder,\n    ) -> Result<Config> {\n        Self::with_tls_ctx(version, tls::Context::from_boring(tls_ctx_builder))\n    }\n\n    fn with_tls_ctx(version: u32, tls_ctx: tls::Context) -> Result<Config> {\n        if !is_reserved_version(version) && !version_is_supported(version) {\n            return Err(Error::UnknownVersion);\n        }\n\n        Ok(Config {\n            local_transport_params: TransportParams::default(),\n            version,\n            tls_ctx,\n            application_protos: Vec::new(),\n            grease: true,\n            cc_algorithm: CongestionControlAlgorithm::CUBIC,\n            custom_bbr_params: None,\n            initial_congestion_window_packets:\n                DEFAULT_INITIAL_CONGESTION_WINDOW_PACKETS,\n            enable_relaxed_loss_threshold: false,\n            enable_cubic_idle_restart_fix: true,\n            enable_send_streams_blocked: false,\n            pmtud: false,\n            pmtud_max_probes: pmtud::MAX_PROBES_DEFAULT,\n            hystart: true,\n            pacing: true,\n            max_pacing_rate: None,\n\n            tx_cap_factor: TX_CAP_FACTOR,\n\n            dgram_recv_max_queue_len: DEFAULT_MAX_DGRAM_QUEUE_LEN,\n            dgram_send_max_queue_len: DEFAULT_MAX_DGRAM_QUEUE_LEN,\n\n            path_challenge_recv_max_queue_len:\n                DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN,\n\n            max_send_udp_payload_size: MAX_SEND_UDP_PAYLOAD_SIZE,\n\n            max_connection_window: MAX_CONNECTION_WINDOW,\n            max_stream_window: stream::MAX_STREAM_WINDOW,\n\n            max_amplification_factor: MAX_AMPLIFICATION_FACTOR,\n\n            disable_dcid_reuse: false,\n\n            track_unknown_transport_params: None,\n            initial_rtt: DEFAULT_INITIAL_RTT,\n        })\n    }\n\n    /// Configures the given certificate chain.\n    ///\n    /// The content of `file` is parsed as a PEM-encoded leaf certificate,\n    /// followed by optional intermediate certificates.\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut config = quiche::Config::new(0xbabababa)?;\n    /// config.load_cert_chain_from_pem_file(\"/path/to/cert.pem\")?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn load_cert_chain_from_pem_file(&mut self, file: &str) -> Result<()> {\n        self.tls_ctx.use_certificate_chain_file(file)\n    }\n\n    /// Configures the given private key.\n    ///\n    /// The content of `file` is parsed as a PEM-encoded private key.\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut config = quiche::Config::new(0xbabababa)?;\n    /// config.load_priv_key_from_pem_file(\"/path/to/key.pem\")?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn load_priv_key_from_pem_file(&mut self, file: &str) -> Result<()> {\n        self.tls_ctx.use_privkey_file(file)\n    }\n\n    /// Specifies a file where trusted CA certificates are stored for the\n    /// purposes of certificate verification.\n    ///\n    /// The content of `file` is parsed as a PEM-encoded certificate chain.\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut config = quiche::Config::new(0xbabababa)?;\n    /// config.load_verify_locations_from_file(\"/path/to/cert.pem\")?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn load_verify_locations_from_file(&mut self, file: &str) -> Result<()> {\n        self.tls_ctx.load_verify_locations_from_file(file)\n    }\n\n    /// Specifies a directory where trusted CA certificates are stored for the\n    /// purposes of certificate verification.\n    ///\n    /// The content of `dir` a set of PEM-encoded certificate chains.\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut config = quiche::Config::new(0xbabababa)?;\n    /// config.load_verify_locations_from_directory(\"/path/to/certs\")?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn load_verify_locations_from_directory(\n        &mut self, dir: &str,\n    ) -> Result<()> {\n        self.tls_ctx.load_verify_locations_from_directory(dir)\n    }\n\n    /// Configures whether to verify the peer's certificate.\n    ///\n    /// This should usually be `true` for client-side connections and `false`\n    /// for server-side ones.\n    ///\n    /// Note that by default, no verification is performed.\n    ///\n    /// Also note that on the server-side, enabling verification of the peer\n    /// will trigger a certificate request and make authentication errors\n    /// fatal, but will still allow anonymous clients (i.e. clients that\n    /// don't present a certificate at all). Servers can check whether a\n    /// client presented a certificate by calling [`peer_cert()`] if they\n    /// need to.\n    ///\n    /// [`peer_cert()`]: struct.Connection.html#method.peer_cert\n    pub fn verify_peer(&mut self, verify: bool) {\n        self.tls_ctx.set_verify(verify);\n    }\n\n    /// Configures whether to do path MTU discovery.\n    ///\n    /// The default value is `false`.\n    pub fn discover_pmtu(&mut self, discover: bool) {\n        self.pmtud = discover;\n    }\n\n    /// Configures the maximum number of PMTUD probe attempts before treating\n    /// a probe size as failed.\n    ///\n    /// Defaults to 3 per [RFC 8899 Section 5.1.2](https://datatracker.ietf.org/doc/html/rfc8899#section-5.1.2).\n    /// If 0 is passed, the default value is used.\n    pub fn set_pmtud_max_probes(&mut self, max_probes: u8) {\n        self.pmtud_max_probes = max_probes;\n    }\n\n    /// Configures whether to send GREASE values.\n    ///\n    /// The default value is `true`.\n    pub fn grease(&mut self, grease: bool) {\n        self.grease = grease;\n    }\n\n    /// Enables logging of secrets.\n    ///\n    /// When logging is enabled, the [`set_keylog()`] method must be called on\n    /// the connection for its cryptographic secrets to be logged in the\n    /// [keylog] format to the specified writer.\n    ///\n    /// [`set_keylog()`]: struct.Connection.html#method.set_keylog\n    /// [keylog]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format\n    pub fn log_keys(&mut self) {\n        self.tls_ctx.enable_keylog();\n    }\n\n    /// Configures the session ticket key material.\n    ///\n    /// On the server this key will be used to encrypt and decrypt session\n    /// tickets, used to perform session resumption without server-side state.\n    ///\n    /// By default a key is generated internally, and rotated regularly, so\n    /// applications don't need to call this unless they need to use a\n    /// specific key (e.g. in order to support resumption across multiple\n    /// servers), in which case the application is also responsible for\n    /// rotating the key to provide forward secrecy.\n    pub fn set_ticket_key(&mut self, key: &[u8]) -> Result<()> {\n        self.tls_ctx.set_ticket_key(key)\n    }\n\n    /// Enables sending or receiving early data.\n    pub fn enable_early_data(&mut self) {\n        self.tls_ctx.set_early_data_enabled(true);\n    }\n\n    /// Configures the list of supported application protocols.\n    ///\n    /// On the client this configures the list of protocols to send to the\n    /// server as part of the ALPN extension.\n    ///\n    /// On the server this configures the list of supported protocols to match\n    /// against the client-supplied list.\n    ///\n    /// Applications must set a value, but no default is provided.\n    ///\n    /// ## Examples:\n    ///\n    /// ```\n    /// # let mut config = quiche::Config::new(0xbabababa)?;\n    /// config.set_application_protos(&[b\"http/1.1\", b\"http/0.9\"]);\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn set_application_protos(\n        &mut self, protos_list: &[&[u8]],\n    ) -> Result<()> {\n        self.application_protos =\n            protos_list.iter().map(|s| s.to_vec()).collect();\n\n        self.tls_ctx.set_alpn(protos_list)\n    }\n\n    /// Configures the list of supported application protocols using wire\n    /// format.\n    ///\n    /// The list of protocols `protos` must be a series of non-empty, 8-bit\n    /// length-prefixed strings.\n    ///\n    /// See [`set_application_protos`](Self::set_application_protos) for more\n    /// background about application protocols.\n    ///\n    /// ## Examples:\n    ///\n    /// ```\n    /// # let mut config = quiche::Config::new(0xbabababa)?;\n    /// config.set_application_protos_wire_format(b\"\\x08http/1.1\\x08http/0.9\")?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn set_application_protos_wire_format(\n        &mut self, protos: &[u8],\n    ) -> Result<()> {\n        let mut b = octets::Octets::with_slice(protos);\n\n        let mut protos_list = Vec::new();\n\n        while let Ok(proto) = b.get_bytes_with_u8_length() {\n            protos_list.push(proto.buf());\n        }\n\n        self.set_application_protos(&protos_list)\n    }\n\n    /// Sets the anti-amplification limit factor.\n    ///\n    /// The default value is `3`.\n    pub fn set_max_amplification_factor(&mut self, v: usize) {\n        self.max_amplification_factor = v;\n    }\n\n    /// Sets the send capacity factor.\n    ///\n    /// The default value is `1`.\n    pub fn set_send_capacity_factor(&mut self, v: f64) {\n        self.tx_cap_factor = v;\n    }\n\n    /// Sets the connection's initial RTT.\n    ///\n    /// The default value is `333`.\n    pub fn set_initial_rtt(&mut self, v: Duration) {\n        self.initial_rtt = v;\n    }\n\n    /// Sets the `max_idle_timeout` transport parameter, in milliseconds.\n    ///\n    /// The default value is infinite, that is, no timeout is used.\n    pub fn set_max_idle_timeout(&mut self, v: u64) {\n        self.local_transport_params.max_idle_timeout =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `max_udp_payload_size transport` parameter.\n    ///\n    /// The default value is `65527`.\n    pub fn set_max_recv_udp_payload_size(&mut self, v: usize) {\n        self.local_transport_params.max_udp_payload_size =\n            cmp::min(v as u64, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the maximum outgoing UDP payload size.\n    ///\n    /// The default and minimum value is `1200`.\n    pub fn set_max_send_udp_payload_size(&mut self, v: usize) {\n        self.max_send_udp_payload_size = cmp::max(v, MAX_SEND_UDP_PAYLOAD_SIZE);\n    }\n\n    /// Sets the `initial_max_data` transport parameter.\n    ///\n    /// When set to a non-zero value quiche will only allow at most `v` bytes of\n    /// incoming stream data to be buffered for the whole connection (that is,\n    /// data that is not yet read by the application) and will allow more data\n    /// to be received as the buffer is consumed by the application.\n    ///\n    /// When set to zero, either explicitly or via the default, quiche will not\n    /// give any flow control to the peer, preventing it from sending any stream\n    /// data.\n    ///\n    /// The default value is `0`.\n    pub fn set_initial_max_data(&mut self, v: u64) {\n        self.local_transport_params.initial_max_data =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `initial_max_stream_data_bidi_local` transport parameter.\n    ///\n    /// When set to a non-zero value quiche will only allow at most `v` bytes\n    /// of incoming stream data to be buffered for each locally-initiated\n    /// bidirectional stream (that is, data that is not yet read by the\n    /// application) and will allow more data to be received as the buffer is\n    /// consumed by the application.\n    ///\n    /// When set to zero, either explicitly or via the default, quiche will not\n    /// give any flow control to the peer, preventing it from sending any stream\n    /// data.\n    ///\n    /// The default value is `0`.\n    pub fn set_initial_max_stream_data_bidi_local(&mut self, v: u64) {\n        self.local_transport_params\n            .initial_max_stream_data_bidi_local =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `initial_max_stream_data_bidi_remote` transport parameter.\n    ///\n    /// When set to a non-zero value quiche will only allow at most `v` bytes\n    /// of incoming stream data to be buffered for each remotely-initiated\n    /// bidirectional stream (that is, data that is not yet read by the\n    /// application) and will allow more data to be received as the buffer is\n    /// consumed by the application.\n    ///\n    /// When set to zero, either explicitly or via the default, quiche will not\n    /// give any flow control to the peer, preventing it from sending any stream\n    /// data.\n    ///\n    /// The default value is `0`.\n    pub fn set_initial_max_stream_data_bidi_remote(&mut self, v: u64) {\n        self.local_transport_params\n            .initial_max_stream_data_bidi_remote =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `initial_max_stream_data_uni` transport parameter.\n    ///\n    /// When set to a non-zero value quiche will only allow at most `v` bytes\n    /// of incoming stream data to be buffered for each unidirectional stream\n    /// (that is, data that is not yet read by the application) and will allow\n    /// more data to be received as the buffer is consumed by the application.\n    ///\n    /// When set to zero, either explicitly or via the default, quiche will not\n    /// give any flow control to the peer, preventing it from sending any stream\n    /// data.\n    ///\n    /// The default value is `0`.\n    pub fn set_initial_max_stream_data_uni(&mut self, v: u64) {\n        self.local_transport_params.initial_max_stream_data_uni =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `initial_max_streams_bidi` transport parameter.\n    ///\n    /// When set to a non-zero value quiche will only allow `v` number of\n    /// concurrent remotely-initiated bidirectional streams to be open at any\n    /// given time and will increase the limit automatically as streams are\n    /// completed.\n    ///\n    /// When set to zero, either explicitly or via the default, quiche will not\n    /// not allow the peer to open any bidirectional streams.\n    ///\n    /// A bidirectional stream is considered completed when all incoming data\n    /// has been read by the application (up to the `fin` offset) or the\n    /// stream's read direction has been shutdown, and all outgoing data has\n    /// been acked by the peer (up to the `fin` offset) or the stream's write\n    /// direction has been shutdown.\n    ///\n    /// The default value is `0`.\n    pub fn set_initial_max_streams_bidi(&mut self, v: u64) {\n        self.local_transport_params.initial_max_streams_bidi =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `initial_max_streams_uni` transport parameter.\n    ///\n    /// When set to a non-zero value quiche will only allow `v` number of\n    /// concurrent remotely-initiated unidirectional streams to be open at any\n    /// given time and will increase the limit automatically as streams are\n    /// completed.\n    ///\n    /// When set to zero, either explicitly or via the default, quiche will not\n    /// not allow the peer to open any unidirectional streams.\n    ///\n    /// A unidirectional stream is considered completed when all incoming data\n    /// has been read by the application (up to the `fin` offset) or the\n    /// stream's read direction has been shutdown.\n    ///\n    /// The default value is `0`.\n    pub fn set_initial_max_streams_uni(&mut self, v: u64) {\n        self.local_transport_params.initial_max_streams_uni =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `ack_delay_exponent` transport parameter.\n    ///\n    /// The default value is `3`.\n    pub fn set_ack_delay_exponent(&mut self, v: u64) {\n        self.local_transport_params.ack_delay_exponent =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `max_ack_delay` transport parameter.\n    ///\n    /// The default value is `25`.\n    pub fn set_max_ack_delay(&mut self, v: u64) {\n        self.local_transport_params.max_ack_delay =\n            cmp::min(v, octets::MAX_VAR_INT);\n    }\n\n    /// Sets the `active_connection_id_limit` transport parameter.\n    ///\n    /// The default value is `2`. Lower values will be ignored.\n    pub fn set_active_connection_id_limit(&mut self, v: u64) {\n        if v >= 2 {\n            self.local_transport_params.active_conn_id_limit =\n                cmp::min(v, octets::MAX_VAR_INT);\n        }\n    }\n\n    /// Sets the `disable_active_migration` transport parameter.\n    ///\n    /// The default value is `false`.\n    pub fn set_disable_active_migration(&mut self, v: bool) {\n        self.local_transport_params.disable_active_migration = v;\n    }\n\n    /// Sets the congestion control algorithm used.\n    ///\n    /// The default value is `CongestionControlAlgorithm::CUBIC`.\n    pub fn set_cc_algorithm(&mut self, algo: CongestionControlAlgorithm) {\n        self.cc_algorithm = algo;\n    }\n\n    /// Sets custom BBR settings.\n    ///\n    /// This API is experimental and will be removed in the future.\n    ///\n    /// Currently this only applies if cc_algorithm is\n    /// `CongestionControlAlgorithm::Bbr2Gcongestion` is set.\n    ///\n    /// The default value is `None`.\n    #[cfg(feature = \"internal\")]\n    #[doc(hidden)]\n    pub fn set_custom_bbr_params(&mut self, custom_bbr_settings: BbrParams) {\n        self.custom_bbr_params = Some(custom_bbr_settings);\n    }\n\n    /// Sets the congestion control algorithm used by string.\n    ///\n    /// The default value is `cubic`. On error `Error::CongestionControl`\n    /// will be returned.\n    ///\n    /// ## Examples:\n    ///\n    /// ```\n    /// # let mut config = quiche::Config::new(0xbabababa)?;\n    /// config.set_cc_algorithm_name(\"reno\");\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn set_cc_algorithm_name(&mut self, name: &str) -> Result<()> {\n        self.cc_algorithm = CongestionControlAlgorithm::from_str(name)?;\n\n        Ok(())\n    }\n\n    /// Sets initial congestion window size in terms of packet count.\n    ///\n    /// The default value is 10.\n    pub fn set_initial_congestion_window_packets(&mut self, packets: usize) {\n        self.initial_congestion_window_packets = packets;\n    }\n\n    /// Configure whether to enable relaxed loss detection on spurious loss.\n    ///\n    /// The default value is false.\n    pub fn set_enable_relaxed_loss_threshold(&mut self, enable: bool) {\n        self.enable_relaxed_loss_threshold = enable;\n    }\n\n    /// Configure whether to enable the CUBIC idle restart fix.\n    ///\n    /// When enabled, the epoch shift on idle restart uses the later of\n    /// the last ACK time and last send time, avoiding an inflated delta\n    /// when bytes-in-flight transiently hits zero.\n    ///\n    /// The default value is `true`.\n    pub fn set_enable_cubic_idle_restart_fix(&mut self, enable: bool) {\n        self.enable_cubic_idle_restart_fix = enable;\n    }\n\n    /// Configure whether to enable sending STREAMS_BLOCKED frames.\n    ///\n    /// STREAMS_BLOCKED frames are an optional advisory signal in the QUIC\n    /// protocol which SHOULD be sent when the sender wishes to open a stream\n    /// but is unable to do so due to the maximum stream limit set by its peer.\n    ///\n    /// The default value is false.\n    pub fn set_enable_send_streams_blocked(&mut self, enable: bool) {\n        self.enable_send_streams_blocked = enable;\n    }\n\n    /// Configures whether to enable HyStart++.\n    ///\n    /// The default value is `true`.\n    pub fn enable_hystart(&mut self, v: bool) {\n        self.hystart = v;\n    }\n\n    /// Configures whether to enable pacing.\n    ///\n    /// The default value is `true`.\n    pub fn enable_pacing(&mut self, v: bool) {\n        self.pacing = v;\n    }\n\n    /// Sets the max value for pacing rate.\n    ///\n    /// By default pacing rate is not limited.\n    pub fn set_max_pacing_rate(&mut self, v: u64) {\n        self.max_pacing_rate = Some(v);\n    }\n\n    /// Configures whether to enable receiving DATAGRAM frames.\n    ///\n    /// When enabled, the `max_datagram_frame_size` transport parameter is set\n    /// to 65536 as recommended by draft-ietf-quic-datagram-01.\n    ///\n    /// The default is `false`.\n    pub fn enable_dgram(\n        &mut self, enabled: bool, recv_queue_len: usize, send_queue_len: usize,\n    ) {\n        self.local_transport_params.max_datagram_frame_size = if enabled {\n            Some(MAX_DGRAM_FRAME_SIZE)\n        } else {\n            None\n        };\n        self.dgram_recv_max_queue_len = recv_queue_len;\n        self.dgram_send_max_queue_len = send_queue_len;\n    }\n\n    /// Configures the max number of queued received PATH_CHALLENGE frames.\n    ///\n    /// When an endpoint receives a PATH_CHALLENGE frame and the queue is full,\n    /// the frame is discarded.\n    ///\n    /// The default is 3.\n    pub fn set_path_challenge_recv_max_queue_len(&mut self, queue_len: usize) {\n        self.path_challenge_recv_max_queue_len = queue_len;\n    }\n\n    /// Sets the maximum size of the connection window.\n    ///\n    /// The default value is MAX_CONNECTION_WINDOW (24MBytes).\n    pub fn set_max_connection_window(&mut self, v: u64) {\n        self.max_connection_window = v;\n    }\n\n    /// Sets the maximum size of the stream window.\n    ///\n    /// The default value is MAX_STREAM_WINDOW (16MBytes).\n    pub fn set_max_stream_window(&mut self, v: u64) {\n        self.max_stream_window = v;\n    }\n\n    /// Sets the initial stateless reset token.\n    ///\n    /// This value is only advertised by servers. Setting a stateless retry\n    /// token as a client has no effect on the connection.\n    ///\n    /// The default value is `None`.\n    pub fn set_stateless_reset_token(&mut self, v: Option<u128>) {\n        self.local_transport_params.stateless_reset_token = v;\n    }\n\n    /// Sets whether the QUIC connection should avoid reusing DCIDs over\n    /// different paths.\n    ///\n    /// When set to `true`, it ensures that a destination Connection ID is never\n    /// reused on different paths. Such behaviour may lead to connection stall\n    /// if the peer performs a non-voluntary migration (e.g., NAT rebinding) and\n    /// does not provide additional destination Connection IDs to handle such\n    /// event.\n    ///\n    /// The default value is `false`.\n    pub fn set_disable_dcid_reuse(&mut self, v: bool) {\n        self.disable_dcid_reuse = v;\n    }\n\n    /// Enables tracking unknown transport parameters.\n    ///\n    /// Specify the maximum number of bytes used to track unknown transport\n    /// parameters. The size includes the identifier and its value. If storing a\n    /// transport parameter would cause the limit to be exceeded, it is quietly\n    /// dropped.\n    ///\n    /// The default is that the feature is disabled.\n    pub fn enable_track_unknown_transport_parameters(&mut self, size: usize) {\n        self.track_unknown_transport_params = Some(size);\n    }\n}\n\n/// Tracks the health of the tx_buffered value.\n#[derive(Clone, Copy, Debug, Default, PartialEq)]\npub enum TxBufferTrackingState {\n    /// The send buffer is in a good state\n    #[default]\n    Ok,\n    /// The send buffer is in an inconsistent state, which could lead to\n    /// connection stalls or excess buffering due to bugs we haven't\n    /// tracked down yet.\n    Inconsistent,\n}\n\n/// Tracks if the connection hit the peer stream limit and which\n/// STREAMS_BLOCKED frames have been sent.\n#[derive(Default)]\nstruct StreamsBlockedState {\n    /// The peer's max_streams limit at which we last became blocked on\n    /// opening new local streams, if any.\n    blocked_at: Option<u64>,\n\n    /// The stream limit sent on the most recently sent STREAMS_BLOCKED\n    /// frame. If != to blocked_at, the connection has pending STREAMS_BLOCKED\n    /// frames to send.\n    blocked_sent: Option<u64>,\n}\n\nimpl StreamsBlockedState {\n    /// Returns true if there is a STREAMS_BLOCKED frame that needs sending.\n    fn has_pending_stream_blocked_frame(&self) -> bool {\n        self.blocked_sent < self.blocked_at\n    }\n\n    /// Update the stream blocked limit.\n    fn update_at(&mut self, limit: u64) {\n        self.blocked_at = self.blocked_at.max(Some(limit));\n    }\n\n    /// Clear blocked_sent to force retransmission of the most recently sent\n    /// STREAMS_BLOCKED frame.\n    fn force_retransmit_sent_limit_eq(&mut self, limit: u64) {\n        // Only clear blocked_sent if the lost frame had the most recently sent\n        // limit.\n        if self.blocked_sent == Some(limit) {\n            self.blocked_sent = None;\n        }\n    }\n}\n\n/// A QUIC connection.\npub struct Connection<F = DefaultBufFactory>\nwhere\n    F: BufFactory,\n{\n    /// QUIC wire version used for the connection.\n    version: u32,\n\n    /// Connection Identifiers.\n    ids: cid::ConnectionIdentifiers,\n\n    /// Unique opaque ID for the connection that can be used for logging.\n    trace_id: String,\n\n    /// Packet number spaces.\n    pkt_num_spaces: [packet::PktNumSpace; packet::Epoch::count()],\n\n    /// The crypto context.\n    crypto_ctx: [packet::CryptoContext; packet::Epoch::count()],\n\n    /// Next packet number.\n    next_pkt_num: u64,\n\n    // TODO\n    // combine with `next_pkt_num`\n    /// Track the packet skip context\n    pkt_num_manager: packet::PktNumManager,\n\n    /// Peer's transport parameters.\n    peer_transport_params: TransportParams,\n\n    /// If tracking unknown transport parameters from a peer, how much space to\n    /// use in bytes.\n    peer_transport_params_track_unknown: Option<usize>,\n\n    /// Local transport parameters.\n    local_transport_params: TransportParams,\n\n    /// TLS handshake state.\n    handshake: tls::Handshake,\n\n    /// Serialized TLS session buffer.\n    ///\n    /// This field is populated when a new session ticket is processed on the\n    /// client. On the server this is empty.\n    session: Option<Vec<u8>>,\n\n    /// The configuration for recovery.\n    recovery_config: recovery::RecoveryConfig,\n\n    /// The path manager.\n    paths: path::PathMap,\n\n    /// PATH_CHALLENGE receive queue max length.\n    path_challenge_recv_max_queue_len: usize,\n\n    /// Total number of received PATH_CHALLENGE frames.\n    path_challenge_rx_count: u64,\n\n    /// List of supported application protocols.\n    application_protos: Vec<Vec<u8>>,\n\n    /// Total number of received packets.\n    recv_count: usize,\n\n    /// Total number of sent packets.\n    sent_count: usize,\n\n    /// Total number of lost packets.\n    lost_count: usize,\n\n    /// Total number of lost packets that were later acked.\n    spurious_lost_count: usize,\n\n    /// Total number of packets sent with data retransmitted.\n    retrans_count: usize,\n\n    /// Total number of sent DATAGRAM frames.\n    dgram_sent_count: usize,\n\n    /// Total number of received DATAGRAM frames.\n    dgram_recv_count: usize,\n\n    /// Total number of bytes received from the peer.\n    rx_data: u64,\n\n    /// Receiver flow controller.\n    flow_control: flowcontrol::FlowControl,\n\n    /// Whether we send MAX_DATA frame.\n    should_send_max_data: bool,\n\n    /// True if there is a pending MAX_STREAMS_BIDI frame to send.\n    should_send_max_streams_bidi: bool,\n\n    /// True if there is a pending MAX_STREAMS_UNI frame to send.\n    should_send_max_streams_uni: bool,\n\n    /// Number of stream data bytes that can be buffered.\n    tx_cap: usize,\n\n    /// The send capacity factor.\n    tx_cap_factor: f64,\n\n    /// Number of bytes buffered in the send buffer.\n    tx_buffered: usize,\n\n    /// Tracks the health of tx_buffered.\n    tx_buffered_state: TxBufferTrackingState,\n\n    /// Total number of bytes sent to the peer.\n    tx_data: u64,\n\n    /// Peer's flow control limit for the connection.\n    max_tx_data: u64,\n\n    /// Last tx_data before running a full send() loop.\n    last_tx_data: u64,\n\n    /// Total number of bytes retransmitted over the connection.\n    /// This counts only STREAM and CRYPTO data.\n    stream_retrans_bytes: u64,\n\n    /// Total number of bytes sent over the connection.\n    sent_bytes: u64,\n\n    /// Total number of bytes received over the connection.\n    recv_bytes: u64,\n\n    /// Total number of bytes sent acked over the connection.\n    acked_bytes: u64,\n\n    /// Total number of bytes sent lost over the connection.\n    lost_bytes: u64,\n\n    /// Streams map, indexed by stream ID.\n    streams: stream::StreamMap<F>,\n\n    /// Peer's original destination connection ID. Used by the client to\n    /// validate the server's transport parameter.\n    odcid: Option<ConnectionId<'static>>,\n\n    /// Peer's retry source connection ID. Used by the client during stateless\n    /// retry to validate the server's transport parameter.\n    rscid: Option<ConnectionId<'static>>,\n\n    /// Received address verification token.\n    token: Option<Vec<u8>>,\n\n    /// Error code and reason to be sent to the peer in a CONNECTION_CLOSE\n    /// frame.\n    local_error: Option<ConnectionError>,\n\n    /// Error code and reason received from the peer in a CONNECTION_CLOSE\n    /// frame.\n    peer_error: Option<ConnectionError>,\n\n    /// The connection-level limit at which send blocking occurred.\n    blocked_limit: Option<u64>,\n\n    /// Idle timeout expiration time.\n    idle_timer: Option<Instant>,\n\n    /// Draining timeout expiration time.\n    draining_timer: Option<Instant>,\n\n    /// List of raw packets that were received before they could be decrypted.\n    undecryptable_pkts: VecDeque<(Vec<u8>, RecvInfo)>,\n\n    /// The negotiated ALPN protocol.\n    alpn: Vec<u8>,\n\n    /// Whether this is a server-side connection.\n    is_server: bool,\n\n    /// Whether the initial secrets have been derived.\n    derived_initial_secrets: bool,\n\n    /// Whether a version negotiation packet has already been received. Only\n    /// relevant for client connections.\n    did_version_negotiation: bool,\n\n    /// Whether stateless retry has been performed.\n    did_retry: bool,\n\n    /// Whether the peer already updated its connection ID.\n    got_peer_conn_id: bool,\n\n    /// Whether the peer verified our initial address.\n    peer_verified_initial_address: bool,\n\n    /// Whether the peer's transport parameters were parsed.\n    parsed_peer_transport_params: bool,\n\n    /// Whether the connection handshake has been completed.\n    handshake_completed: bool,\n\n    /// Whether the HANDSHAKE_DONE frame has been sent.\n    handshake_done_sent: bool,\n\n    /// Whether the HANDSHAKE_DONE frame has been acked.\n    handshake_done_acked: bool,\n\n    /// Whether the connection handshake has been confirmed.\n    handshake_confirmed: bool,\n\n    /// Key phase bit used for outgoing protected packets.\n    key_phase: bool,\n\n    /// Whether an ack-eliciting packet has been sent since last receiving a\n    /// packet.\n    ack_eliciting_sent: bool,\n\n    /// Whether the connection is closed.\n    closed: bool,\n\n    /// Whether the connection was timed out.\n    timed_out: bool,\n\n    /// Whether to send GREASE.\n    grease: bool,\n\n    /// Whether to send STREAMS_BLOCKED frames when bidi or uni stream quota\n    /// exhausted.\n    enable_send_streams_blocked: bool,\n\n    /// TLS keylog writer.\n    keylog: Option<Box<dyn std::io::Write + Send + Sync>>,\n\n    #[cfg(feature = \"qlog\")]\n    qlog: QlogInfo,\n\n    /// DATAGRAM queues.\n    dgram_recv_queue: dgram::DatagramQueue,\n    dgram_send_queue: dgram::DatagramQueue,\n\n    /// Whether to emit DATAGRAM frames in the next packet.\n    emit_dgram: bool,\n\n    /// Whether the connection should prevent from reusing destination\n    /// Connection IDs when the peer migrates.\n    disable_dcid_reuse: bool,\n\n    /// The number of streams reset by local.\n    reset_stream_local_count: u64,\n\n    /// The number of streams stopped by local.\n    stopped_stream_local_count: u64,\n\n    /// The number of streams reset by remote.\n    reset_stream_remote_count: u64,\n\n    /// The number of streams stopped by remote.\n    stopped_stream_remote_count: u64,\n\n    /// The number of DATA_BLOCKED frames sent due to hitting the connection\n    /// flow control limit.\n    data_blocked_sent_count: u64,\n\n    /// The number of STREAM_DATA_BLOCKED frames sent due to a stream hitting\n    /// the stream flow control limit.\n    stream_data_blocked_sent_count: u64,\n\n    /// The number of DATA_BLOCKED frames received from the remote endpoint.\n    data_blocked_recv_count: u64,\n\n    /// The number of STREAM_DATA_BLOCKED frames received from the remote\n    /// endpoint.\n    stream_data_blocked_recv_count: u64,\n\n    /// The number of STREAMS_BLOCKED frames received from the remote endpoint\n    /// indicating the peer is blocked on opening new bidirectional streams.\n    streams_blocked_bidi_recv_count: u64,\n\n    /// The number of STREAMS_BLOCKED frames received from the remote endpoint\n    /// indicating the peer is blocked on opening new unidirectional streams.\n    streams_blocked_uni_recv_count: u64,\n\n    /// Tracks if the connection hit the peer's bidi or uni stream limit, and if\n    /// STREAMS_BLOCKED frames are pending transmission.\n    streams_blocked_bidi_state: StreamsBlockedState,\n    streams_blocked_uni_state: StreamsBlockedState,\n\n    /// The anti-amplification limit factor.\n    max_amplification_factor: usize,\n}\n\n/// Creates a new server-side connection.\n///\n/// The `scid` parameter represents the server's source connection ID, while\n/// the optional `odcid` parameter represents the original destination ID the\n/// client sent before a Retry packet (this is only required when using the\n/// [`retry()`] function). See also the [`accept_with_retry()`] function for\n/// more advanced retry cases.\n///\n/// [`retry()`]: fn.retry.html\n///\n/// ## Examples:\n///\n/// ```no_run\n/// # let mut config = quiche::Config::new(0xbabababa)?;\n/// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n/// # let local = \"127.0.0.1:0\".parse().unwrap();\n/// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n/// let conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n/// # Ok::<(), quiche::Error>(())\n/// ```\n#[inline(always)]\npub fn accept(\n    scid: &ConnectionId, odcid: Option<&ConnectionId>, local: SocketAddr,\n    peer: SocketAddr, config: &mut Config,\n) -> Result<Connection> {\n    accept_with_buf_factory(scid, odcid, local, peer, config)\n}\n\n/// Creates a new server-side connection, with a custom buffer generation\n/// method.\n///\n/// The buffers generated can be anything that can be drereferenced as a byte\n/// slice. See [`accept`] and [`BufFactory`] for more info.\n#[inline]\npub fn accept_with_buf_factory<F: BufFactory>(\n    scid: &ConnectionId, odcid: Option<&ConnectionId>, local: SocketAddr,\n    peer: SocketAddr, config: &mut Config,\n) -> Result<Connection<F>> {\n    // For connections with `odcid` set, we historically used `retry_source_cid =\n    // scid`. Keep this behavior to preserve backwards compatibility.\n    // `accept_with_retry` allows the SCIDs to be specified separately.\n    let retry_cids = odcid.map(|odcid| RetryConnectionIds {\n        original_destination_cid: odcid,\n        retry_source_cid: scid,\n    });\n    Connection::new(scid, retry_cids, None, local, peer, config, true)\n}\n\n/// A wrapper for connection IDs used in [`accept_with_retry`].\npub struct RetryConnectionIds<'a> {\n    /// The DCID of the first Initial packet received by the server, which\n    /// triggered the Retry packet.\n    pub original_destination_cid: &'a ConnectionId<'a>,\n    /// The SCID of the Retry packet sent by the server. This can be different\n    /// from the new connection's SCID.\n    pub retry_source_cid: &'a ConnectionId<'a>,\n}\n\n/// Creates a new server-side connection after the client responded to a Retry\n/// packet.\n///\n/// To generate a Retry packet in the first place, use the [`retry()`] function.\n///\n/// The `scid` parameter represents the server's source connection ID, which can\n/// be freshly generated after the application has successfully verified the\n/// Retry. `retry_cids` is used to tie the new connection to the Initial + Retry\n/// exchange that preceded the connection's creation.\n///\n/// The DCID of the client's Initial packet is inherently untrusted data. It is\n/// safe to use the DCID in the `retry_source_cid` field of the\n/// `RetryConnectionIds` provided to this function. However, using the Initial's\n/// DCID for the `scid` parameter carries risks. Applications are advised to\n/// implement their own DCID validation steps before using the DCID in that\n/// manner.\n#[inline]\npub fn accept_with_retry<F: BufFactory>(\n    scid: &ConnectionId, retry_cids: RetryConnectionIds, local: SocketAddr,\n    peer: SocketAddr, config: &mut Config,\n) -> Result<Connection<F>> {\n    Connection::new(scid, Some(retry_cids), None, local, peer, config, true)\n}\n\n/// Creates a new client-side connection.\n///\n/// The `scid` parameter is used as the connection's source connection ID,\n/// while the optional `server_name` parameter is used to verify the peer's\n/// certificate.\n///\n/// ## Examples:\n///\n/// ```no_run\n/// # let mut config = quiche::Config::new(0xbabababa)?;\n/// # let server_name = \"quic.tech\";\n/// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n/// # let local = \"127.0.0.1:4321\".parse().unwrap();\n/// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n/// let conn =\n///     quiche::connect(Some(&server_name), &scid, local, peer, &mut config)?;\n/// # Ok::<(), quiche::Error>(())\n/// ```\n#[inline]\npub fn connect(\n    server_name: Option<&str>, scid: &ConnectionId, local: SocketAddr,\n    peer: SocketAddr, config: &mut Config,\n) -> Result<Connection> {\n    let mut conn = Connection::new(scid, None, None, local, peer, config, false)?;\n\n    if let Some(server_name) = server_name {\n        conn.handshake.set_host_name(server_name)?;\n    }\n\n    Ok(conn)\n}\n\n/// Creates a new client-side connection using the given DCID initially.\n///\n/// Be aware that [RFC 9000] places requirements for unpredictability and length\n/// on the client DCID field. This function is dangerous if these  requirements\n/// are not satisfied.\n///\n/// The `scid` parameter is used as the connection's source connection ID, while\n/// the optional `server_name` parameter is used to verify the peer's\n/// certificate.\n///\n/// [RFC 9000]: <https://datatracker.ietf.org/doc/html/rfc9000#section-7.2-3>\n#[cfg(feature = \"custom-client-dcid\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"custom-client-dcid\")))]\npub fn connect_with_dcid(\n    server_name: Option<&str>, scid: &ConnectionId, dcid: &ConnectionId,\n    local: SocketAddr, peer: SocketAddr, config: &mut Config,\n) -> Result<Connection> {\n    let mut conn =\n        Connection::new(scid, None, Some(dcid), local, peer, config, false)?;\n\n    if let Some(server_name) = server_name {\n        conn.handshake.set_host_name(server_name)?;\n    }\n\n    Ok(conn)\n}\n\n/// Creates a new client-side connection, with a custom buffer generation\n/// method.\n///\n/// The buffers generated can be anything that can be drereferenced as a byte\n/// slice. See [`connect`] and [`BufFactory`] for more info.\n#[inline]\npub fn connect_with_buffer_factory<F: BufFactory>(\n    server_name: Option<&str>, scid: &ConnectionId, local: SocketAddr,\n    peer: SocketAddr, config: &mut Config,\n) -> Result<Connection<F>> {\n    let mut conn = Connection::new(scid, None, None, local, peer, config, false)?;\n\n    if let Some(server_name) = server_name {\n        conn.handshake.set_host_name(server_name)?;\n    }\n\n    Ok(conn)\n}\n\n/// Creates a new client-side connection, with a custom buffer generation\n/// method using the given dcid initially.\n/// Be aware the RFC places requirements for unpredictability and length\n/// on the client DCID field.\n/// [`RFC9000`]:  https://datatracker.ietf.org/doc/html/rfc9000#section-7.2-3\n///\n/// The buffers generated can be anything that can be drereferenced as a byte\n/// slice. See [`connect`] and [`BufFactory`] for more info.\n#[cfg(feature = \"custom-client-dcid\")]\n#[cfg_attr(docsrs, doc(cfg(feature = \"custom-client-dcid\")))]\npub fn connect_with_dcid_and_buffer_factory<F: BufFactory>(\n    server_name: Option<&str>, scid: &ConnectionId, dcid: &ConnectionId,\n    local: SocketAddr, peer: SocketAddr, config: &mut Config,\n) -> Result<Connection<F>> {\n    let mut conn =\n        Connection::new(scid, None, Some(dcid), local, peer, config, false)?;\n\n    if let Some(server_name) = server_name {\n        conn.handshake.set_host_name(server_name)?;\n    }\n\n    Ok(conn)\n}\n\n/// Writes a version negotiation packet.\n///\n/// The `scid` and `dcid` parameters are the source connection ID and the\n/// destination connection ID extracted from the received client's Initial\n/// packet that advertises an unsupported version.\n///\n/// ## Examples:\n///\n/// ```no_run\n/// # let mut buf = [0; 512];\n/// # let mut out = [0; 512];\n/// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n/// let (len, src) = socket.recv_from(&mut buf).unwrap();\n///\n/// let hdr =\n///     quiche::Header::from_slice(&mut buf[..len], quiche::MAX_CONN_ID_LEN)?;\n///\n/// if hdr.version != quiche::PROTOCOL_VERSION {\n///     let len = quiche::negotiate_version(&hdr.scid, &hdr.dcid, &mut out)?;\n///     socket.send_to(&out[..len], &src).unwrap();\n/// }\n/// # Ok::<(), quiche::Error>(())\n/// ```\n#[inline]\npub fn negotiate_version(\n    scid: &ConnectionId, dcid: &ConnectionId, out: &mut [u8],\n) -> Result<usize> {\n    packet::negotiate_version(scid, dcid, out)\n}\n\n/// Writes a stateless retry packet.\n///\n/// The `scid` and `dcid` parameters are the source connection ID and the\n/// destination connection ID extracted from the received client's Initial\n/// packet, while `new_scid` is the server's new source connection ID and\n/// `token` is the address validation token the client needs to echo back.\n///\n/// The application is responsible for generating the address validation\n/// token to be sent to the client, and verifying tokens sent back by the\n/// client. The generated token should include the `dcid` parameter, such\n/// that it can be later extracted from the token and passed to the\n/// [`accept()`] function as its `odcid` parameter.\n///\n/// [`accept()`]: fn.accept.html\n///\n/// ## Examples:\n///\n/// ```no_run\n/// # let mut config = quiche::Config::new(0xbabababa)?;\n/// # let mut buf = [0; 512];\n/// # let mut out = [0; 512];\n/// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n/// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n/// # let local = socket.local_addr().unwrap();\n/// # fn mint_token(hdr: &quiche::Header, src: &std::net::SocketAddr) -> Vec<u8> {\n/// #     vec![]\n/// # }\n/// # fn validate_token<'a>(src: &std::net::SocketAddr, token: &'a [u8]) -> Option<quiche::ConnectionId<'a>> {\n/// #     None\n/// # }\n/// let (len, peer) = socket.recv_from(&mut buf).unwrap();\n///\n/// let hdr = quiche::Header::from_slice(&mut buf[..len], quiche::MAX_CONN_ID_LEN)?;\n///\n/// let token = hdr.token.as_ref().unwrap();\n///\n/// // No token sent by client, create a new one.\n/// if token.is_empty() {\n///     let new_token = mint_token(&hdr, &peer);\n///\n///     let len = quiche::retry(\n///         &hdr.scid, &hdr.dcid, &scid, &new_token, hdr.version, &mut out,\n///     )?;\n///\n///     socket.send_to(&out[..len], &peer).unwrap();\n///     return Ok(());\n/// }\n///\n/// // Client sent token, validate it.\n/// let odcid = validate_token(&peer, token);\n///\n/// if odcid.is_none() {\n///     // Invalid address validation token.\n///     return Ok(());\n/// }\n///\n/// let conn = quiche::accept(&scid, odcid.as_ref(), local, peer, &mut config)?;\n/// # Ok::<(), quiche::Error>(())\n/// ```\n#[inline]\npub fn retry(\n    scid: &ConnectionId, dcid: &ConnectionId, new_scid: &ConnectionId,\n    token: &[u8], version: u32, out: &mut [u8],\n) -> Result<usize> {\n    packet::retry(scid, dcid, new_scid, token, version, out)\n}\n\n/// Returns true if the given protocol version is supported.\n#[inline]\npub fn version_is_supported(version: u32) -> bool {\n    matches!(version, PROTOCOL_VERSION_V1)\n}\n\n/// Pushes a frame to the output packet if there is enough space.\n///\n/// Returns `true` on success, `false` otherwise. In case of failure it means\n/// there is no room to add the frame in the packet. You may retry to add the\n/// frame later.\nmacro_rules! push_frame_to_pkt {\n    ($out:expr, $frames:expr, $frame:expr, $left:expr) => {{\n        if $frame.wire_len() <= $left {\n            $left -= $frame.wire_len();\n\n            $frame.to_bytes(&mut $out)?;\n\n            $frames.push($frame);\n\n            true\n        } else {\n            false\n        }\n    }};\n}\n\n/// Executes the provided body if the qlog feature is enabled, quiche has been\n/// configured with a log writer, the event's importance is within the\n/// configured level.\nmacro_rules! qlog_with_type {\n    ($ty:expr, $qlog:expr, $qlog_streamer_ref:ident, $body:block) => {{\n        #[cfg(feature = \"qlog\")]\n        {\n            if EventImportance::from($ty).is_contained_in(&$qlog.level) {\n                if let Some($qlog_streamer_ref) = &mut $qlog.streamer {\n                    $body\n                }\n            }\n        }\n    }};\n}\n\n#[cfg(feature = \"qlog\")]\nconst QLOG_PARAMS_SET: EventType =\n    EventType::QuicEventType(QuicEventType::ParametersSet);\n\n#[cfg(feature = \"qlog\")]\nconst QLOG_PACKET_RX: EventType =\n    EventType::QuicEventType(QuicEventType::PacketReceived);\n\n#[cfg(feature = \"qlog\")]\nconst QLOG_PACKET_TX: EventType =\n    EventType::QuicEventType(QuicEventType::PacketSent);\n\n#[cfg(feature = \"qlog\")]\nconst QLOG_DATA_MV: EventType =\n    EventType::QuicEventType(QuicEventType::StreamDataMoved);\n\n#[cfg(feature = \"qlog\")]\nconst QLOG_METRICS: EventType =\n    EventType::QuicEventType(QuicEventType::RecoveryMetricsUpdated);\n\n#[cfg(feature = \"qlog\")]\nconst QLOG_CONNECTION_CLOSED: EventType =\n    EventType::QuicEventType(QuicEventType::ConnectionClosed);\n\n#[cfg(feature = \"qlog\")]\nstruct QlogInfo {\n    streamer: Option<qlog::streamer::QlogStreamer>,\n    logged_peer_params: bool,\n    level: EventImportance,\n}\n\n#[cfg(feature = \"qlog\")]\nimpl Default for QlogInfo {\n    fn default() -> Self {\n        QlogInfo {\n            streamer: None,\n            logged_peer_params: false,\n            level: EventImportance::Base,\n        }\n    }\n}\n\nimpl<F: BufFactory> Connection<F> {\n    fn new(\n        scid: &ConnectionId, retry_cids: Option<RetryConnectionIds>,\n        client_dcid: Option<&ConnectionId>, local: SocketAddr, peer: SocketAddr,\n        config: &mut Config, is_server: bool,\n    ) -> Result<Connection<F>> {\n        let tls = config.tls_ctx.new_handshake()?;\n        Connection::with_tls(\n            scid,\n            retry_cids,\n            client_dcid,\n            local,\n            peer,\n            config,\n            tls,\n            is_server,\n        )\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn with_tls(\n        scid: &ConnectionId, retry_cids: Option<RetryConnectionIds>,\n        client_dcid: Option<&ConnectionId>, local: SocketAddr, peer: SocketAddr,\n        config: &Config, tls: tls::Handshake, is_server: bool,\n    ) -> Result<Connection<F>> {\n        if retry_cids.is_some() && client_dcid.is_some() {\n            // These are exclusive, the caller should only specify one or the\n            // other.\n            return Err(Error::InvalidDcidInitialization);\n        }\n        #[cfg(feature = \"custom-client-dcid\")]\n        if let Some(client_dcid) = client_dcid {\n            // The Minimum length is 8.\n            // See https://datatracker.ietf.org/doc/html/rfc9000#section-7.2-3\n            if client_dcid.to_vec().len() < 8 {\n                return Err(Error::InvalidDcidInitialization);\n            }\n        }\n        #[cfg(not(feature = \"custom-client-dcid\"))]\n        if client_dcid.is_some() {\n            return Err(Error::InvalidDcidInitialization);\n        }\n\n        let max_rx_data = config.local_transport_params.initial_max_data;\n\n        let scid_as_hex: Vec<String> =\n            scid.iter().map(|b| format!(\"{b:02x}\")).collect();\n\n        let reset_token = if is_server {\n            config.local_transport_params.stateless_reset_token\n        } else {\n            None\n        };\n\n        let recovery_config = recovery::RecoveryConfig::from_config(config);\n\n        let mut path = path::Path::new(\n            local,\n            peer,\n            &recovery_config,\n            config.path_challenge_recv_max_queue_len,\n            true,\n            Some(config),\n        );\n\n        // If we sent a Retry assume the peer's address is verified.\n        path.verified_peer_address = retry_cids.is_some();\n        // Assume clients validate the server's address implicitly.\n        path.peer_verified_local_address = is_server;\n\n        // Do not allocate more than the number of active CIDs.\n        let paths = path::PathMap::new(\n            path,\n            config.local_transport_params.active_conn_id_limit as usize,\n            is_server,\n        );\n\n        let active_path_id = paths.get_active_path_id()?;\n\n        let ids = cid::ConnectionIdentifiers::new(\n            config.local_transport_params.active_conn_id_limit as usize,\n            scid,\n            active_path_id,\n            reset_token,\n        );\n\n        let mut conn = Connection {\n            version: config.version,\n\n            ids,\n\n            trace_id: scid_as_hex.join(\"\"),\n\n            pkt_num_spaces: [\n                packet::PktNumSpace::new(),\n                packet::PktNumSpace::new(),\n                packet::PktNumSpace::new(),\n            ],\n\n            crypto_ctx: [\n                packet::CryptoContext::new(),\n                packet::CryptoContext::new(),\n                packet::CryptoContext::new(),\n            ],\n\n            next_pkt_num: 0,\n\n            pkt_num_manager: packet::PktNumManager::new(),\n\n            peer_transport_params: TransportParams::default(),\n\n            peer_transport_params_track_unknown: config\n                .track_unknown_transport_params,\n\n            local_transport_params: config.local_transport_params.clone(),\n\n            handshake: tls,\n\n            session: None,\n\n            recovery_config,\n\n            paths,\n            path_challenge_recv_max_queue_len: config\n                .path_challenge_recv_max_queue_len,\n            path_challenge_rx_count: 0,\n\n            application_protos: config.application_protos.clone(),\n\n            recv_count: 0,\n            sent_count: 0,\n            lost_count: 0,\n            spurious_lost_count: 0,\n            retrans_count: 0,\n            dgram_sent_count: 0,\n            dgram_recv_count: 0,\n            sent_bytes: 0,\n            recv_bytes: 0,\n            acked_bytes: 0,\n            lost_bytes: 0,\n\n            rx_data: 0,\n            flow_control: flowcontrol::FlowControl::new(\n                max_rx_data,\n                cmp::min(max_rx_data / 2 * 3, DEFAULT_CONNECTION_WINDOW),\n                config.max_connection_window,\n            ),\n            should_send_max_data: false,\n            should_send_max_streams_bidi: false,\n            should_send_max_streams_uni: false,\n\n            tx_cap: 0,\n            tx_cap_factor: config.tx_cap_factor,\n\n            tx_buffered: 0,\n            tx_buffered_state: TxBufferTrackingState::Ok,\n\n            tx_data: 0,\n            max_tx_data: 0,\n            last_tx_data: 0,\n\n            stream_retrans_bytes: 0,\n\n            streams: stream::StreamMap::new(\n                config.local_transport_params.initial_max_streams_bidi,\n                config.local_transport_params.initial_max_streams_uni,\n                config.max_stream_window,\n            ),\n\n            odcid: None,\n\n            rscid: None,\n\n            token: None,\n\n            local_error: None,\n\n            peer_error: None,\n\n            blocked_limit: None,\n\n            idle_timer: None,\n\n            draining_timer: None,\n\n            undecryptable_pkts: VecDeque::new(),\n\n            alpn: Vec::new(),\n\n            is_server,\n\n            derived_initial_secrets: false,\n\n            did_version_negotiation: false,\n\n            did_retry: false,\n\n            got_peer_conn_id: false,\n\n            // Assume clients validate the server's address implicitly.\n            peer_verified_initial_address: is_server,\n\n            parsed_peer_transport_params: false,\n\n            handshake_completed: false,\n\n            handshake_done_sent: false,\n            handshake_done_acked: false,\n\n            handshake_confirmed: false,\n\n            key_phase: false,\n\n            ack_eliciting_sent: false,\n\n            closed: false,\n\n            timed_out: false,\n\n            grease: config.grease,\n\n            enable_send_streams_blocked: config.enable_send_streams_blocked,\n\n            keylog: None,\n\n            #[cfg(feature = \"qlog\")]\n            qlog: Default::default(),\n\n            dgram_recv_queue: dgram::DatagramQueue::new(\n                config.dgram_recv_max_queue_len,\n            ),\n\n            dgram_send_queue: dgram::DatagramQueue::new(\n                config.dgram_send_max_queue_len,\n            ),\n\n            emit_dgram: true,\n\n            disable_dcid_reuse: config.disable_dcid_reuse,\n\n            reset_stream_local_count: 0,\n            stopped_stream_local_count: 0,\n            reset_stream_remote_count: 0,\n            stopped_stream_remote_count: 0,\n\n            data_blocked_sent_count: 0,\n            stream_data_blocked_sent_count: 0,\n            data_blocked_recv_count: 0,\n            stream_data_blocked_recv_count: 0,\n\n            streams_blocked_bidi_recv_count: 0,\n            streams_blocked_uni_recv_count: 0,\n\n            streams_blocked_bidi_state: Default::default(),\n            streams_blocked_uni_state: Default::default(),\n\n            max_amplification_factor: config.max_amplification_factor,\n        };\n\n        if let Some(retry_cids) = retry_cids {\n            conn.local_transport_params\n                .original_destination_connection_id =\n                Some(retry_cids.original_destination_cid.to_vec().into());\n\n            conn.local_transport_params.retry_source_connection_id =\n                Some(retry_cids.retry_source_cid.to_vec().into());\n\n            conn.did_retry = true;\n        }\n\n        conn.local_transport_params.initial_source_connection_id =\n            Some(conn.ids.get_scid(0)?.cid.to_vec().into());\n\n        conn.handshake.init(is_server)?;\n\n        conn.handshake\n            .use_legacy_codepoint(config.version != PROTOCOL_VERSION_V1);\n\n        conn.encode_transport_params()?;\n\n        if !is_server {\n            let dcid = if let Some(client_dcid) = client_dcid {\n                // We already had an dcid generated for us, use it.\n                client_dcid.to_vec()\n            } else {\n                // Derive initial secrets for the client. We can do this here\n                // because we already generated the random\n                // destination connection ID.\n                let mut dcid = [0; 16];\n                rand::rand_bytes(&mut dcid[..]);\n                dcid.to_vec()\n            };\n\n            let (aead_open, aead_seal) = crypto::derive_initial_key_material(\n                &dcid,\n                conn.version,\n                conn.is_server,\n                false,\n            )?;\n\n            let reset_token = conn.peer_transport_params.stateless_reset_token;\n            conn.set_initial_dcid(\n                dcid.to_vec().into(),\n                reset_token,\n                active_path_id,\n            )?;\n\n            conn.crypto_ctx[packet::Epoch::Initial].crypto_open = Some(aead_open);\n            conn.crypto_ctx[packet::Epoch::Initial].crypto_seal = Some(aead_seal);\n\n            conn.derived_initial_secrets = true;\n        }\n\n        Ok(conn)\n    }\n\n    /// Sets keylog output to the designated [`Writer`].\n    ///\n    /// This needs to be called as soon as the connection is created, to avoid\n    /// missing some early logs.\n    ///\n    /// [`Writer`]: https://doc.rust-lang.org/std/io/trait.Write.html\n    #[inline]\n    pub fn set_keylog(&mut self, writer: Box<dyn std::io::Write + Send + Sync>) {\n        self.keylog = Some(writer);\n    }\n\n    /// Sets qlog output to the designated [`Writer`].\n    ///\n    /// Only events included in `QlogLevel::Base` are written. The serialization\n    /// format is JSON-SEQ.\n    ///\n    /// This needs to be called as soon as the connection is created, to avoid\n    /// missing some early logs.\n    ///\n    /// [`Writer`]: https://doc.rust-lang.org/std/io/trait.Write.html\n    #[cfg(feature = \"qlog\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"qlog\")))]\n    pub fn set_qlog(\n        &mut self, writer: Box<dyn std::io::Write + Send + Sync>, title: String,\n        description: String,\n    ) {\n        self.set_qlog_with_level(writer, title, description, QlogLevel::Base)\n    }\n\n    /// Sets qlog output to the designated [`Writer`].\n    ///\n    /// Only qlog events included in the specified `QlogLevel` are written. The\n    /// serialization format is JSON-SEQ.\n    ///\n    /// This needs to be called as soon as the connection is created, to avoid\n    /// missing some early logs.\n    ///\n    /// [`Writer`]: https://doc.rust-lang.org/std/io/trait.Write.html\n    #[cfg(feature = \"qlog\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"qlog\")))]\n    pub fn set_qlog_with_level(\n        &mut self, writer: Box<dyn std::io::Write + Send + Sync>, title: String,\n        description: String, qlog_level: QlogLevel,\n    ) {\n        use qlog::events::quic::TransportInitiator;\n        use qlog::events::HTTP3_URI;\n        use qlog::events::QUIC_URI;\n\n        let vp = if self.is_server {\n            qlog::VantagePointType::Server\n        } else {\n            qlog::VantagePointType::Client\n        };\n\n        let level = match qlog_level {\n            QlogLevel::Core => EventImportance::Core,\n\n            QlogLevel::Base => EventImportance::Base,\n\n            QlogLevel::Extra => EventImportance::Extra,\n        };\n\n        self.qlog.level = level;\n\n        let trace = qlog::TraceSeq::new(\n            Some(title.to_string()),\n            Some(description.to_string()),\n            None,\n            Some(qlog::VantagePoint {\n                name: None,\n                ty: vp,\n                flow: None,\n            }),\n            vec![QUIC_URI.to_string(), HTTP3_URI.to_string()],\n        );\n\n        let mut streamer = qlog::streamer::QlogStreamer::new(\n            Some(title),\n            Some(description),\n            Instant::now(),\n            trace,\n            self.qlog.level,\n            writer,\n        );\n\n        streamer.start_log().ok();\n\n        let ev_data = self\n            .local_transport_params\n            .to_qlog(TransportInitiator::Local, self.handshake.cipher());\n\n        // This event occurs very early, so just mark the relative time as 0.0.\n        streamer.add_event(Event::with_time(0.0, ev_data)).ok();\n\n        self.qlog.streamer = Some(streamer);\n    }\n\n    /// Returns a mutable reference to the QlogStreamer, if it exists.\n    #[cfg(feature = \"qlog\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"qlog\")))]\n    pub fn qlog_streamer(&mut self) -> Option<&mut qlog::streamer::QlogStreamer> {\n        self.qlog.streamer.as_mut()\n    }\n\n    /// Configures the given session for resumption.\n    ///\n    /// On the client, this can be used to offer the given serialized session,\n    /// as returned by [`session()`], for resumption.\n    ///\n    /// This must only be called immediately after creating a connection, that\n    /// is, before any packet is sent or received.\n    ///\n    /// [`session()`]: struct.Connection.html#method.session\n    #[inline]\n    pub fn set_session(&mut self, session: &[u8]) -> Result<()> {\n        let mut b = octets::Octets::with_slice(session);\n\n        let session_len = b.get_u64()? as usize;\n        let session_bytes = b.get_bytes(session_len)?;\n\n        self.handshake.set_session(session_bytes.as_ref())?;\n\n        let raw_params_len = b.get_u64()? as usize;\n        let raw_params_bytes = b.get_bytes(raw_params_len)?;\n\n        let peer_params = TransportParams::decode(\n            raw_params_bytes.as_ref(),\n            self.is_server,\n            self.peer_transport_params_track_unknown,\n        )?;\n\n        self.process_peer_transport_params(peer_params)?;\n\n        Ok(())\n    }\n\n    /// Sets the `max_idle_timeout` transport parameter, in milliseconds.\n    ///\n    /// This must only be called immediately after creating a connection, that\n    /// is, before any packet is sent or received.\n    ///\n    /// The default value is infinite, that is, no timeout is used unless\n    /// already configured when creating the connection.\n    pub fn set_max_idle_timeout(&mut self, v: u64) -> Result<()> {\n        self.local_transport_params.max_idle_timeout =\n            cmp::min(v, octets::MAX_VAR_INT);\n\n        self.encode_transport_params()\n    }\n\n    /// Sets the congestion control algorithm used.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_cc_algorithm()`].\n    ///\n    /// [`Config::set_cc_algorithm()`]: struct.Config.html#method.set_cc_algorithm\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_cc_algorithm_in_handshake(\n        ssl: &mut boring::ssl::SslRef, algo: CongestionControlAlgorithm,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.cc_algorithm = algo;\n\n        Ok(())\n    }\n\n    /// Sets custom BBR settings.\n    ///\n    /// This API is experimental and will be removed in the future.\n    ///\n    /// Currently this only applies if cc_algorithm is\n    /// `CongestionControlAlgorithm::Bbr2Gcongestion` is set.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_custom_bbr_settings()`].\n    ///\n    /// [`Config::set_custom_bbr_settings()`]: struct.Config.html#method.set_custom_bbr_settings\n    #[cfg(all(feature = \"boringssl-boring-crate\", feature = \"internal\"))]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    #[doc(hidden)]\n    pub fn set_custom_bbr_settings_in_handshake(\n        ssl: &mut boring::ssl::SslRef, custom_bbr_params: BbrParams,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.custom_bbr_params = Some(custom_bbr_params);\n\n        Ok(())\n    }\n\n    /// Sets the congestion control algorithm used by string.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_cc_algorithm_name()`].\n    ///\n    /// [`Config::set_cc_algorithm_name()`]: struct.Config.html#method.set_cc_algorithm_name\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_cc_algorithm_name_in_handshake(\n        ssl: &mut boring::ssl::SslRef, name: &str,\n    ) -> Result<()> {\n        let cc_algo = CongestionControlAlgorithm::from_str(name)?;\n        Self::set_cc_algorithm_in_handshake(ssl, cc_algo)\n    }\n\n    /// Sets initial congestion window size in terms of packet count.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_initial_congestion_window_packets()`].\n    ///\n    /// [`Config::set_initial_congestion_window_packets()`]: struct.Config.html#method.set_initial_congestion_window_packets\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_initial_congestion_window_packets_in_handshake(\n        ssl: &mut boring::ssl::SslRef, packets: usize,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.initial_congestion_window_packets = packets;\n\n        Ok(())\n    }\n\n    /// Configure whether to enable relaxed loss detection on spurious loss.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_enable_relaxed_loss_threshold()`].\n    ///\n    /// [`Config::set_enable_relaxed_loss_threshold()`]: struct.Config.html#method.set_enable_relaxed_loss_threshold\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_enable_relaxed_loss_threshold_in_handshake(\n        ssl: &mut boring::ssl::SslRef, enable: bool,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.enable_relaxed_loss_threshold = enable;\n\n        Ok(())\n    }\n\n    /// Configure whether to enable the CUBIC idle restart fix.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_enable_cubic_idle_restart_fix()`].\n    ///\n    /// [`Config::set_enable_cubic_idle_restart_fix()`]: struct.Config.html#method.set_enable_cubic_idle_restart_fix\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_enable_cubic_idle_restart_fix_in_handshake(\n        ssl: &mut boring::ssl::SslRef, enable: bool,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.enable_cubic_idle_restart_fix = enable;\n\n        Ok(())\n    }\n\n    /// Configures whether to enable HyStart++.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::enable_hystart()`].\n    ///\n    /// [`Config::enable_hystart()`]: struct.Config.html#method.enable_hystart\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_hystart_in_handshake(\n        ssl: &mut boring::ssl::SslRef, v: bool,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.hystart = v;\n\n        Ok(())\n    }\n\n    /// Configures whether to enable pacing.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::enable_pacing()`].\n    ///\n    /// [`Config::enable_pacing()`]: struct.Config.html#method.enable_pacing\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_pacing_in_handshake(\n        ssl: &mut boring::ssl::SslRef, v: bool,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.pacing = v;\n\n        Ok(())\n    }\n\n    /// Sets the max value for pacing rate.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_max_pacing_rate()`].\n    ///\n    /// [`Config::set_max_pacing_rate()`]: struct.Config.html#method.set_max_pacing_rate\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_max_pacing_rate_in_handshake(\n        ssl: &mut boring::ssl::SslRef, v: Option<u64>,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.max_pacing_rate = v;\n\n        Ok(())\n    }\n\n    /// Sets the maximum outgoing UDP payload size.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_max_send_udp_payload_size()`].\n    ///\n    /// [`Config::set_max_send_udp_payload_size()`]: struct.Config.html#method.set_max_send_udp_payload_size\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_max_send_udp_payload_size_in_handshake(\n        ssl: &mut boring::ssl::SslRef, v: usize,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.recovery_config.max_send_udp_payload_size = v;\n\n        Ok(())\n    }\n\n    /// Sets the send capacity factor.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_send_capacity_factor()`].\n    ///\n    /// [`Config::set_max_send_udp_payload_size()`]: struct.Config.html#method.set_send_capacity_factor\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_send_capacity_factor_in_handshake(\n        ssl: &mut boring::ssl::SslRef, v: f64,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.tx_cap_factor = v;\n\n        Ok(())\n    }\n\n    /// Configures whether to do path MTU discovery.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::discover_pmtu()`].\n    ///\n    /// [`Config::discover_pmtu()`]: struct.Config.html#method.discover_pmtu\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_discover_pmtu_in_handshake(\n        ssl: &mut boring::ssl::SslRef, discover: bool, max_probes: u8,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.pmtud = Some((discover, max_probes));\n\n        Ok(())\n    }\n\n    /// Sets the `max_idle_timeout` transport parameter, in milliseconds.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_max_idle_timeout()`].\n    ///\n    /// [`Config::set_max_idle_timeout()`]: struct.Config.html#method.set_max_idle_timeout\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_max_idle_timeout_in_handshake(\n        ssl: &mut boring::ssl::SslRef, v: u64,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.local_transport_params.max_idle_timeout = v;\n\n        Self::set_transport_parameters_in_hanshake(\n            ex_data.local_transport_params.clone(),\n            ex_data.is_server,\n            ssl,\n        )\n    }\n\n    /// Sets the `initial_max_streams_bidi` transport parameter.\n    ///\n    /// This function can only be called inside one of BoringSSL's handshake\n    /// callbacks, before any packet has been sent. Calling this function any\n    /// other time will have no effect.\n    ///\n    /// See [`Config::set_initial_max_streams_bidi()`].\n    ///\n    /// [`Config::set_initial_max_streams_bidi()`]: struct.Config.html#method.set_initial_max_streams_bidi\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    #[cfg_attr(docsrs, doc(cfg(feature = \"boringssl-boring-crate\")))]\n    pub fn set_initial_max_streams_bidi_in_handshake(\n        ssl: &mut boring::ssl::SslRef, v: u64,\n    ) -> Result<()> {\n        let ex_data = tls::ExData::from_ssl_ref(ssl).ok_or(Error::TlsFail)?;\n\n        ex_data.local_transport_params.initial_max_streams_bidi = v;\n\n        Self::set_transport_parameters_in_hanshake(\n            ex_data.local_transport_params.clone(),\n            ex_data.is_server,\n            ssl,\n        )\n    }\n\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    fn set_transport_parameters_in_hanshake(\n        params: TransportParams, is_server: bool, ssl: &mut boring::ssl::SslRef,\n    ) -> Result<()> {\n        use foreign_types_shared::ForeignTypeRef;\n\n        // In order to apply the new parameter to the TLS state before TPs are\n        // written into a TLS message, we need to re-encode all TPs immediately.\n        //\n        // Since we don't have direct access to the main `Connection` object, we\n        // need to re-create the `Handshake` state from the `SslRef`.\n        //\n        // SAFETY: the `Handshake` object must not be drop()ed, otherwise it\n        // would free the underlying BoringSSL structure.\n        let mut handshake =\n            unsafe { tls::Handshake::from_ptr(ssl.as_ptr() as _) };\n        handshake.set_quic_transport_params(&params, is_server)?;\n\n        // Avoid running `drop(handshake)` as that would free the underlying\n        // handshake state.\n        std::mem::forget(handshake);\n\n        Ok(())\n    }\n\n    /// Processes QUIC packets received from the peer.\n    ///\n    /// On success the number of bytes processed from the input buffer is\n    /// returned. On error the connection will be closed by calling [`close()`]\n    /// with the appropriate error code.\n    ///\n    /// Coalesced packets will be processed as necessary.\n    ///\n    /// Note that the contents of the input buffer `buf` might be modified by\n    /// this function due to, for example, in-place decryption.\n    ///\n    /// [`close()`]: struct.Connection.html#method.close\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut buf = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// loop {\n    ///     let (read, from) = socket.recv_from(&mut buf).unwrap();\n    ///\n    ///     let recv_info = quiche::RecvInfo {\n    ///         from,\n    ///         to: local,\n    ///     };\n    ///\n    ///     let read = match conn.recv(&mut buf[..read], recv_info) {\n    ///         Ok(v) => v,\n    ///\n    ///         Err(e) => {\n    ///             // An error occurred, handle it.\n    ///             break;\n    ///         },\n    ///     };\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn recv(&mut self, buf: &mut [u8], info: RecvInfo) -> Result<usize> {\n        let len = buf.len();\n\n        if len == 0 {\n            return Err(Error::BufferTooShort);\n        }\n\n        let recv_pid = self.paths.path_id_from_addrs(&(info.to, info.from));\n\n        if let Some(recv_pid) = recv_pid {\n            let recv_path = self.paths.get_mut(recv_pid)?;\n\n            // Keep track of how many bytes we received from the client, so we\n            // can limit bytes sent back before address validation, to a\n            // multiple of this. The limit needs to be increased early on, so\n            // that if there is an error there is enough credit to send a\n            // CONNECTION_CLOSE.\n            //\n            // It doesn't matter if the packets received were valid or not, we\n            // only need to track the total amount of bytes received.\n            //\n            // Note that we also need to limit the number of bytes we sent on a\n            // path if we are not the host that initiated its usage.\n            if self.is_server && !recv_path.verified_peer_address {\n                recv_path.max_send_bytes += len * self.max_amplification_factor;\n            }\n        } else if !self.is_server {\n            // If a client receives packets from an unknown server address,\n            // the client MUST discard these packets.\n            trace!(\n                \"{} client received packet from unknown address {:?}, dropping\",\n                self.trace_id,\n                info,\n            );\n\n            return Ok(len);\n        }\n\n        let mut done = 0;\n        let mut left = len;\n\n        // Process coalesced packets.\n        while left > 0 {\n            let read = match self.recv_single(\n                &mut buf[len - left..len],\n                &info,\n                recv_pid,\n            ) {\n                Ok(v) => v,\n\n                Err(Error::Done) => {\n                    // If the packet can't be processed or decrypted, check if\n                    // it's a stateless reset.\n                    if self.is_stateless_reset(&buf[len - left..len]) {\n                        trace!(\"{} packet is a stateless reset\", self.trace_id);\n\n                        self.mark_closed();\n                    }\n\n                    left\n                },\n\n                Err(e) => {\n                    // In case of error processing the incoming packet, close\n                    // the connection.\n                    self.close(false, e.to_wire(), b\"\").ok();\n                    return Err(e);\n                },\n            };\n\n            done += read;\n            left -= read;\n        }\n\n        // Even though the packet was previously \"accepted\", it\n        // should be safe to forward the error, as it also comes\n        // from the `recv()` method.\n        self.process_undecrypted_0rtt_packets()?;\n\n        Ok(done)\n    }\n\n    fn process_undecrypted_0rtt_packets(&mut self) -> Result<()> {\n        // Process previously undecryptable 0-RTT packets if the decryption key\n        // is now available.\n        if self.crypto_ctx[packet::Epoch::Application]\n            .crypto_0rtt_open\n            .is_some()\n        {\n            while let Some((mut pkt, info)) = self.undecryptable_pkts.pop_front()\n            {\n                if let Err(e) = self.recv(&mut pkt, info) {\n                    self.undecryptable_pkts.clear();\n\n                    return Err(e);\n                }\n            }\n        }\n        Ok(())\n    }\n\n    /// Returns true if a QUIC packet is a stateless reset.\n    fn is_stateless_reset(&self, buf: &[u8]) -> bool {\n        // If the packet is too small, then we just throw it away.\n        let buf_len = buf.len();\n        if buf_len < 21 {\n            return false;\n        }\n\n        // TODO: we should iterate over all active destination connection IDs\n        // and check against their reset token.\n        match self.peer_transport_params.stateless_reset_token {\n            Some(token) => {\n                let token_len = 16;\n\n                crypto::verify_slices_are_equal(\n                    &token.to_be_bytes(),\n                    &buf[buf_len - token_len..buf_len],\n                )\n                .is_ok()\n            },\n\n            None => false,\n        }\n    }\n\n    /// Processes a single QUIC packet received from the peer.\n    ///\n    /// On success the number of bytes processed from the input buffer is\n    /// returned. When the [`Done`] error is returned, processing of the\n    /// remainder of the incoming UDP datagram should be interrupted.\n    ///\n    /// Note that a server might observe a new 4-tuple, preventing to\n    /// know in advance to which path the incoming packet belongs to (`recv_pid`\n    /// is `None`). As a client, packets from unknown 4-tuple are dropped\n    /// beforehand (see `recv()`).\n    ///\n    /// On error, an error other than [`Done`] is returned.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    fn recv_single(\n        &mut self, buf: &mut [u8], info: &RecvInfo, recv_pid: Option<usize>,\n    ) -> Result<usize> {\n        let now = Instant::now();\n\n        if buf.is_empty() {\n            return Err(Error::Done);\n        }\n\n        if self.is_closed() || self.is_draining() {\n            return Err(Error::Done);\n        }\n\n        let is_closing = self.local_error.is_some();\n\n        if is_closing {\n            return Err(Error::Done);\n        }\n\n        let buf_len = buf.len();\n\n        let mut b = octets::OctetsMut::with_slice(buf);\n\n        let mut hdr = Header::from_bytes(&mut b, self.source_id().len())\n            .map_err(|e| {\n                drop_pkt_on_err(\n                    e,\n                    self.recv_count,\n                    self.is_server,\n                    &self.trace_id,\n                )\n            })?;\n\n        if hdr.ty == Type::VersionNegotiation {\n            // Version negotiation packets can only be sent by the server.\n            if self.is_server {\n                return Err(Error::Done);\n            }\n\n            // Ignore duplicate version negotiation.\n            if self.did_version_negotiation {\n                return Err(Error::Done);\n            }\n\n            // Ignore version negotiation if any other packet has already been\n            // successfully processed.\n            if self.recv_count > 0 {\n                return Err(Error::Done);\n            }\n\n            if hdr.dcid != self.source_id() {\n                return Err(Error::Done);\n            }\n\n            if hdr.scid != self.destination_id() {\n                return Err(Error::Done);\n            }\n\n            trace!(\"{} rx pkt {:?}\", self.trace_id, hdr);\n\n            let versions = hdr.versions.ok_or(Error::Done)?;\n\n            // Ignore version negotiation if the version already selected is\n            // listed.\n            if versions.contains(&self.version) {\n                return Err(Error::Done);\n            }\n\n            let supported_versions =\n                versions.iter().filter(|&&v| version_is_supported(v));\n\n            let mut found_version = false;\n\n            for &v in supported_versions {\n                found_version = true;\n\n                // The final version takes precedence over draft ones.\n                if v == PROTOCOL_VERSION_V1 {\n                    self.version = v;\n                    break;\n                }\n\n                self.version = cmp::max(self.version, v);\n            }\n\n            if !found_version {\n                // We don't support any of the versions offered.\n                //\n                // While a man-in-the-middle attacker might be able to\n                // inject a version negotiation packet that triggers this\n                // failure, the window of opportunity is very small and\n                // this error is quite useful for debugging, so don't just\n                // ignore the packet.\n                return Err(Error::UnknownVersion);\n            }\n\n            self.did_version_negotiation = true;\n\n            // Derive Initial secrets based on the new version.\n            let (aead_open, aead_seal) = crypto::derive_initial_key_material(\n                &self.destination_id(),\n                self.version,\n                self.is_server,\n                true,\n            )?;\n\n            // Reset connection state to force sending another Initial packet.\n            self.drop_epoch_state(packet::Epoch::Initial, now);\n            self.got_peer_conn_id = false;\n            self.handshake.clear()?;\n\n            self.crypto_ctx[packet::Epoch::Initial].crypto_open = Some(aead_open);\n            self.crypto_ctx[packet::Epoch::Initial].crypto_seal = Some(aead_seal);\n\n            self.handshake\n                .use_legacy_codepoint(self.version != PROTOCOL_VERSION_V1);\n\n            // Encode transport parameters again, as the new version might be\n            // using a different format.\n            self.encode_transport_params()?;\n\n            return Err(Error::Done);\n        }\n\n        if hdr.ty == Type::Retry {\n            // Retry packets can only be sent by the server.\n            if self.is_server {\n                return Err(Error::Done);\n            }\n\n            // Ignore duplicate retry.\n            if self.did_retry {\n                return Err(Error::Done);\n            }\n\n            // Check if Retry packet is valid.\n            if packet::verify_retry_integrity(\n                &b,\n                &self.destination_id(),\n                self.version,\n            )\n            .is_err()\n            {\n                return Err(Error::Done);\n            }\n\n            trace!(\"{} rx pkt {:?}\", self.trace_id, hdr);\n\n            self.token = hdr.token;\n            self.did_retry = true;\n\n            // Remember peer's new connection ID.\n            self.odcid = Some(self.destination_id().into_owned());\n\n            self.set_initial_dcid(\n                hdr.scid.clone(),\n                None,\n                self.paths.get_active_path_id()?,\n            )?;\n\n            self.rscid = Some(self.destination_id().into_owned());\n\n            // Derive Initial secrets using the new connection ID.\n            let (aead_open, aead_seal) = crypto::derive_initial_key_material(\n                &hdr.scid,\n                self.version,\n                self.is_server,\n                true,\n            )?;\n\n            // Reset connection state to force sending another Initial packet.\n            self.drop_epoch_state(packet::Epoch::Initial, now);\n            self.got_peer_conn_id = false;\n            self.handshake.clear()?;\n\n            self.crypto_ctx[packet::Epoch::Initial].crypto_open = Some(aead_open);\n            self.crypto_ctx[packet::Epoch::Initial].crypto_seal = Some(aead_seal);\n\n            return Err(Error::Done);\n        }\n\n        if self.is_server && !self.did_version_negotiation {\n            if !version_is_supported(hdr.version) {\n                return Err(Error::UnknownVersion);\n            }\n\n            self.version = hdr.version;\n            self.did_version_negotiation = true;\n\n            self.handshake\n                .use_legacy_codepoint(self.version != PROTOCOL_VERSION_V1);\n\n            // Encode transport parameters again, as the new version might be\n            // using a different format.\n            self.encode_transport_params()?;\n        }\n\n        if hdr.ty != Type::Short && hdr.version != self.version {\n            // At this point version negotiation was already performed, so\n            // ignore packets that don't match the connection's version.\n            return Err(Error::Done);\n        }\n\n        // Long header packets have an explicit payload length, but short\n        // packets don't so just use the remaining capacity in the buffer.\n        let payload_len = if hdr.ty == Type::Short {\n            b.cap()\n        } else {\n            b.get_varint().map_err(|e| {\n                drop_pkt_on_err(\n                    e.into(),\n                    self.recv_count,\n                    self.is_server,\n                    &self.trace_id,\n                )\n            })? as usize\n        };\n\n        // Make sure the buffer is same or larger than an explicit\n        // payload length.\n        if payload_len > b.cap() {\n            return Err(drop_pkt_on_err(\n                Error::InvalidPacket,\n                self.recv_count,\n                self.is_server,\n                &self.trace_id,\n            ));\n        }\n\n        // Derive initial secrets on the server.\n        if !self.derived_initial_secrets {\n            let (aead_open, aead_seal) = crypto::derive_initial_key_material(\n                &hdr.dcid,\n                self.version,\n                self.is_server,\n                false,\n            )?;\n\n            self.crypto_ctx[packet::Epoch::Initial].crypto_open = Some(aead_open);\n            self.crypto_ctx[packet::Epoch::Initial].crypto_seal = Some(aead_seal);\n\n            self.derived_initial_secrets = true;\n        }\n\n        // Select packet number space epoch based on the received packet's type.\n        let epoch = hdr.ty.to_epoch()?;\n\n        // Select AEAD context used to open incoming packet.\n        let aead = if hdr.ty == Type::ZeroRTT {\n            // Only use 0-RTT key if incoming packet is 0-RTT.\n            self.crypto_ctx[epoch].crypto_0rtt_open.as_ref()\n        } else {\n            // Otherwise use the packet number space's main key.\n            self.crypto_ctx[epoch].crypto_open.as_ref()\n        };\n\n        // Finally, discard packet if no usable key is available.\n        let mut aead = match aead {\n            Some(v) => v,\n\n            None => {\n                if hdr.ty == Type::ZeroRTT &&\n                    self.undecryptable_pkts.len() < MAX_UNDECRYPTABLE_PACKETS &&\n                    !self.is_established()\n                {\n                    // Buffer 0-RTT packets when the required read key is not\n                    // available yet, and process them later.\n                    //\n                    // TODO: in the future we might want to buffer other types\n                    // of undecryptable packets as well.\n                    let pkt_len = b.off() + payload_len;\n                    let pkt = (b.buf()[..pkt_len]).to_vec();\n\n                    self.undecryptable_pkts.push_back((pkt, *info));\n                    return Ok(pkt_len);\n                }\n\n                let e = drop_pkt_on_err(\n                    Error::CryptoFail,\n                    self.recv_count,\n                    self.is_server,\n                    &self.trace_id,\n                );\n\n                return Err(e);\n            },\n        };\n\n        let aead_tag_len = aead.alg().tag_len();\n\n        packet::decrypt_hdr(&mut b, &mut hdr, aead).map_err(|e| {\n            drop_pkt_on_err(e, self.recv_count, self.is_server, &self.trace_id)\n        })?;\n\n        let pn = packet::decode_pkt_num(\n            self.pkt_num_spaces[epoch].largest_rx_pkt_num,\n            hdr.pkt_num,\n            hdr.pkt_num_len,\n        );\n\n        let pn_len = hdr.pkt_num_len;\n\n        trace!(\n            \"{} rx pkt {:?} len={} pn={} {}\",\n            self.trace_id,\n            hdr,\n            payload_len,\n            pn,\n            AddrTupleFmt(info.from, info.to)\n        );\n\n        #[cfg(feature = \"qlog\")]\n        let mut qlog_frames = vec![];\n\n        // Check for key update.\n        let mut aead_next = None;\n\n        if self.handshake_confirmed &&\n            hdr.ty != Type::ZeroRTT &&\n            hdr.key_phase != self.key_phase\n        {\n            // Check if this packet arrived before key update.\n            if let Some(key_update) = self.crypto_ctx[epoch]\n                .key_update\n                .as_ref()\n                .and_then(|key_update| {\n                    (pn < key_update.pn_on_update).then_some(key_update)\n                })\n            {\n                aead = &key_update.crypto_open;\n            } else {\n                trace!(\"{} peer-initiated key update\", self.trace_id);\n\n                aead_next = Some((\n                    self.crypto_ctx[epoch]\n                        .crypto_open\n                        .as_ref()\n                        .unwrap()\n                        .derive_next_packet_key()?,\n                    self.crypto_ctx[epoch]\n                        .crypto_seal\n                        .as_ref()\n                        .unwrap()\n                        .derive_next_packet_key()?,\n                ));\n\n                // `aead_next` is always `Some()` at this point, so the `unwrap()`\n                // will never fail.\n                aead = &aead_next.as_ref().unwrap().0;\n            }\n        }\n\n        let mut payload = packet::decrypt_pkt(\n            &mut b,\n            pn,\n            pn_len,\n            payload_len,\n            aead,\n        )\n        .map_err(|e| {\n            drop_pkt_on_err(e, self.recv_count, self.is_server, &self.trace_id)\n        })?;\n\n        if self.pkt_num_spaces[epoch].recv_pkt_num.contains(pn) {\n            trace!(\"{} ignored duplicate packet {}\", self.trace_id, pn);\n            return Err(Error::Done);\n        }\n\n        // Packets with no frames are invalid.\n        if payload.cap() == 0 {\n            return Err(Error::InvalidPacket);\n        }\n\n        // Now that we decrypted the packet, let's see if we can map it to an\n        // existing path.\n        let recv_pid = if hdr.ty == Type::Short && self.got_peer_conn_id {\n            let pkt_dcid = ConnectionId::from_ref(&hdr.dcid);\n            self.get_or_create_recv_path_id(recv_pid, &pkt_dcid, buf_len, info)?\n        } else {\n            // During handshake, we are on the initial path.\n            self.paths.get_active_path_id()?\n        };\n\n        // The key update is verified once a packet is successfully decrypted\n        // using the new keys.\n        if let Some((open_next, seal_next)) = aead_next {\n            if !self.crypto_ctx[epoch]\n                .key_update\n                .as_ref()\n                .is_none_or(|prev| prev.update_acked)\n            {\n                // Peer has updated keys twice without awaiting confirmation.\n                return Err(Error::KeyUpdate);\n            }\n\n            trace!(\"{} key update verified\", self.trace_id);\n\n            let _ = self.crypto_ctx[epoch].crypto_seal.replace(seal_next);\n\n            let open_prev = self.crypto_ctx[epoch]\n                .crypto_open\n                .replace(open_next)\n                .unwrap();\n\n            let recv_path = self.paths.get_mut(recv_pid)?;\n\n            self.crypto_ctx[epoch].key_update = Some(packet::KeyUpdate {\n                crypto_open: open_prev,\n                pn_on_update: pn,\n                update_acked: false,\n                timer: now + (recv_path.recovery.pto() * 3),\n            });\n\n            self.key_phase = !self.key_phase;\n\n            qlog_with_type!(QLOG_PACKET_RX, self.qlog, q, {\n                let trigger = Some(\n                    qlog::events::quic::KeyUpdateOrRetiredTrigger::RemoteUpdate,\n                );\n\n                let ev_data_client =\n                    EventData::QuicKeyUpdated(qlog::events::quic::KeyUpdated {\n                        key_type: qlog::events::quic::KeyType::Client1RttSecret,\n                        trigger: trigger.clone(),\n                        ..Default::default()\n                    });\n\n                q.add_event_data_with_instant(ev_data_client, now).ok();\n\n                let ev_data_server =\n                    EventData::QuicKeyUpdated(qlog::events::quic::KeyUpdated {\n                        key_type: qlog::events::quic::KeyType::Server1RttSecret,\n                        trigger,\n                        ..Default::default()\n                    });\n\n                q.add_event_data_with_instant(ev_data_server, now).ok();\n            });\n        }\n\n        if !self.is_server && !self.got_peer_conn_id {\n            if self.odcid.is_none() {\n                self.odcid = Some(self.destination_id().into_owned());\n            }\n\n            // Replace the randomly generated destination connection ID with\n            // the one supplied by the server.\n            self.set_initial_dcid(\n                hdr.scid.clone(),\n                self.peer_transport_params.stateless_reset_token,\n                recv_pid,\n            )?;\n\n            self.got_peer_conn_id = true;\n        }\n\n        if self.is_server && !self.got_peer_conn_id {\n            self.set_initial_dcid(hdr.scid.clone(), None, recv_pid)?;\n\n            if !self.did_retry {\n                self.local_transport_params\n                    .original_destination_connection_id =\n                    Some(hdr.dcid.to_vec().into());\n\n                self.encode_transport_params()?;\n            }\n\n            self.got_peer_conn_id = true;\n        }\n\n        // To avoid sending an ACK in response to an ACK-only packet, we need\n        // to keep track of whether this packet contains any frame other than\n        // ACK and PADDING.\n        let mut ack_elicited = false;\n\n        // Process packet payload. If a frame cannot be processed, store the\n        // error and stop further packet processing.\n        let mut frame_processing_err = None;\n\n        // To know if the peer migrated the connection, we need to keep track\n        // whether this is a non-probing packet.\n        let mut probing = true;\n\n        // Process packet payload.\n        while payload.cap() > 0 {\n            let frame = frame::Frame::from_bytes(&mut payload, hdr.ty)?;\n\n            qlog_with_type!(QLOG_PACKET_RX, self.qlog, _q, {\n                qlog_frames.push(frame.to_qlog());\n            });\n\n            if frame.ack_eliciting() {\n                ack_elicited = true;\n            }\n\n            if !frame.probing() {\n                probing = false;\n            }\n\n            if let Err(e) = self.process_frame(frame, &hdr, recv_pid, epoch, now)\n            {\n                frame_processing_err = Some(e);\n                break;\n            }\n        }\n\n        qlog_with_type!(QLOG_PACKET_RX, self.qlog, q, {\n            let packet_size = b.len();\n\n            let qlog_pkt_hdr = qlog::events::quic::PacketHeader::with_type(\n                hdr.ty.to_qlog(),\n                Some(pn),\n                Some(hdr.version),\n                Some(&hdr.scid),\n                Some(&hdr.dcid),\n            );\n\n            let qlog_raw_info = RawInfo {\n                length: Some(packet_size as u64),\n                payload_length: Some(payload_len as u64),\n                data: None,\n            };\n\n            let ev_data = EventData::QuicPacketReceived(\n                qlog::events::quic::PacketReceived {\n                    header: qlog_pkt_hdr,\n                    frames: Some(qlog_frames),\n                    raw: Some(qlog_raw_info),\n                    ..Default::default()\n                },\n            );\n\n            q.add_event_data_with_instant(ev_data, now).ok();\n        });\n\n        qlog_with_type!(QLOG_PACKET_RX, self.qlog, q, {\n            let recv_path = self.paths.get_mut(recv_pid)?;\n            recv_path.recovery.maybe_qlog(q, now);\n        });\n\n        if let Some(e) = frame_processing_err {\n            // Any frame error is terminal, so now just return.\n            return Err(e);\n        }\n\n        // Only log the remote transport parameters once the connection is\n        // established (i.e. after frames have been fully parsed) and only\n        // once per connection.\n        if self.is_established() {\n            qlog_with_type!(QLOG_PARAMS_SET, self.qlog, q, {\n                if !self.qlog.logged_peer_params {\n                    let ev_data = self.peer_transport_params.to_qlog(\n                        TransportInitiator::Remote,\n                        self.handshake.cipher(),\n                    );\n\n                    q.add_event_data_with_instant(ev_data, now).ok();\n\n                    self.qlog.logged_peer_params = true;\n                }\n            });\n        }\n\n        // Process acked frames. Note that several packets from several paths\n        // might have been acked by the received packet.\n        for (_, p) in self.paths.iter_mut() {\n            while let Some(acked) = p.recovery.next_acked_frame(epoch) {\n                match acked {\n                    frame::Frame::Ping {\n                        mtu_probe: Some(mtu_probe),\n                    } => {\n                        if let Some(pmtud) = p.pmtud.as_mut() {\n                            trace!(\n                                \"{} pmtud probe acked; probe size {:?}\",\n                                self.trace_id,\n                                mtu_probe\n                            );\n\n                            // Ensure the probe is within the supported MTU range\n                            // before updating the max datagram size\n                            if let Some(current_mtu) =\n                                pmtud.successful_probe(mtu_probe)\n                            {\n                                qlog_with_type!(\n                                    EventType::QuicEventType(\n                                        QuicEventType::MtuUpdated\n                                    ),\n                                    self.qlog,\n                                    q,\n                                    {\n                                        let pmtu_data = EventData::QuicMtuUpdated(\n                                            qlog::events::quic::MtuUpdated {\n                                                old: Some(\n                                                    p.recovery.max_datagram_size()\n                                                        as u32,\n                                                ),\n                                                new: current_mtu as u32,\n                                                done: Some(true),\n                                            },\n                                        );\n\n                                        q.add_event_data_with_instant(\n                                            pmtu_data, now,\n                                        )\n                                        .ok();\n                                    }\n                                );\n\n                                p.recovery\n                                    .pmtud_update_max_datagram_size(current_mtu);\n                            }\n                        }\n                    },\n\n                    frame::Frame::ACK { ranges, .. } => {\n                        // Stop acknowledging packets less than or equal to the\n                        // largest acknowledged in the sent ACK frame that, in\n                        // turn, got acked.\n                        if let Some(largest_acked) = ranges.last() {\n                            self.pkt_num_spaces[epoch]\n                                .recv_pkt_need_ack\n                                .remove_until(largest_acked);\n                        }\n                    },\n\n                    frame::Frame::CryptoHeader { offset, length } => {\n                        self.crypto_ctx[epoch]\n                            .crypto_stream\n                            .send\n                            .ack_and_drop(offset, length);\n                    },\n\n                    frame::Frame::StreamHeader {\n                        stream_id,\n                        offset,\n                        length,\n                        ..\n                    } => {\n                        // Update tx_buffered and emit qlog before checking if the\n                        // stream still exists.  The client does need to ACK\n                        // frames that were received after the client sends a\n                        // ResetStream.\n                        self.tx_buffered =\n                            self.tx_buffered.saturating_sub(length);\n\n                        qlog_with_type!(QLOG_DATA_MV, self.qlog, q, {\n                            let ev_data = EventData::QuicStreamDataMoved(\n                                qlog::events::quic::StreamDataMoved {\n                                    stream_id: Some(stream_id),\n                                    offset: Some(offset),\n                                    raw: Some(RawInfo {\n                                        length: Some(length as u64),\n                                        ..Default::default()\n                                    }),\n                                    from: Some(DataRecipient::Transport),\n                                    to: Some(DataRecipient::Dropped),\n                                    ..Default::default()\n                                },\n                            );\n\n                            q.add_event_data_with_instant(ev_data, now).ok();\n                        });\n\n                        let stream = match self.streams.get_mut(stream_id) {\n                            Some(v) => v,\n\n                            None => continue,\n                        };\n\n                        stream.send.ack_and_drop(offset, length);\n\n                        let priority_key = Arc::clone(&stream.priority_key);\n\n                        // Only collect the stream if it is complete and not\n                        // readable or writable.\n                        //\n                        // If it is readable, it will get collected when\n                        // stream_recv() is next used.\n                        //\n                        // If it is writable, it might mean that the stream\n                        // has been stopped by the peer (i.e. a STOP_SENDING\n                        // frame is received), in which case before collecting\n                        // the stream we will need to propagate the\n                        // `StreamStopped` error to the application. It will\n                        // instead get collected when one of stream_capacity(),\n                        // stream_writable(), stream_send(), ... is next called.\n                        //\n                        // Note that we can't use `is_writable()` here because\n                        // it returns false if the stream is stopped. Instead,\n                        // since the stream is marked as writable when a\n                        // STOP_SENDING frame is received, we check the writable\n                        // queue directly instead.\n                        let is_writable = priority_key.writable.is_linked() &&\n                            // Ensure that the stream is actually stopped.\n                            stream.send.is_stopped();\n\n                        let is_complete = stream.is_complete();\n                        let is_readable = stream.is_readable();\n\n                        if is_complete && !is_readable && !is_writable {\n                            let local = stream.local;\n                            self.streams.collect(stream_id, local);\n                        }\n                    },\n\n                    frame::Frame::HandshakeDone => {\n                        // Explicitly set this to true, so that if the frame was\n                        // already scheduled for retransmission, it is aborted.\n                        self.handshake_done_sent = true;\n\n                        self.handshake_done_acked = true;\n                    },\n\n                    frame::Frame::ResetStream { stream_id, .. } => {\n                        let stream = match self.streams.get_mut(stream_id) {\n                            Some(v) => v,\n\n                            None => continue,\n                        };\n\n                        let priority_key = Arc::clone(&stream.priority_key);\n\n                        // Only collect the stream if it is complete and not\n                        // readable or writable.\n                        //\n                        // If it is readable, it will get collected when\n                        // stream_recv() is next used.\n                        //\n                        // If it is writable, it might mean that the stream\n                        // has been stopped by the peer (i.e. a STOP_SENDING\n                        // frame is received), in which case before collecting\n                        // the stream we will need to propagate the\n                        // `StreamStopped` error to the application. It will\n                        // instead get collected when one of stream_capacity(),\n                        // stream_writable(), stream_send(), ... is next called.\n                        //\n                        // Note that we can't use `is_writable()` here because\n                        // it returns false if the stream is stopped. Instead,\n                        // since the stream is marked as writable when a\n                        // STOP_SENDING frame is received, we check the writable\n                        // queue directly instead.\n                        let is_writable = priority_key.writable.is_linked() &&\n                            // Ensure that the stream is actually stopped.\n                            stream.send.is_stopped();\n\n                        let is_complete = stream.is_complete();\n                        let is_readable = stream.is_readable();\n\n                        if is_complete && !is_readable && !is_writable {\n                            let local = stream.local;\n                            self.streams.collect(stream_id, local);\n                        }\n                    },\n\n                    _ => (),\n                }\n            }\n        }\n\n        // Now that we processed all the frames, if there is a path that has no\n        // Destination CID, try to allocate one.\n        let no_dcid = self\n            .paths\n            .iter_mut()\n            .filter(|(_, p)| p.active_dcid_seq.is_none());\n\n        for (pid, p) in no_dcid {\n            if self.ids.zero_length_dcid() {\n                p.active_dcid_seq = Some(0);\n                continue;\n            }\n\n            let dcid_seq = match self.ids.lowest_available_dcid_seq() {\n                Some(seq) => seq,\n                None => break,\n            };\n\n            self.ids.link_dcid_to_path_id(dcid_seq, pid)?;\n\n            p.active_dcid_seq = Some(dcid_seq);\n        }\n\n        // We only record the time of arrival of the largest packet number\n        // that still needs to be acked, to be used for ACK delay calculation.\n        if self.pkt_num_spaces[epoch].recv_pkt_need_ack.last() < Some(pn) {\n            self.pkt_num_spaces[epoch].largest_rx_pkt_time = now;\n        }\n\n        self.pkt_num_spaces[epoch].recv_pkt_num.insert(pn);\n\n        self.pkt_num_spaces[epoch].recv_pkt_need_ack.push_item(pn);\n\n        self.pkt_num_spaces[epoch].ack_elicited =\n            cmp::max(self.pkt_num_spaces[epoch].ack_elicited, ack_elicited);\n\n        self.pkt_num_spaces[epoch].largest_rx_pkt_num =\n            cmp::max(self.pkt_num_spaces[epoch].largest_rx_pkt_num, pn);\n\n        if !probing {\n            self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num = cmp::max(\n                self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num,\n                pn,\n            );\n\n            // Did the peer migrated to another path?\n            let active_path_id = self.paths.get_active_path_id()?;\n\n            if self.is_server &&\n                recv_pid != active_path_id &&\n                self.pkt_num_spaces[epoch].largest_rx_non_probing_pkt_num == pn\n            {\n                self.on_peer_migrated(recv_pid, self.disable_dcid_reuse, now)?;\n            }\n        }\n\n        if let Some(idle_timeout) = self.idle_timeout() {\n            self.idle_timer = Some(now + idle_timeout);\n        }\n\n        // Update send capacity.\n        self.update_tx_cap();\n\n        self.recv_count += 1;\n        self.paths.get_mut(recv_pid)?.recv_count += 1;\n\n        let read = b.off() + aead_tag_len;\n\n        self.recv_bytes += read as u64;\n        self.paths.get_mut(recv_pid)?.recv_bytes += read as u64;\n\n        // An Handshake packet has been received from the client and has been\n        // successfully processed, so we can drop the initial state and consider\n        // the client's address to be verified.\n        if self.is_server && hdr.ty == Type::Handshake {\n            self.drop_epoch_state(packet::Epoch::Initial, now);\n\n            self.paths.get_mut(recv_pid)?.verified_peer_address = true;\n        }\n\n        self.ack_eliciting_sent = false;\n\n        Ok(read)\n    }\n\n    /// Writes a single QUIC packet to be sent to the peer.\n    ///\n    /// On success the number of bytes written to the output buffer is\n    /// returned, or [`Done`] if there was nothing to write.\n    ///\n    /// The application should call `send()` multiple times until [`Done`] is\n    /// returned, indicating that there are no more packets to send. It is\n    /// recommended that `send()` be called in the following cases:\n    ///\n    ///  * When the application receives QUIC packets from the peer (that is,\n    ///    any time [`recv()`] is also called).\n    ///\n    ///  * When the connection timer expires (that is, any time [`on_timeout()`]\n    ///    is also called).\n    ///\n    ///  * When the application sends data to the peer (for example, any time\n    ///    [`stream_send()`] or [`stream_shutdown()`] are called).\n    ///\n    ///  * When the application receives data from the peer (for example any\n    ///    time [`stream_recv()`] is called).\n    ///\n    /// Once [`is_draining()`] returns `true`, it is no longer necessary to call\n    /// `send()` and all calls will return [`Done`].\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`recv()`]: struct.Connection.html#method.recv\n    /// [`on_timeout()`]: struct.Connection.html#method.on_timeout\n    /// [`stream_send()`]: struct.Connection.html#method.stream_send\n    /// [`stream_shutdown()`]: struct.Connection.html#method.stream_shutdown\n    /// [`stream_recv()`]: struct.Connection.html#method.stream_recv\n    /// [`is_draining()`]: struct.Connection.html#method.is_draining\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut out = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// loop {\n    ///     let (write, send_info) = match conn.send(&mut out) {\n    ///         Ok(v) => v,\n    ///\n    ///         Err(quiche::Error::Done) => {\n    ///             // Done writing.\n    ///             break;\n    ///         },\n    ///\n    ///         Err(e) => {\n    ///             // An error occurred, handle it.\n    ///             break;\n    ///         },\n    ///     };\n    ///\n    ///     socket.send_to(&out[..write], &send_info.to).unwrap();\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn send(&mut self, out: &mut [u8]) -> Result<(usize, SendInfo)> {\n        self.send_on_path(out, None, None)\n    }\n\n    /// Writes a single QUIC packet to be sent to the peer from the specified\n    /// local address `from` to the destination address `to`.\n    ///\n    /// The behavior of this method differs depending on the value of the `from`\n    /// and `to` parameters:\n    ///\n    ///  * If both are `Some`, then the method only consider the 4-tuple\n    ///    (`from`, `to`). Application can monitor the 4-tuple availability,\n    ///    either by monitoring [`path_event_next()`] events or by relying on\n    ///    the [`paths_iter()`] method. If the provided 4-tuple does not exist\n    ///    on the connection (anymore), it returns an [`InvalidState`].\n    ///\n    ///  * If `from` is `Some` and `to` is `None`, then the method only\n    ///    considers sending packets on paths having `from` as local address.\n    ///\n    ///  * If `to` is `Some` and `from` is `None`, then the method only\n    ///    considers sending packets on paths having `to` as peer address.\n    ///\n    ///  * If both are `None`, all available paths are considered.\n    ///\n    /// On success the number of bytes written to the output buffer is\n    /// returned, or [`Done`] if there was nothing to write.\n    ///\n    /// The application should call `send_on_path()` multiple times until\n    /// [`Done`] is returned, indicating that there are no more packets to\n    /// send. It is recommended that `send_on_path()` be called in the\n    /// following cases:\n    ///\n    ///  * When the application receives QUIC packets from the peer (that is,\n    ///    any time [`recv()`] is also called).\n    ///\n    ///  * When the connection timer expires (that is, any time [`on_timeout()`]\n    ///    is also called).\n    ///\n    ///  * When the application sends data to the peer (for examples, any time\n    ///    [`stream_send()`] or [`stream_shutdown()`] are called).\n    ///\n    ///  * When the application receives data from the peer (for example any\n    ///    time [`stream_recv()`] is called).\n    ///\n    /// Once [`is_draining()`] returns `true`, it is no longer necessary to call\n    /// `send_on_path()` and all calls will return [`Done`].\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`InvalidState`]: enum.Error.html#InvalidState\n    /// [`recv()`]: struct.Connection.html#method.recv\n    /// [`on_timeout()`]: struct.Connection.html#method.on_timeout\n    /// [`stream_send()`]: struct.Connection.html#method.stream_send\n    /// [`stream_shutdown()`]: struct.Connection.html#method.stream_shutdown\n    /// [`stream_recv()`]: struct.Connection.html#method.stream_recv\n    /// [`path_event_next()`]: struct.Connection.html#method.path_event_next\n    /// [`paths_iter()`]: struct.Connection.html#method.paths_iter\n    /// [`is_draining()`]: struct.Connection.html#method.is_draining\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut out = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// loop {\n    ///     let (write, send_info) = match conn.send_on_path(&mut out, Some(local), Some(peer)) {\n    ///         Ok(v) => v,\n    ///\n    ///         Err(quiche::Error::Done) => {\n    ///             // Done writing.\n    ///             break;\n    ///         },\n    ///\n    ///         Err(e) => {\n    ///             // An error occurred, handle it.\n    ///             break;\n    ///         },\n    ///     };\n    ///\n    ///     socket.send_to(&out[..write], &send_info.to).unwrap();\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn send_on_path(\n        &mut self, out: &mut [u8], from: Option<SocketAddr>,\n        to: Option<SocketAddr>,\n    ) -> Result<(usize, SendInfo)> {\n        if out.is_empty() {\n            return Err(Error::BufferTooShort);\n        }\n\n        if self.is_closed() || self.is_draining() {\n            return Err(Error::Done);\n        }\n\n        let now = Instant::now();\n\n        if self.local_error.is_none() {\n            self.do_handshake(now)?;\n        }\n\n        // Forwarding the error value here could confuse\n        // applications, as they may not expect getting a `recv()`\n        // error when calling `send()`.\n        //\n        // We simply fall-through to sending packets, which should\n        // take care of terminating the connection as needed.\n        let _ = self.process_undecrypted_0rtt_packets();\n\n        // There's no point in trying to send a packet if the Initial secrets\n        // have not been derived yet, so return early.\n        if !self.derived_initial_secrets {\n            return Err(Error::Done);\n        }\n\n        let mut has_initial = false;\n\n        let mut done = 0;\n\n        // Limit output packet size to respect the sender and receiver's\n        // maximum UDP payload size limit.\n        let mut left = cmp::min(out.len(), self.max_send_udp_payload_size());\n\n        let send_pid = match (from, to) {\n            (Some(f), Some(t)) => self\n                .paths\n                .path_id_from_addrs(&(f, t))\n                .ok_or(Error::InvalidState)?,\n\n            _ => self.get_send_path_id(from, to)?,\n        };\n\n        let send_path = self.paths.get_mut(send_pid)?;\n\n        // Update max datagram size to allow path MTU discovery probe to be sent.\n        if let Some(pmtud) = send_path.pmtud.as_mut() {\n            if pmtud.should_probe() {\n                let size = if self.handshake_confirmed || self.handshake_completed\n                {\n                    pmtud.get_probe_size()\n                } else {\n                    pmtud.get_current_mtu()\n                };\n\n                send_path.recovery.pmtud_update_max_datagram_size(size);\n\n                left =\n                    cmp::min(out.len(), send_path.recovery.max_datagram_size());\n            }\n        }\n\n        // Limit data sent by the server based on the amount of data received\n        // from the client before its address is validated.\n        if !send_path.verified_peer_address && self.is_server {\n            left = cmp::min(left, send_path.max_send_bytes);\n        }\n\n        // Generate coalesced packets.\n        while left > 0 {\n            let (ty, written) = match self.send_single(\n                &mut out[done..done + left],\n                send_pid,\n                has_initial,\n                now,\n            ) {\n                Ok(v) => v,\n\n                Err(Error::BufferTooShort) | Err(Error::Done) => break,\n\n                Err(e) => return Err(e),\n            };\n\n            done += written;\n            left -= written;\n\n            match ty {\n                Type::Initial => has_initial = true,\n\n                // No more packets can be coalesced after a 1-RTT.\n                Type::Short => break,\n\n                _ => (),\n            };\n\n            // When sending multiple PTO probes, don't coalesce them together,\n            // so they are sent on separate UDP datagrams.\n            if let Ok(epoch) = ty.to_epoch() {\n                if self.paths.get_mut(send_pid)?.recovery.loss_probes(epoch) > 0 {\n                    break;\n                }\n            }\n\n            // Don't coalesce packets that must go on different paths.\n            if !(from.is_some() && to.is_some()) &&\n                self.get_send_path_id(from, to)? != send_pid\n            {\n                break;\n            }\n        }\n\n        if done == 0 {\n            self.last_tx_data = self.tx_data;\n\n            return Err(Error::Done);\n        }\n\n        if has_initial && left > 0 && done < MIN_CLIENT_INITIAL_LEN {\n            let pad_len = cmp::min(left, MIN_CLIENT_INITIAL_LEN - done);\n\n            // Fill padding area with null bytes, to avoid leaking information\n            // in case the application reuses the packet buffer.\n            out[done..done + pad_len].fill(0);\n\n            done += pad_len;\n        }\n\n        let send_path = self.paths.get(send_pid)?;\n\n        let info = SendInfo {\n            from: send_path.local_addr(),\n            to: send_path.peer_addr(),\n\n            at: send_path.recovery.get_packet_send_time(now),\n        };\n\n        Ok((done, info))\n    }\n\n    fn send_single(\n        &mut self, out: &mut [u8], send_pid: usize, has_initial: bool,\n        now: Instant,\n    ) -> Result<(Type, usize)> {\n        if out.is_empty() {\n            return Err(Error::BufferTooShort);\n        }\n\n        if self.is_draining() {\n            return Err(Error::Done);\n        }\n\n        let is_closing = self.local_error.is_some();\n\n        let out_len = out.len();\n\n        let mut b = octets::OctetsMut::with_slice(out);\n\n        let pkt_type = self.write_pkt_type(send_pid)?;\n\n        let max_dgram_len = if !self.dgram_send_queue.is_empty() {\n            self.dgram_max_writable_len()\n        } else {\n            None\n        };\n\n        let epoch = pkt_type.to_epoch()?;\n        let pkt_space = &mut self.pkt_num_spaces[epoch];\n        let crypto_ctx = &mut self.crypto_ctx[epoch];\n\n        // Process lost frames. There might be several paths having lost frames.\n        for (_, p) in self.paths.iter_mut() {\n            while let Some(lost) = p.recovery.next_lost_frame(epoch) {\n                match lost {\n                    frame::Frame::CryptoHeader { offset, length } => {\n                        crypto_ctx.crypto_stream.send.retransmit(offset, length);\n\n                        self.stream_retrans_bytes += length as u64;\n                        p.stream_retrans_bytes += length as u64;\n\n                        self.retrans_count += 1;\n                        p.retrans_count += 1;\n                    },\n\n                    frame::Frame::StreamHeader {\n                        stream_id,\n                        offset,\n                        length,\n                        fin,\n                    } => {\n                        let stream = match self.streams.get_mut(stream_id) {\n                            // Only retransmit data if the stream is not closed\n                            // or stopped.\n                            Some(v) if !v.send.is_stopped() => v,\n\n                            // Data on a closed stream will not be retransmitted\n                            // or acked after it is declared lost, so update\n                            // tx_buffered and qlog.\n                            _ => {\n                                self.tx_buffered =\n                                    self.tx_buffered.saturating_sub(length);\n\n                                qlog_with_type!(QLOG_DATA_MV, self.qlog, q, {\n                                    let ev_data = EventData::QuicStreamDataMoved(\n                                        qlog::events::quic::StreamDataMoved {\n                                            stream_id: Some(stream_id),\n                                            offset: Some(offset),\n                                            raw: Some(RawInfo {\n                                                length: Some(length as u64),\n                                                ..Default::default()\n                                            }),\n                                            from: Some(DataRecipient::Transport),\n                                            to: Some(DataRecipient::Dropped),\n                                            ..Default::default()\n                                        },\n                                    );\n\n                                    q.add_event_data_with_instant(ev_data, now)\n                                        .ok();\n                                });\n\n                                continue;\n                            },\n                        };\n\n                        let was_flushable = stream.is_flushable();\n\n                        let empty_fin = length == 0 && fin;\n\n                        stream.send.retransmit(offset, length);\n\n                        // If the stream is now flushable push it to the\n                        // flushable queue, but only if it wasn't already\n                        // queued.\n                        //\n                        // Consider the stream flushable also when we are\n                        // sending a zero-length frame that has the fin flag\n                        // set.\n                        if (stream.is_flushable() || empty_fin) && !was_flushable\n                        {\n                            let priority_key = Arc::clone(&stream.priority_key);\n                            self.streams.insert_flushable(&priority_key);\n                        }\n\n                        self.stream_retrans_bytes += length as u64;\n                        p.stream_retrans_bytes += length as u64;\n\n                        self.retrans_count += 1;\n                        p.retrans_count += 1;\n                    },\n\n                    frame::Frame::ACK { .. } => {\n                        pkt_space.ack_elicited = true;\n                    },\n\n                    frame::Frame::ResetStream {\n                        stream_id,\n                        error_code,\n                        final_size,\n                    } => {\n                        self.streams\n                            .insert_reset(stream_id, error_code, final_size);\n                    },\n\n                    frame::Frame::StopSending {\n                        stream_id,\n                        error_code,\n                    } =>\n                    // We only need to retransmit the STOP_SENDING frame if\n                    // the stream is still active and not FIN'd. Even if the\n                    // packet was lost, if the application has the final\n                    // size at this point there is no need to retransmit.\n                        if let Some(stream) = self.streams.get(stream_id) {\n                            if !stream.recv.is_fin() {\n                                self.streams\n                                    .insert_stopped(stream_id, error_code);\n                            }\n                        },\n\n                    // Retransmit HANDSHAKE_DONE only if it hasn't been acked at\n                    // least once already.\n                    frame::Frame::HandshakeDone if !self.handshake_done_acked => {\n                        self.handshake_done_sent = false;\n                    },\n\n                    frame::Frame::MaxStreamData { stream_id, .. } => {\n                        if self.streams.get(stream_id).is_some() {\n                            self.streams.insert_almost_full(stream_id);\n                        }\n                    },\n\n                    frame::Frame::MaxData { .. } => {\n                        self.should_send_max_data = true;\n                    },\n\n                    frame::Frame::MaxStreamsUni { .. } => {\n                        self.should_send_max_streams_uni = true;\n                    },\n\n                    frame::Frame::MaxStreamsBidi { .. } => {\n                        self.should_send_max_streams_bidi = true;\n                    },\n\n                    // Retransmit STREAMS_BLOCKED frames if the frame with the\n                    // most recent limit is lost.  These are informational\n                    // signals to the peer, reliably sending them\n                    // ensures the signal is used consistently and helps\n                    // debugging.\n                    frame::Frame::StreamsBlockedBidi { limit } => {\n                        self.streams_blocked_bidi_state\n                            .force_retransmit_sent_limit_eq(limit);\n                    },\n\n                    frame::Frame::StreamsBlockedUni { limit } => {\n                        self.streams_blocked_uni_state\n                            .force_retransmit_sent_limit_eq(limit);\n                    },\n\n                    frame::Frame::NewConnectionId { seq_num, .. } => {\n                        self.ids.mark_advertise_new_scid_seq(seq_num, true);\n                    },\n\n                    frame::Frame::RetireConnectionId { seq_num } => {\n                        self.ids.mark_retire_dcid_seq(seq_num, true)?;\n                    },\n\n                    frame::Frame::Ping {\n                        mtu_probe: Some(failed_probe),\n                    } =>\n                        if let Some(pmtud) = p.pmtud.as_mut() {\n                            trace!(\"pmtud probe dropped: {failed_probe}\");\n                            pmtud.failed_probe(failed_probe);\n                        },\n\n                    _ => (),\n                }\n            }\n        }\n        self.check_tx_buffered_invariant();\n\n        let is_app_limited = self.delivery_rate_check_if_app_limited();\n        let n_paths = self.paths.len();\n        let path = self.paths.get_mut(send_pid)?;\n        let flow_control = &mut self.flow_control;\n        let pkt_space = &mut self.pkt_num_spaces[epoch];\n        let crypto_ctx = &mut self.crypto_ctx[epoch];\n        let pkt_num_manager = &mut self.pkt_num_manager;\n\n        let mut left = if let Some(pmtud) = path.pmtud.as_mut() {\n            // Limit output buffer size by estimated path MTU.\n            cmp::min(pmtud.get_current_mtu(), b.cap())\n        } else {\n            b.cap()\n        };\n\n        if pkt_num_manager.should_skip_pn(self.handshake_completed) {\n            pkt_num_manager.set_skip_pn(Some(self.next_pkt_num));\n            self.next_pkt_num += 1;\n        };\n        let pn = self.next_pkt_num;\n\n        let largest_acked_pkt =\n            path.recovery.get_largest_acked_on_epoch(epoch).unwrap_or(0);\n        let pn_len = packet::pkt_num_len(pn, largest_acked_pkt);\n\n        // The AEAD overhead at the current encryption level.\n        let crypto_overhead = crypto_ctx.crypto_overhead().ok_or(Error::Done)?;\n\n        let dcid_seq = path.active_dcid_seq.ok_or(Error::OutOfIdentifiers)?;\n\n        let dcid =\n            ConnectionId::from_ref(self.ids.get_dcid(dcid_seq)?.cid.as_ref());\n\n        let scid = if let Some(scid_seq) = path.active_scid_seq {\n            ConnectionId::from_ref(self.ids.get_scid(scid_seq)?.cid.as_ref())\n        } else if pkt_type == Type::Short {\n            ConnectionId::default()\n        } else {\n            return Err(Error::InvalidState);\n        };\n\n        let hdr = Header {\n            ty: pkt_type,\n\n            version: self.version,\n\n            dcid,\n            scid,\n\n            pkt_num: 0,\n            pkt_num_len: pn_len,\n\n            // Only clone token for Initial packets, as other packets don't have\n            // this field (Retry doesn't count, as it's not encoded as part of\n            // this code path).\n            token: if pkt_type == Type::Initial {\n                self.token.clone()\n            } else {\n                None\n            },\n\n            versions: None,\n            key_phase: self.key_phase,\n        };\n\n        hdr.to_bytes(&mut b)?;\n\n        let hdr_trace = if log::max_level() == log::LevelFilter::Trace {\n            Some(format!(\"{hdr:?}\"))\n        } else {\n            None\n        };\n\n        let hdr_ty = hdr.ty;\n\n        #[cfg(feature = \"qlog\")]\n        let qlog_pkt_hdr = self.qlog.streamer.as_ref().map(|_q| {\n            qlog::events::quic::PacketHeader::with_type(\n                hdr.ty.to_qlog(),\n                Some(pn),\n                Some(hdr.version),\n                Some(&hdr.scid),\n                Some(&hdr.dcid),\n            )\n        });\n\n        // Calculate the space required for the packet, including the header\n        // the payload length, the packet number and the AEAD overhead.\n        let mut overhead = b.off() + pn_len + crypto_overhead;\n\n        // We assume that the payload length, which is only present in long\n        // header packets, can always be encoded with a 2-byte varint.\n        if pkt_type != Type::Short {\n            overhead += PAYLOAD_LENGTH_LEN;\n        }\n\n        // Make sure we have enough space left for the packet overhead.\n        match left.checked_sub(overhead) {\n            Some(v) => left = v,\n\n            None => {\n                // We can't send more because there isn't enough space available\n                // in the output buffer.\n                //\n                // This usually happens when we try to send a new packet but\n                // failed because cwnd is almost full. In such case app_limited\n                // is set to false here to make cwnd grow when ACK is received.\n                path.recovery.update_app_limited(false);\n                return Err(Error::Done);\n            },\n        }\n\n        // Make sure there is enough space for the minimum payload length.\n        if left < PAYLOAD_MIN_LEN {\n            path.recovery.update_app_limited(false);\n            return Err(Error::Done);\n        }\n\n        let mut frames: SmallVec<[frame::Frame; 1]> = SmallVec::new();\n\n        let mut ack_eliciting = false;\n        let mut in_flight = false;\n        let mut is_pmtud_probe = false;\n        let mut has_data = false;\n\n        // Whether or not we should explicitly elicit an ACK via PING frame if we\n        // implicitly elicit one otherwise.\n        let ack_elicit_required = path.recovery.should_elicit_ack(epoch);\n\n        let header_offset = b.off();\n\n        // Reserve space for payload length in advance. Since we don't yet know\n        // what the final length will be, we reserve 2 bytes in all cases.\n        //\n        // Only long header packets have an explicit length field.\n        if pkt_type != Type::Short {\n            b.skip(PAYLOAD_LENGTH_LEN)?;\n        }\n\n        packet::encode_pkt_num(pn, pn_len, &mut b)?;\n\n        let payload_offset = b.off();\n\n        let cwnd_available =\n            path.recovery.cwnd_available().saturating_sub(overhead);\n\n        let left_before_packing_ack_frame = left;\n\n        // Create ACK frame.\n        //\n        // When we need to explicitly elicit an ACK via PING later, go ahead and\n        // generate an ACK (if there's anything to ACK) since we're going to\n        // send a packet with PING anyways, even if we haven't received anything\n        // ACK eliciting.\n        if pkt_space.recv_pkt_need_ack.len() > 0 &&\n            (pkt_space.ack_elicited || ack_elicit_required) &&\n            (!is_closing ||\n                (pkt_type == Type::Handshake &&\n                    self.local_error\n                        .as_ref()\n                        .is_some_and(|le| le.is_app))) &&\n            path.active()\n        {\n            #[cfg(not(feature = \"fuzzing\"))]\n            let ack_delay = pkt_space.largest_rx_pkt_time.elapsed();\n\n            #[cfg(not(feature = \"fuzzing\"))]\n            let ack_delay = ack_delay.as_micros() as u64 /\n                2_u64\n                    .pow(self.local_transport_params.ack_delay_exponent as u32);\n\n            // pseudo-random reproducible ack delays when fuzzing\n            #[cfg(feature = \"fuzzing\")]\n            let ack_delay = rand::rand_u8() as u64 + 1;\n\n            let frame = frame::Frame::ACK {\n                ack_delay,\n                ranges: pkt_space.recv_pkt_need_ack.clone(),\n                ecn_counts: None, // sending ECN is not supported at this time\n            };\n\n            // When a PING frame needs to be sent, avoid sending the ACK if\n            // there is not enough cwnd available for both (note that PING\n            // frames are always 1 byte, so we just need to check that the\n            // ACK's length is lower than cwnd).\n            if pkt_space.ack_elicited || frame.wire_len() < cwnd_available {\n                // ACK-only packets are not congestion controlled so ACKs must\n                // be bundled considering the buffer capacity only, and not the\n                // available cwnd.\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    pkt_space.ack_elicited = false;\n                }\n            }\n        }\n\n        // Limit output packet size by congestion window size.\n        left = cmp::min(\n            left,\n            // Bytes consumed by ACK frames.\n            cwnd_available.saturating_sub(left_before_packing_ack_frame - left),\n        );\n\n        let mut challenge_data = None;\n\n        if pkt_type == Type::Short {\n            // Create PMTUD probe.\n            //\n            // In order to send a PMTUD probe the current `left` value, which was\n            // already limited by the current PMTU measure, needs to be ignored,\n            // but the outgoing packet still needs to be limited by\n            // the output buffer size, as well as the congestion\n            // window.\n            //\n            // In addition, the PMTUD probe is only generated when the handshake\n            // is confirmed, to avoid interfering with the handshake\n            // (e.g. due to the anti-amplification limits).\n            if let Ok(active_path) = self.paths.get_active_mut() {\n                let should_probe_pmtu = active_path.should_send_pmtu_probe(\n                    self.handshake_confirmed,\n                    self.handshake_completed,\n                    out_len,\n                    is_closing,\n                    frames.is_empty(),\n                );\n\n                if should_probe_pmtu {\n                    if let Some(pmtud) = active_path.pmtud.as_mut() {\n                        let probe_size = pmtud.get_probe_size();\n                        trace!(\n                        \"{} sending pmtud probe pmtu_probe={} estimated_pmtu={}\",\n                        self.trace_id,\n                        probe_size,\n                        pmtud.get_current_mtu(),\n                    );\n\n                        left = probe_size;\n\n                        match left.checked_sub(overhead) {\n                            Some(v) => left = v,\n\n                            None => {\n                                // We can't send more because there isn't enough\n                                // space available in the output buffer.\n                                //\n                                // This usually happens when we try to send a new\n                                // packet but failed because cwnd is almost full.\n                                //\n                                // In such case app_limited is set to false here\n                                // to make cwnd grow when ACK is received.\n                                active_path.recovery.update_app_limited(false);\n                                return Err(Error::Done);\n                            },\n                        }\n\n                        let frame = frame::Frame::Padding {\n                            len: probe_size - overhead - 1,\n                        };\n\n                        if push_frame_to_pkt!(b, frames, frame, left) {\n                            let frame = frame::Frame::Ping {\n                                mtu_probe: Some(probe_size),\n                            };\n\n                            if push_frame_to_pkt!(b, frames, frame, left) {\n                                ack_eliciting = true;\n                                in_flight = true;\n                            }\n                        }\n\n                        // Reset probe flag after sending to prevent duplicate\n                        // probes in a single flight.\n                        pmtud.set_in_flight(true);\n                        is_pmtud_probe = true;\n                    }\n                }\n            }\n\n            let path = self.paths.get_mut(send_pid)?;\n            // Create PATH_RESPONSE frame if needed.\n            // We do not try to ensure that these are really sent.\n            while let Some(challenge) = path.pop_received_challenge() {\n                let frame = frame::Frame::PathResponse { data: challenge };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    ack_eliciting = true;\n                    in_flight = true;\n                } else {\n                    // If there are other pending PATH_RESPONSE, don't lose them\n                    // now.\n                    break;\n                }\n            }\n\n            // Create PATH_CHALLENGE frame if needed.\n            if path.validation_requested() {\n                // TODO: ensure that data is unique over paths.\n                let data = rand::rand_u64().to_be_bytes();\n\n                let frame = frame::Frame::PathChallenge { data };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    // Let's notify the path once we know the packet size.\n                    challenge_data = Some(data);\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            if let Some(key_update) = crypto_ctx.key_update.as_mut() {\n                key_update.update_acked = true;\n            }\n        }\n\n        let path = self.paths.get_mut(send_pid)?;\n\n        if pkt_type == Type::Short && !is_closing {\n            // Create NEW_CONNECTION_ID frames as needed.\n            while let Some(seq_num) = self.ids.next_advertise_new_scid_seq() {\n                let frame = self.ids.get_new_connection_id_frame_for(seq_num)?;\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.ids.mark_advertise_new_scid_seq(seq_num, false);\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                } else {\n                    break;\n                }\n            }\n        }\n\n        if pkt_type == Type::Short && !is_closing && path.active() {\n            // Create HANDSHAKE_DONE frame.\n            // self.should_send_handshake_done() but without the need to borrow\n            if self.handshake_completed &&\n                !self.handshake_done_sent &&\n                self.is_server\n            {\n                let frame = frame::Frame::HandshakeDone;\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.handshake_done_sent = true;\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            // Create MAX_STREAMS_BIDI frame.\n            if self.streams.should_update_max_streams_bidi() ||\n                self.should_send_max_streams_bidi\n            {\n                let frame = frame::Frame::MaxStreamsBidi {\n                    max: self.streams.max_streams_bidi_next(),\n                };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.streams.update_max_streams_bidi();\n                    self.should_send_max_streams_bidi = false;\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            // Create MAX_STREAMS_UNI frame.\n            if self.streams.should_update_max_streams_uni() ||\n                self.should_send_max_streams_uni\n            {\n                let frame = frame::Frame::MaxStreamsUni {\n                    max: self.streams.max_streams_uni_next(),\n                };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.streams.update_max_streams_uni();\n                    self.should_send_max_streams_uni = false;\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            // Create DATA_BLOCKED frame.\n            if let Some(limit) = self.blocked_limit {\n                let frame = frame::Frame::DataBlocked { limit };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.blocked_limit = None;\n                    self.data_blocked_sent_count =\n                        self.data_blocked_sent_count.saturating_add(1);\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            // Create STREAMS_BLOCKED (bidi) frame when the local endpoint has\n            // exhausted the peer's bidirectional stream count limit.\n            if self\n                .streams_blocked_bidi_state\n                .has_pending_stream_blocked_frame()\n            {\n                if let Some(limit) = self.streams_blocked_bidi_state.blocked_at {\n                    let frame = frame::Frame::StreamsBlockedBidi { limit };\n\n                    if push_frame_to_pkt!(b, frames, frame, left) {\n                        // Record the limit we just notified the peer about so\n                        // that redundant frames for the same limit are\n                        // suppressed.\n                        self.streams_blocked_bidi_state.blocked_sent =\n                            Some(limit);\n\n                        ack_eliciting = true;\n                        in_flight = true;\n                    }\n                }\n            }\n\n            // Create STREAMS_BLOCKED (uni) frame when the local endpoint has\n            // exhausted the peer's unidirectional stream count limit.\n            if self\n                .streams_blocked_uni_state\n                .has_pending_stream_blocked_frame()\n            {\n                if let Some(limit) = self.streams_blocked_uni_state.blocked_at {\n                    let frame = frame::Frame::StreamsBlockedUni { limit };\n\n                    if push_frame_to_pkt!(b, frames, frame, left) {\n                        // Record the limit we just notified the peer about so\n                        // that redundant frames for the same limit are\n                        // suppressed.\n                        self.streams_blocked_uni_state.blocked_sent = Some(limit);\n\n                        ack_eliciting = true;\n                        in_flight = true;\n                    }\n                }\n            }\n\n            // Create MAX_STREAM_DATA frames as needed.\n            for stream_id in self.streams.almost_full() {\n                let stream = match self.streams.get_mut(stream_id) {\n                    Some(v) => v,\n\n                    None => {\n                        // The stream doesn't exist anymore, so remove it from\n                        // the almost full set.\n                        self.streams.remove_almost_full(stream_id);\n                        continue;\n                    },\n                };\n\n                // Autotune the stream window size, but only if this is not a\n                // retransmission (on a retransmit the stream will be in\n                // `self.streams.almost_full()` but it's `almost_full()`\n                // method returns false.\n                if stream.recv.almost_full() {\n                    stream.recv.autotune_window(now, path.recovery.rtt());\n                }\n\n                let frame = frame::Frame::MaxStreamData {\n                    stream_id,\n                    max: stream.recv.max_data_next(),\n                };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    let recv_win = stream.recv.window();\n\n                    stream.recv.update_max_data(now);\n\n                    self.streams.remove_almost_full(stream_id);\n\n                    ack_eliciting = true;\n                    in_flight = true;\n\n                    // Make sure the connection window always has some\n                    // room compared to the stream window.\n                    flow_control.ensure_window_lower_bound(\n                        (recv_win as f64 * CONNECTION_WINDOW_FACTOR) as u64,\n                    );\n                }\n            }\n\n            // Create MAX_DATA frame as needed.\n            if flow_control.should_update_max_data() &&\n                flow_control.max_data() < flow_control.max_data_next()\n            {\n                // Autotune the connection window size. We only tune the window\n                // if we are sending an \"organic\" update, not on retransmits.\n                flow_control.autotune_window(now, path.recovery.rtt());\n                self.should_send_max_data = true;\n            }\n\n            if self.should_send_max_data {\n                let frame = frame::Frame::MaxData {\n                    max: flow_control.max_data_next(),\n                };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.should_send_max_data = false;\n\n                    // Commits the new max_rx_data limit.\n                    flow_control.update_max_data(now);\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            // Create STOP_SENDING frames as needed.\n            for (stream_id, error_code) in self\n                .streams\n                .stopped()\n                .map(|(&k, &v)| (k, v))\n                .collect::<Vec<(u64, u64)>>()\n            {\n                let frame = frame::Frame::StopSending {\n                    stream_id,\n                    error_code,\n                };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.streams.remove_stopped(stream_id);\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            // Create RESET_STREAM frames as needed.\n            for (stream_id, (error_code, final_size)) in self\n                .streams\n                .reset()\n                .map(|(&k, &v)| (k, v))\n                .collect::<Vec<(u64, (u64, u64))>>()\n            {\n                let frame = frame::Frame::ResetStream {\n                    stream_id,\n                    error_code,\n                    final_size,\n                };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.streams.remove_reset(stream_id);\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            // Create STREAM_DATA_BLOCKED frames as needed.\n            for (stream_id, limit) in self\n                .streams\n                .blocked()\n                .map(|(&k, &v)| (k, v))\n                .collect::<Vec<(u64, u64)>>()\n            {\n                let frame = frame::Frame::StreamDataBlocked { stream_id, limit };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.streams.remove_blocked(stream_id);\n                    self.stream_data_blocked_sent_count =\n                        self.stream_data_blocked_sent_count.saturating_add(1);\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                }\n            }\n\n            // Create RETIRE_CONNECTION_ID frames as needed.\n            let retire_dcid_seqs = self.ids.retire_dcid_seqs();\n\n            for seq_num in retire_dcid_seqs {\n                // The sequence number specified in a RETIRE_CONNECTION_ID frame\n                // MUST NOT refer to the Destination Connection ID field of the\n                // packet in which the frame is contained.\n                let dcid_seq = path.active_dcid_seq.ok_or(Error::InvalidState)?;\n\n                if seq_num == dcid_seq {\n                    continue;\n                }\n\n                let frame = frame::Frame::RetireConnectionId { seq_num };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    self.ids.mark_retire_dcid_seq(seq_num, false)?;\n\n                    ack_eliciting = true;\n                    in_flight = true;\n                } else {\n                    break;\n                }\n            }\n        }\n\n        // Create CONNECTION_CLOSE frame. Try to send this only on the active\n        // path, unless it is the last one available.\n        if path.active() || n_paths == 1 {\n            if let Some(conn_err) = self.local_error.as_ref() {\n                if conn_err.is_app {\n                    // Create ApplicationClose frame.\n                    if pkt_type == Type::Short {\n                        let frame = frame::Frame::ApplicationClose {\n                            error_code: conn_err.error_code,\n                            reason: conn_err.reason.clone(),\n                        };\n\n                        if push_frame_to_pkt!(b, frames, frame, left) {\n                            let pto = path.recovery.pto();\n                            self.draining_timer = Some(now + (pto * 3));\n\n                            ack_eliciting = true;\n                            in_flight = true;\n                        }\n                    }\n                } else {\n                    // Create ConnectionClose frame.\n                    let frame = frame::Frame::ConnectionClose {\n                        error_code: conn_err.error_code,\n                        frame_type: 0,\n                        reason: conn_err.reason.clone(),\n                    };\n\n                    if push_frame_to_pkt!(b, frames, frame, left) {\n                        let pto = path.recovery.pto();\n                        self.draining_timer = Some(now + (pto * 3));\n\n                        ack_eliciting = true;\n                        in_flight = true;\n                    }\n                }\n            }\n        }\n\n        // Create CRYPTO frame.\n        if crypto_ctx.crypto_stream.is_flushable() &&\n            left > frame::MAX_CRYPTO_OVERHEAD &&\n            !is_closing &&\n            path.active()\n        {\n            let crypto_off = crypto_ctx.crypto_stream.send.off_front();\n\n            // Encode the frame.\n            //\n            // Instead of creating a `frame::Frame` object, encode the frame\n            // directly into the packet buffer.\n            //\n            // First we reserve some space in the output buffer for writing the\n            // frame header (we assume the length field is always a 2-byte\n            // varint as we don't know the value yet).\n            //\n            // Then we emit the data from the crypto stream's send buffer.\n            //\n            // Finally we go back and encode the frame header with the now\n            // available information.\n            let hdr_off = b.off();\n            let hdr_len = 1 + // frame type\n                octets::varint_len(crypto_off) + // offset\n                2; // length, always encode as 2-byte varint\n\n            if let Some(max_len) = left.checked_sub(hdr_len) {\n                let (mut crypto_hdr, mut crypto_payload) =\n                    b.split_at(hdr_off + hdr_len)?;\n\n                // Write stream data into the packet buffer.\n                let (len, _) = crypto_ctx\n                    .crypto_stream\n                    .send\n                    .emit(&mut crypto_payload.as_mut()[..max_len])?;\n\n                // Encode the frame's header.\n                //\n                // Due to how `OctetsMut::split_at()` works, `crypto_hdr` starts\n                // from the initial offset of `b` (rather than the current\n                // offset), so it needs to be advanced to the\n                // initial frame offset.\n                crypto_hdr.skip(hdr_off)?;\n\n                frame::encode_crypto_header(\n                    crypto_off,\n                    len as u64,\n                    &mut crypto_hdr,\n                )?;\n\n                // Advance the packet buffer's offset.\n                b.skip(hdr_len + len)?;\n\n                let frame = frame::Frame::CryptoHeader {\n                    offset: crypto_off,\n                    length: len,\n                };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    ack_eliciting = true;\n                    in_flight = true;\n                    has_data = true;\n                }\n            }\n        }\n\n        // The preference of data-bearing frame to include in a packet\n        // is managed by `self.emit_dgram`. However, whether any frames\n        // can be sent depends on the state of their buffers. In the case\n        // where one type is preferred but its buffer is empty, fall back\n        // to the other type in order not to waste this function call.\n        let mut dgram_emitted = false;\n        let dgrams_to_emit = max_dgram_len.is_some();\n        let stream_to_emit = self.streams.has_flushable();\n\n        let mut do_dgram = self.emit_dgram && dgrams_to_emit;\n        let do_stream = !self.emit_dgram && stream_to_emit;\n\n        if !do_stream && dgrams_to_emit {\n            do_dgram = true;\n        }\n\n        // Create DATAGRAM frame.\n        if (pkt_type == Type::Short || pkt_type == Type::ZeroRTT) &&\n            left > frame::MAX_DGRAM_OVERHEAD &&\n            !is_closing &&\n            path.active() &&\n            do_dgram\n        {\n            if let Some(max_dgram_payload) = max_dgram_len {\n                while let Some(len) = self.dgram_send_queue.peek_front_len() {\n                    let hdr_off = b.off();\n                    let hdr_len = 1 + // frame type\n                        2; // length, always encode as 2-byte varint\n\n                    if (hdr_len + len) <= left {\n                        // Front of the queue fits this packet, send it.\n                        match self.dgram_send_queue.pop() {\n                            Some(data) => {\n                                // Encode the frame.\n                                //\n                                // Instead of creating a `frame::Frame` object,\n                                // encode the frame directly into the packet\n                                // buffer.\n                                //\n                                // First we reserve some space in the output\n                                // buffer for writing the frame header (we\n                                // assume the length field is always a 2-byte\n                                // varint as we don't know the value yet).\n                                //\n                                // Then we emit the data from the DATAGRAM's\n                                // buffer.\n                                //\n                                // Finally we go back and encode the frame\n                                // header with the now available information.\n                                let (mut dgram_hdr, mut dgram_payload) =\n                                    b.split_at(hdr_off + hdr_len)?;\n\n                                dgram_payload.as_mut()[..len]\n                                    .copy_from_slice(&data);\n\n                                // Encode the frame's header.\n                                //\n                                // Due to how `OctetsMut::split_at()` works,\n                                // `dgram_hdr` starts from the initial offset\n                                // of `b` (rather than the current offset), so\n                                // it needs to be advanced to the initial frame\n                                // offset.\n                                dgram_hdr.skip(hdr_off)?;\n\n                                frame::encode_dgram_header(\n                                    len as u64,\n                                    &mut dgram_hdr,\n                                )?;\n\n                                // Advance the packet buffer's offset.\n                                b.skip(hdr_len + len)?;\n\n                                let frame =\n                                    frame::Frame::DatagramHeader { length: len };\n\n                                if push_frame_to_pkt!(b, frames, frame, left) {\n                                    ack_eliciting = true;\n                                    in_flight = true;\n                                    dgram_emitted = true;\n                                    self.dgram_sent_count =\n                                        self.dgram_sent_count.saturating_add(1);\n                                    path.dgram_sent_count =\n                                        path.dgram_sent_count.saturating_add(1);\n                                }\n                            },\n\n                            None => continue,\n                        };\n                    } else if len > max_dgram_payload {\n                        // This dgram frame will never fit. Let's purge it.\n                        self.dgram_send_queue.pop();\n                    } else {\n                        break;\n                    }\n                }\n            }\n        }\n\n        // Create a single STREAM frame for the first stream that is flushable.\n        if (pkt_type == Type::Short || pkt_type == Type::ZeroRTT) &&\n            left > frame::MAX_STREAM_OVERHEAD &&\n            !is_closing &&\n            path.active() &&\n            !dgram_emitted\n        {\n            while let Some(priority_key) = self.streams.peek_flushable() {\n                let stream_id = priority_key.id;\n                let stream = match self.streams.get_mut(stream_id) {\n                    // Avoid sending frames for streams that were already stopped.\n                    //\n                    // This might happen if stream data was buffered but not yet\n                    // flushed on the wire when a STOP_SENDING frame is received.\n                    Some(v) if !v.send.is_stopped() => v,\n                    _ => {\n                        self.streams.remove_flushable(&priority_key);\n                        continue;\n                    },\n                };\n\n                let stream_off = stream.send.off_front();\n\n                // Encode the frame.\n                //\n                // Instead of creating a `frame::Frame` object, encode the frame\n                // directly into the packet buffer.\n                //\n                // First we reserve some space in the output buffer for writing\n                // the frame header (we assume the length field is always a\n                // 2-byte varint as we don't know the value yet).\n                //\n                // Then we emit the data from the stream's send buffer.\n                //\n                // Finally we go back and encode the frame header with the now\n                // available information.\n                let hdr_off = b.off();\n                let hdr_len = 1 + // frame type\n                    octets::varint_len(stream_id) + // stream_id\n                    octets::varint_len(stream_off) + // offset\n                    2; // length, always encode as 2-byte varint\n\n                let max_len = match left.checked_sub(hdr_len) {\n                    Some(v) => v,\n                    None => {\n                        let priority_key = Arc::clone(&stream.priority_key);\n                        self.streams.remove_flushable(&priority_key);\n\n                        continue;\n                    },\n                };\n\n                let (mut stream_hdr, mut stream_payload) =\n                    b.split_at(hdr_off + hdr_len)?;\n\n                // Write stream data into the packet buffer.\n                let (len, fin) =\n                    stream.send.emit(&mut stream_payload.as_mut()[..max_len])?;\n\n                // Encode the frame's header.\n                //\n                // Due to how `OctetsMut::split_at()` works, `stream_hdr` starts\n                // from the initial offset of `b` (rather than the current\n                // offset), so it needs to be advanced to the initial frame\n                // offset.\n                stream_hdr.skip(hdr_off)?;\n\n                frame::encode_stream_header(\n                    stream_id,\n                    stream_off,\n                    len as u64,\n                    fin,\n                    &mut stream_hdr,\n                )?;\n\n                // Advance the packet buffer's offset.\n                b.skip(hdr_len + len)?;\n\n                let frame = frame::Frame::StreamHeader {\n                    stream_id,\n                    offset: stream_off,\n                    length: len,\n                    fin,\n                };\n\n                if push_frame_to_pkt!(b, frames, frame, left) {\n                    ack_eliciting = true;\n                    in_flight = true;\n                    has_data = true;\n                }\n\n                let priority_key = Arc::clone(&stream.priority_key);\n                // If the stream is no longer flushable, remove it from the queue\n                if !stream.is_flushable() {\n                    self.streams.remove_flushable(&priority_key);\n                } else if stream.incremental {\n                    // Shuffle the incremental stream to the back of the\n                    // queue.\n                    self.streams.remove_flushable(&priority_key);\n                    self.streams.insert_flushable(&priority_key);\n                }\n\n                #[cfg(feature = \"fuzzing\")]\n                // Coalesce STREAM frames when fuzzing.\n                if left > frame::MAX_STREAM_OVERHEAD {\n                    continue;\n                }\n\n                break;\n            }\n        }\n\n        // Alternate trying to send DATAGRAMs next time.\n        self.emit_dgram = !dgram_emitted;\n\n        // If no other ack-eliciting frame is sent, include a PING frame\n        // - if PTO probe needed; OR\n        // - if we've sent too many non ack-eliciting packets without having\n        // sent an ACK eliciting one; OR\n        // - the application requested an ack-eliciting frame be sent.\n        if (ack_elicit_required || path.needs_ack_eliciting) &&\n            !ack_eliciting &&\n            left >= 1 &&\n            !is_closing\n        {\n            let frame = frame::Frame::Ping { mtu_probe: None };\n\n            if push_frame_to_pkt!(b, frames, frame, left) {\n                ack_eliciting = true;\n                in_flight = true;\n            }\n        }\n\n        if ack_eliciting && !is_pmtud_probe {\n            path.needs_ack_eliciting = false;\n            path.recovery.ping_sent(epoch);\n        }\n\n        if !has_data &&\n            !dgram_emitted &&\n            cwnd_available > frame::MAX_STREAM_OVERHEAD\n        {\n            path.recovery.on_app_limited();\n        }\n\n        if frames.is_empty() {\n            // When we reach this point we are not able to write more, so set\n            // app_limited to false.\n            path.recovery.update_app_limited(false);\n            return Err(Error::Done);\n        }\n\n        // When coalescing a 1-RTT packet, we can't add padding in the UDP\n        // datagram, so use PADDING frames instead.\n        //\n        // This is only needed if\n        // 1) an Initial packet has already been written to the UDP datagram,\n        // as Initial always requires padding.\n        //\n        // 2) this is a probing packet towards an unvalidated peer address.\n        if (has_initial || !path.validated()) &&\n            pkt_type == Type::Short &&\n            left >= 1\n        {\n            let frame = frame::Frame::Padding { len: left };\n\n            if push_frame_to_pkt!(b, frames, frame, left) {\n                in_flight = true;\n            }\n        }\n\n        // Pad payload so that it's always at least 4 bytes.\n        if b.off() - payload_offset < PAYLOAD_MIN_LEN {\n            let payload_len = b.off() - payload_offset;\n\n            let frame = frame::Frame::Padding {\n                len: PAYLOAD_MIN_LEN - payload_len,\n            };\n\n            #[allow(unused_assignments)]\n            if push_frame_to_pkt!(b, frames, frame, left) {\n                in_flight = true;\n            }\n        }\n\n        let payload_len = b.off() - payload_offset;\n\n        // Fill in payload length.\n        if pkt_type != Type::Short {\n            let len = pn_len + payload_len + crypto_overhead;\n\n            let (_, mut payload_with_len) = b.split_at(header_offset)?;\n            payload_with_len\n                .put_varint_with_len(len as u64, PAYLOAD_LENGTH_LEN)?;\n        }\n\n        trace!(\n            \"{} tx pkt {} len={} pn={} {}\",\n            self.trace_id,\n            hdr_trace.unwrap_or_default(),\n            payload_len,\n            pn,\n            AddrTupleFmt(path.local_addr(), path.peer_addr())\n        );\n\n        #[cfg(feature = \"qlog\")]\n        let mut qlog_frames: SmallVec<\n            [qlog::events::quic::QuicFrame; 1],\n        > = SmallVec::with_capacity(frames.len());\n\n        for frame in &mut frames {\n            trace!(\"{} tx frm {:?}\", self.trace_id, frame);\n\n            qlog_with_type!(QLOG_PACKET_TX, self.qlog, _q, {\n                qlog_frames.push(frame.to_qlog());\n            });\n        }\n\n        qlog_with_type!(QLOG_PACKET_TX, self.qlog, q, {\n            if let Some(header) = qlog_pkt_hdr {\n                // Qlog packet raw info described at\n                // https://datatracker.ietf.org/doc/html/draft-ietf-quic-qlog-main-schema-00#section-5.1\n                //\n                // `length` includes packet headers and trailers (AEAD tag).\n                let length = payload_len + payload_offset + crypto_overhead;\n                let qlog_raw_info = RawInfo {\n                    length: Some(length as u64),\n                    payload_length: Some(payload_len as u64),\n                    data: None,\n                };\n\n                let send_at_time =\n                    now.duration_since(q.start_time()).as_secs_f64() * 1000.0;\n\n                let ev_data =\n                    EventData::QuicPacketSent(qlog::events::quic::PacketSent {\n                        header,\n                        frames: Some(qlog_frames),\n                        raw: Some(qlog_raw_info),\n                        send_at_time: Some(send_at_time),\n                        ..Default::default()\n                    });\n\n                q.add_event_data_with_instant(ev_data, now).ok();\n            }\n        });\n\n        let aead = match crypto_ctx.crypto_seal {\n            Some(ref mut v) => v,\n            None => return Err(Error::InvalidState),\n        };\n\n        let written = packet::encrypt_pkt(\n            &mut b,\n            pn,\n            pn_len,\n            payload_len,\n            payload_offset,\n            None,\n            aead,\n        )?;\n\n        let sent_pkt_has_data = if path.recovery.gcongestion_enabled() {\n            has_data || dgram_emitted\n        } else {\n            has_data\n        };\n\n        let sent_pkt = recovery::Sent {\n            pkt_num: pn,\n            frames,\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: if ack_eliciting { written } else { 0 },\n            ack_eliciting,\n            in_flight,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: sent_pkt_has_data,\n            is_pmtud_probe,\n        };\n\n        if in_flight && is_app_limited {\n            path.recovery.delivery_rate_update_app_limited(true);\n        }\n\n        self.next_pkt_num += 1;\n\n        let handshake_status = recovery::HandshakeStatus {\n            has_handshake_keys: self.crypto_ctx[packet::Epoch::Handshake]\n                .has_keys(),\n            peer_verified_address: self.peer_verified_initial_address,\n            completed: self.handshake_completed,\n        };\n\n        self.on_packet_sent(send_pid, sent_pkt, epoch, handshake_status, now)?;\n\n        let path = self.paths.get_mut(send_pid)?;\n        qlog_with_type!(QLOG_METRICS, self.qlog, q, {\n            path.recovery.maybe_qlog(q, now);\n        });\n\n        // Record sent packet size if we probe the path.\n        if let Some(data) = challenge_data {\n            path.add_challenge_sent(data, written, now);\n        }\n\n        self.sent_count += 1;\n        self.sent_bytes += written as u64;\n        path.sent_count += 1;\n        path.sent_bytes += written as u64;\n\n        if self.dgram_send_queue.byte_size() > path.recovery.cwnd_available() {\n            path.recovery.update_app_limited(false);\n        }\n\n        path.max_send_bytes = path.max_send_bytes.saturating_sub(written);\n\n        // On the client, drop initial state after sending an Handshake packet.\n        if !self.is_server && hdr_ty == Type::Handshake {\n            self.drop_epoch_state(packet::Epoch::Initial, now);\n        }\n\n        // (Re)start the idle timer if we are sending the first ack-eliciting\n        // packet since last receiving a packet.\n        if ack_eliciting && !self.ack_eliciting_sent {\n            if let Some(idle_timeout) = self.idle_timeout() {\n                self.idle_timer = Some(now + idle_timeout);\n            }\n        }\n\n        if ack_eliciting {\n            self.ack_eliciting_sent = true;\n        }\n\n        Ok((pkt_type, written))\n    }\n\n    fn on_packet_sent(\n        &mut self, send_pid: usize, sent_pkt: recovery::Sent,\n        epoch: packet::Epoch, handshake_status: recovery::HandshakeStatus,\n        now: Instant,\n    ) -> Result<()> {\n        let path = self.paths.get_mut(send_pid)?;\n\n        // It's fine to set the skip counter based on a non-active path's values.\n        let cwnd = path.recovery.cwnd();\n        let max_datagram_size = path.recovery.max_datagram_size();\n        self.pkt_num_spaces[epoch].on_packet_sent(&sent_pkt);\n        self.pkt_num_manager.on_packet_sent(\n            cwnd,\n            max_datagram_size,\n            self.handshake_completed,\n        );\n\n        path.recovery.on_packet_sent(\n            sent_pkt,\n            epoch,\n            handshake_status,\n            now,\n            &self.trace_id,\n        );\n\n        Ok(())\n    }\n\n    /// Returns the desired send time for the next packet.\n    #[inline]\n    pub fn get_next_release_time(&self) -> Option<ReleaseDecision> {\n        Some(\n            self.paths\n                .get_active()\n                .ok()?\n                .recovery\n                .get_next_release_time(),\n        )\n    }\n\n    /// Returns whether gcongestion is enabled.\n    #[inline]\n    pub fn gcongestion_enabled(&self) -> Option<bool> {\n        Some(self.paths.get_active().ok()?.recovery.gcongestion_enabled())\n    }\n\n    /// Returns the maximum pacing into the future.\n    ///\n    /// Equals 1/8 of the smoothed RTT, but at least 1ms and not greater than\n    /// 5ms.\n    pub fn max_release_into_future(&self) -> Duration {\n        self.paths\n            .get_active()\n            .map(|p| p.recovery.rtt().mul_f64(0.125))\n            .unwrap_or(Duration::from_millis(1))\n            .max(Duration::from_millis(1))\n            .min(Duration::from_millis(5))\n    }\n\n    /// Returns whether pacing is enabled.\n    #[inline]\n    pub fn pacing_enabled(&self) -> bool {\n        self.recovery_config.pacing\n    }\n\n    /// Returns the size of the send quantum, in bytes.\n    ///\n    /// This represents the maximum size of a packet burst as determined by the\n    /// congestion control algorithm in use.\n    ///\n    /// Applications can, for example, use it in conjunction with segmentation\n    /// offloading mechanisms as the maximum limit for outgoing aggregates of\n    /// multiple packets.\n    #[inline]\n    pub fn send_quantum(&self) -> usize {\n        match self.paths.get_active() {\n            Ok(p) => p.recovery.send_quantum(),\n            _ => 0,\n        }\n    }\n\n    /// Returns the size of the send quantum over the given 4-tuple, in bytes.\n    ///\n    /// This represents the maximum size of a packet burst as determined by the\n    /// congestion control algorithm in use.\n    ///\n    /// Applications can, for example, use it in conjunction with segmentation\n    /// offloading mechanisms as the maximum limit for outgoing aggregates of\n    /// multiple packets.\n    ///\n    /// If the (`local_addr`, peer_addr`) 4-tuple relates to a non-existing\n    /// path, this method returns 0.\n    pub fn send_quantum_on_path(\n        &self, local_addr: SocketAddr, peer_addr: SocketAddr,\n    ) -> usize {\n        self.paths\n            .path_id_from_addrs(&(local_addr, peer_addr))\n            .and_then(|pid| self.paths.get(pid).ok())\n            .map(|path| path.recovery.send_quantum())\n            .unwrap_or(0)\n    }\n\n    /// Reads contiguous data from a stream into the provided slice.\n    ///\n    /// The slice must be sized by the caller and will be populated up to its\n    /// capacity.\n    ///\n    /// On success the amount of bytes read and a flag indicating the fin state\n    /// is returned as a tuple, or [`Done`] if there is no data to read.\n    ///\n    /// Reading data from a stream may trigger queueing of control messages\n    /// (e.g. MAX_STREAM_DATA). [`send()`] should be called afterwards.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`send()`]: struct.Connection.html#method.send\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut buf = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// # let stream_id = 0;\n    /// while let Ok((read, fin)) = conn.stream_recv(stream_id, &mut buf) {\n    ///     println!(\"Got {} bytes on stream {}\", read, stream_id);\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn stream_recv(\n        &mut self, stream_id: u64, out: &mut [u8],\n    ) -> Result<(usize, bool)> {\n        self.do_stream_recv(stream_id, RecvAction::Emit { out })\n    }\n\n    /// Discard contiguous data from a stream without copying.\n    ///\n    /// On success the amount of bytes discarded and a flag indicating the fin\n    /// state is returned as a tuple, or [`Done`] if there is no data to\n    /// discard.\n    ///\n    /// Discarding data from a stream may trigger queueing of control messages\n    /// (e.g. MAX_STREAM_DATA). [`send()`] should be called afterwards.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`send()`]: struct.Connection.html#method.send\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// # let stream_id = 0;\n    /// while let Ok((read, fin)) = conn.stream_discard(stream_id, 1) {\n    ///     println!(\"Discarded {} byte(s) on stream {}\", read, stream_id);\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn stream_discard(\n        &mut self, stream_id: u64, len: usize,\n    ) -> Result<(usize, bool)> {\n        self.do_stream_recv(stream_id, RecvAction::Discard { len })\n    }\n\n    // Reads or discards contiguous data from a stream.\n    //\n    // Passing an `action` of `StreamRecvAction::Emit` results in a read into\n    // the provided slice. It must be sized by the caller and will be populated\n    // up to its capacity.\n    //\n    // Passing an `action` of `StreamRecvAction::Discard` results in discard up\n    // to the indicated length.\n    //\n    // On success the amount of bytes read or discarded, and a flag indicating\n    // the fin state, is returned as a tuple, or [`Done`] if there is no data to\n    // read or discard.\n    //\n    // Reading or discarding data from a stream may trigger queueing of control\n    // messages (e.g. MAX_STREAM_DATA). [`send()`] should be called afterwards.\n    //\n    // [`Done`]: enum.Error.html#variant.Done\n    // [`send()`]: struct.Connection.html#method.send\n    fn do_stream_recv(\n        &mut self, stream_id: u64, action: RecvAction,\n    ) -> Result<(usize, bool)> {\n        // We can't read on our own unidirectional streams.\n        if !stream::is_bidi(stream_id) &&\n            stream::is_local(stream_id, self.is_server)\n        {\n            return Err(Error::InvalidStreamState(stream_id));\n        }\n\n        let stream = self\n            .streams\n            .get_mut(stream_id)\n            .ok_or(Error::InvalidStreamState(stream_id))?;\n\n        if !stream.is_readable() {\n            return Err(Error::Done);\n        }\n\n        let local = stream.local;\n        let priority_key = Arc::clone(&stream.priority_key);\n\n        #[cfg(feature = \"qlog\")]\n        let offset = stream.recv.off_front();\n\n        #[cfg(feature = \"qlog\")]\n        let to = match action {\n            RecvAction::Emit { .. } => Some(DataRecipient::Application),\n\n            RecvAction::Discard { .. } => Some(DataRecipient::Dropped),\n        };\n\n        let (read, fin) = match stream.recv.emit_or_discard(action) {\n            Ok(v) => v,\n\n            Err(e) => {\n                // Collect the stream if it is now complete. This can happen if\n                // we got a `StreamReset` error which will now be propagated to\n                // the application, so we don't need to keep the stream's state\n                // anymore.\n                if stream.is_complete() {\n                    self.streams.collect(stream_id, local);\n                }\n\n                self.streams.remove_readable(&priority_key);\n                return Err(e);\n            },\n        };\n\n        self.flow_control.add_consumed(read as u64);\n\n        let readable = stream.is_readable();\n\n        let complete = stream.is_complete();\n\n        if stream.recv.almost_full() {\n            self.streams.insert_almost_full(stream_id);\n        }\n\n        if !readable {\n            self.streams.remove_readable(&priority_key);\n        }\n\n        if complete {\n            self.streams.collect(stream_id, local);\n        }\n\n        qlog_with_type!(QLOG_DATA_MV, self.qlog, q, {\n            let ev_data = EventData::QuicStreamDataMoved(\n                qlog::events::quic::StreamDataMoved {\n                    stream_id: Some(stream_id),\n                    offset: Some(offset),\n                    raw: Some(RawInfo {\n                        length: Some(read as u64),\n                        ..Default::default()\n                    }),\n                    from: Some(DataRecipient::Transport),\n                    to,\n                    additional_info: fin\n                        .then_some(DataMovedAdditionalInfo::FinSet),\n                },\n            );\n\n            let now = Instant::now();\n            q.add_event_data_with_instant(ev_data, now).ok();\n        });\n\n        if priority_key.incremental && readable {\n            // Shuffle the incremental stream to the back of the queue.\n            self.streams.remove_readable(&priority_key);\n            self.streams.insert_readable(&priority_key);\n        }\n\n        Ok((read, fin))\n    }\n\n    /// Writes data to a stream.\n    ///\n    /// On success the number of bytes written is returned, or [`Done`] if no\n    /// data was written (e.g. because the stream has no capacity).\n    ///\n    /// Applications can provide a 0-length buffer with the fin flag set to\n    /// true. This will lead to a 0-length FIN STREAM frame being sent at the\n    /// latest offset. The `Ok(0)` value is only returned when the application\n    /// provided a 0-length buffer.\n    ///\n    /// In addition, if the peer has signalled that it doesn't want to receive\n    /// any more data from this stream by sending the `STOP_SENDING` frame, the\n    /// [`StreamStopped`] error will be returned instead of any data.\n    ///\n    /// Note that in order to avoid buffering an infinite amount of data in the\n    /// stream's send buffer, streams are only allowed to buffer outgoing data\n    /// up to the amount that the peer allows it to send (that is, up to the\n    /// stream's outgoing flow control capacity).\n    ///\n    /// This means that the number of written bytes returned can be lower than\n    /// the length of the input buffer when the stream doesn't have enough\n    /// capacity for the operation to complete. The application should retry the\n    /// operation once the stream is reported as writable again.\n    ///\n    /// Applications should call this method only after the handshake is\n    /// completed (whenever [`is_established()`] returns `true`) or during\n    /// early data if enabled (whenever [`is_in_early_data()`] returns `true`).\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`StreamStopped`]: enum.Error.html#variant.StreamStopped\n    /// [`is_established()`]: struct.Connection.html#method.is_established\n    /// [`is_in_early_data()`]: struct.Connection.html#method.is_in_early_data\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut buf = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = \"127.0.0.1:4321\".parse().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// # let stream_id = 0;\n    /// conn.stream_send(stream_id, b\"hello\", true)?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn stream_send(\n        &mut self, stream_id: u64, buf: &[u8], fin: bool,\n    ) -> Result<usize> {\n        self.stream_do_send(\n            stream_id,\n            buf,\n            fin,\n            |stream: &mut stream::Stream<F>,\n             buf: &[u8],\n             cap: usize,\n             fin: bool| {\n                stream.send.write(&buf[..cap], fin).map(|v| (v, v))\n            },\n        )\n    }\n\n    /// Writes data to a stream with zero copying, instead, it appends the\n    /// provided buffer directly to the send queue if the capacity allows\n    /// it.\n    ///\n    /// When a partial write happens (including when [`Error::Done`] is\n    /// returned) the remaining (unwritten) buffer will also be returned.\n    /// The application should retry the operation once the stream is\n    /// reported as writable again.\n    pub fn stream_send_zc(\n        &mut self, stream_id: u64, buf: F::Buf, len: Option<usize>, fin: bool,\n    ) -> Result<(usize, Option<F::Buf>)>\n    where\n        F::Buf: BufSplit,\n    {\n        self.stream_do_send(\n            stream_id,\n            buf,\n            fin,\n            |stream: &mut stream::Stream<F>,\n             buf: F::Buf,\n             cap: usize,\n             fin: bool| {\n                let len = len.unwrap_or(usize::MAX).min(cap);\n                let (sent, remaining) = stream.send.append_buf(buf, len, fin)?;\n                Ok((sent, (sent, remaining)))\n            },\n        )\n    }\n\n    fn stream_do_send<B, R, SND>(\n        &mut self, stream_id: u64, buf: B, fin: bool, write_fn: SND,\n    ) -> Result<R>\n    where\n        B: AsRef<[u8]>,\n        SND: FnOnce(&mut stream::Stream<F>, B, usize, bool) -> Result<(usize, R)>,\n    {\n        // We can't write on the peer's unidirectional streams.\n        if !stream::is_bidi(stream_id) &&\n            !stream::is_local(stream_id, self.is_server)\n        {\n            return Err(Error::InvalidStreamState(stream_id));\n        }\n\n        let len = buf.as_ref().len();\n\n        // Mark the connection as blocked if the connection-level flow control\n        // limit doesn't let us buffer all the data.\n        //\n        // Note that this is separate from \"send capacity\" as that also takes\n        // congestion control into consideration.\n        if self.max_tx_data - self.tx_data < len as u64 {\n            self.blocked_limit = Some(self.max_tx_data);\n        }\n\n        let cap = self.tx_cap;\n\n        // Get existing stream or create a new one.\n        let stream = match self.get_or_create_stream(stream_id, true) {\n            Ok(v) => v,\n\n            Err(Error::StreamLimit) => {\n                // If the local endpoint has exhausted the peer's stream count\n                // limit, record the current limit so that a STREAMS_BLOCKED\n                // frame can be sent.\n                if self.enable_send_streams_blocked &&\n                    stream::is_local(stream_id, self.is_server)\n                {\n                    if stream::is_bidi(stream_id) {\n                        let limit = self.streams.peer_max_streams_bidi();\n                        self.streams_blocked_bidi_state.update_at(limit);\n                    } else {\n                        let limit = self.streams.peer_max_streams_uni();\n                        self.streams_blocked_uni_state.update_at(limit);\n                    }\n                }\n\n                return Err(Error::StreamLimit);\n            },\n\n            Err(e) => return Err(e),\n        };\n\n        #[cfg(feature = \"qlog\")]\n        let offset = stream.send.off_back();\n\n        let was_writable = stream.is_writable();\n\n        let was_flushable = stream.is_flushable();\n\n        let is_complete = stream.is_complete();\n        let is_readable = stream.is_readable();\n\n        let priority_key = Arc::clone(&stream.priority_key);\n\n        // Return early if the stream has been stopped, and collect its state\n        // if complete.\n        if let Err(Error::StreamStopped(e)) = stream.send.cap() {\n            // Only collect the stream if it is complete and not readable.\n            // If it is readable, it will get collected when stream_recv()\n            // is used.\n            //\n            // The stream can't be writable if it has been stopped.\n            if is_complete && !is_readable {\n                let local = stream.local;\n                self.streams.collect(stream_id, local);\n            }\n\n            return Err(Error::StreamStopped(e));\n        };\n\n        // Truncate the input buffer based on the connection's send capacity if\n        // necessary.\n        //\n        // When the cap is zero, the method returns Ok(0) *only* when the passed\n        // buffer is empty. We return Error::Done otherwise.\n        if cap == 0 && len > 0 {\n            if was_writable {\n                // When `stream_writable_next()` returns a stream, the writable\n                // mark is removed, but because the stream is blocked by the\n                // connection-level send capacity it won't be marked as writable\n                // again once the capacity increases.\n                //\n                // Since the stream is writable already, mark it here instead.\n                self.streams.insert_writable(&priority_key);\n            }\n\n            return Err(Error::Done);\n        }\n\n        let (cap, fin, blocked_by_cap) = if cap < len {\n            (cap, false, true)\n        } else {\n            (len, fin, false)\n        };\n\n        let (sent, ret) = match write_fn(stream, buf, cap, fin) {\n            Ok(v) => v,\n\n            Err(e) => {\n                self.streams.remove_writable(&priority_key);\n                return Err(e);\n            },\n        };\n\n        let incremental = stream.incremental;\n        let priority_key = Arc::clone(&stream.priority_key);\n\n        let flushable = stream.is_flushable();\n\n        let writable = stream.is_writable();\n\n        let empty_fin = len == 0 && fin;\n\n        if sent < cap {\n            let max_off = stream.send.max_off();\n\n            if stream.send.blocked_at() != Some(max_off) {\n                stream.send.update_blocked_at(Some(max_off));\n                self.streams.insert_blocked(stream_id, max_off);\n            }\n        } else {\n            stream.send.update_blocked_at(None);\n            self.streams.remove_blocked(stream_id);\n        }\n\n        // If the stream is now flushable push it to the flushable queue, but\n        // only if it wasn't already queued.\n        //\n        // Consider the stream flushable also when we are sending a zero-length\n        // frame that has the fin flag set.\n        if (flushable || empty_fin) && !was_flushable {\n            self.streams.insert_flushable(&priority_key);\n        }\n\n        if !writable {\n            self.streams.remove_writable(&priority_key);\n        } else if was_writable && blocked_by_cap {\n            // When `stream_writable_next()` returns a stream, the writable\n            // mark is removed, but because the stream is blocked by the\n            // connection-level send capacity it won't be marked as writable\n            // again once the capacity increases.\n            //\n            // Since the stream is writable already, mark it here instead.\n            self.streams.insert_writable(&priority_key);\n        }\n\n        self.tx_cap -= sent;\n\n        self.tx_data += sent as u64;\n\n        self.tx_buffered += sent;\n        self.check_tx_buffered_invariant();\n\n        qlog_with_type!(QLOG_DATA_MV, self.qlog, q, {\n            let ev_data = EventData::QuicStreamDataMoved(\n                qlog::events::quic::StreamDataMoved {\n                    stream_id: Some(stream_id),\n                    offset: Some(offset),\n                    raw: Some(RawInfo {\n                        length: Some(sent as u64),\n                        ..Default::default()\n                    }),\n                    from: Some(DataRecipient::Application),\n                    to: Some(DataRecipient::Transport),\n                    additional_info: fin\n                        .then_some(DataMovedAdditionalInfo::FinSet),\n                },\n            );\n\n            let now = Instant::now();\n            q.add_event_data_with_instant(ev_data, now).ok();\n        });\n\n        if sent == 0 && cap > 0 {\n            return Err(Error::Done);\n        }\n\n        if incremental && writable {\n            // Shuffle the incremental stream to the back of the queue.\n            self.streams.remove_writable(&priority_key);\n            self.streams.insert_writable(&priority_key);\n        }\n\n        Ok(ret)\n    }\n\n    /// Sets the priority for a stream.\n    ///\n    /// A stream's priority determines the order in which stream data is sent\n    /// on the wire (streams with lower priority are sent first). Streams are\n    /// created with a default priority of `127`.\n    ///\n    /// The target stream is created if it did not exist before calling this\n    /// method.\n    pub fn stream_priority(\n        &mut self, stream_id: u64, urgency: u8, incremental: bool,\n    ) -> Result<()> {\n        // Get existing stream or create a new one, but if the stream\n        // has already been closed and collected, ignore the prioritization.\n        let stream = match self.get_or_create_stream(stream_id, true) {\n            Ok(v) => v,\n\n            Err(Error::Done) => return Ok(()),\n\n            Err(e) => return Err(e),\n        };\n\n        if stream.urgency == urgency && stream.incremental == incremental {\n            return Ok(());\n        }\n\n        stream.urgency = urgency;\n        stream.incremental = incremental;\n\n        let new_priority_key = Arc::new(StreamPriorityKey {\n            urgency: stream.urgency,\n            incremental: stream.incremental,\n            id: stream_id,\n            ..Default::default()\n        });\n\n        let old_priority_key =\n            std::mem::replace(&mut stream.priority_key, new_priority_key.clone());\n\n        self.streams\n            .update_priority(&old_priority_key, &new_priority_key);\n\n        Ok(())\n    }\n\n    /// Shuts down reading or writing from/to the specified stream.\n    ///\n    /// When the `direction` argument is set to [`Shutdown::Read`], outstanding\n    /// data in the stream's receive buffer is dropped, and no additional data\n    /// is added to it. Data received after calling this method is still\n    /// validated and acked but not stored, and [`stream_recv()`] will not\n    /// return it to the application. In addition, a `STOP_SENDING` frame will\n    /// be sent to the peer to signal it to stop sending data.\n    ///\n    /// When the `direction` argument is set to [`Shutdown::Write`], outstanding\n    /// data in the stream's send buffer is dropped, and no additional data is\n    /// added to it. Data passed to [`stream_send()`] after calling this method\n    /// will be ignored. In addition, a `RESET_STREAM` frame will be sent to the\n    /// peer to signal the reset.\n    ///\n    /// Locally-initiated unidirectional streams can only be closed in the\n    /// [`Shutdown::Write`] direction. Remotely-initiated unidirectional streams\n    /// can only be closed in the [`Shutdown::Read`] direction. Using an\n    /// incorrect direction will return [`InvalidStreamState`].\n    ///\n    /// [`Shutdown::Read`]: enum.Shutdown.html#variant.Read\n    /// [`Shutdown::Write`]: enum.Shutdown.html#variant.Write\n    /// [`stream_recv()`]: struct.Connection.html#method.stream_recv\n    /// [`stream_send()`]: struct.Connection.html#method.stream_send\n    /// [`InvalidStreamState`]: enum.Error.html#variant.InvalidStreamState\n    pub fn stream_shutdown(\n        &mut self, stream_id: u64, direction: Shutdown, err: u64,\n    ) -> Result<()> {\n        // Don't try to stop a local unidirectional stream.\n        if direction == Shutdown::Read &&\n            stream::is_local(stream_id, self.is_server) &&\n            !stream::is_bidi(stream_id)\n        {\n            return Err(Error::InvalidStreamState(stream_id));\n        }\n\n        // Don't try to reset a remote unidirectional stream.\n        if direction == Shutdown::Write &&\n            !stream::is_local(stream_id, self.is_server) &&\n            !stream::is_bidi(stream_id)\n        {\n            return Err(Error::InvalidStreamState(stream_id));\n        }\n\n        // Get existing stream.\n        let stream = self.streams.get_mut(stream_id).ok_or(Error::Done)?;\n\n        let priority_key = Arc::clone(&stream.priority_key);\n\n        match direction {\n            Shutdown::Read => {\n                let consumed = stream.recv.shutdown()?;\n                self.flow_control.add_consumed(consumed);\n\n                if !stream.recv.is_fin() {\n                    self.streams.insert_stopped(stream_id, err);\n                }\n\n                // Once shutdown, the stream is guaranteed to be non-readable.\n                self.streams.remove_readable(&priority_key);\n\n                self.stopped_stream_local_count =\n                    self.stopped_stream_local_count.saturating_add(1);\n            },\n\n            Shutdown::Write => {\n                let (final_size, unsent) = stream.send.shutdown()?;\n\n                // Claw back some flow control allowance from data that was\n                // buffered but not actually sent before the stream was reset.\n                self.tx_data = self.tx_data.saturating_sub(unsent);\n\n                self.tx_buffered =\n                    self.tx_buffered.saturating_sub(unsent as usize);\n\n                // These drops in qlog are a bit weird, but the only way to ensure\n                // that all bytes that are moved from App to Transport in\n                // stream_do_send are eventually moved from Transport to Dropped.\n                // Ideally we would add a Transport to Network transition also as\n                // a way to indicate when bytes were transmitted vs dropped\n                // without ever being sent.\n                qlog_with_type!(QLOG_DATA_MV, self.qlog, q, {\n                    let ev_data = EventData::QuicStreamDataMoved(\n                        qlog::events::quic::StreamDataMoved {\n                            stream_id: Some(stream_id),\n                            offset: Some(final_size),\n                            raw: Some(RawInfo {\n                                length: Some(unsent),\n                                ..Default::default()\n                            }),\n                            from: Some(DataRecipient::Transport),\n                            to: Some(DataRecipient::Dropped),\n                            ..Default::default()\n                        },\n                    );\n\n                    q.add_event_data_with_instant(ev_data, Instant::now()).ok();\n                });\n\n                // Update send capacity.\n                self.update_tx_cap();\n\n                self.streams.insert_reset(stream_id, err, final_size);\n\n                // Once shutdown, the stream is guaranteed to be non-writable.\n                self.streams.remove_writable(&priority_key);\n\n                self.reset_stream_local_count =\n                    self.reset_stream_local_count.saturating_add(1);\n            },\n        }\n\n        Ok(())\n    }\n\n    /// Returns the stream's send capacity in bytes.\n    ///\n    /// If the specified stream doesn't exist (including when it has already\n    /// been completed and closed), the [`InvalidStreamState`] error will be\n    /// returned.\n    ///\n    /// In addition, if the peer has signalled that it doesn't want to receive\n    /// any more data from this stream by sending the `STOP_SENDING` frame, the\n    /// [`StreamStopped`] error will be returned.\n    ///\n    /// [`InvalidStreamState`]: enum.Error.html#variant.InvalidStreamState\n    /// [`StreamStopped`]: enum.Error.html#variant.StreamStopped\n    #[inline]\n    pub fn stream_capacity(&mut self, stream_id: u64) -> Result<usize> {\n        if let Some(stream) = self.streams.get(stream_id) {\n            let stream_cap = match stream.send.cap() {\n                Ok(v) => v,\n\n                Err(Error::StreamStopped(e)) => {\n                    // Only collect the stream if it is complete and not\n                    // readable. If it is readable, it will get collected when\n                    // stream_recv() is used.\n                    if stream.is_complete() && !stream.is_readable() {\n                        let local = stream.local;\n                        self.streams.collect(stream_id, local);\n                    }\n\n                    return Err(Error::StreamStopped(e));\n                },\n\n                Err(e) => return Err(e),\n            };\n\n            let cap = cmp::min(self.tx_cap, stream_cap);\n            return Ok(cap);\n        };\n\n        Err(Error::InvalidStreamState(stream_id))\n    }\n\n    /// Returns the next stream that has data to read.\n    ///\n    /// Note that once returned by this method, a stream ID will not be returned\n    /// again until it is \"re-armed\".\n    ///\n    /// The application will need to read all of the pending data on the stream,\n    /// and new data has to be received before the stream is reported again.\n    ///\n    /// This is unlike the [`readable()`] method, that returns the same list of\n    /// readable streams when called multiple times in succession.\n    ///\n    /// [`readable()`]: struct.Connection.html#method.readable\n    pub fn stream_readable_next(&mut self) -> Option<u64> {\n        let priority_key = self.streams.readable.front().clone_pointer()?;\n\n        self.streams.remove_readable(&priority_key);\n\n        Some(priority_key.id)\n    }\n\n    /// Returns true if the stream has data that can be read.\n    pub fn stream_readable(&self, stream_id: u64) -> bool {\n        let stream = match self.streams.get(stream_id) {\n            Some(v) => v,\n\n            None => return false,\n        };\n\n        stream.is_readable()\n    }\n\n    /// Returns the next stream that can be written to.\n    ///\n    /// Note that once returned by this method, a stream ID will not be returned\n    /// again until it is \"re-armed\".\n    ///\n    /// This is unlike the [`writable()`] method, that returns the same list of\n    /// writable streams when called multiple times in succession. It is not\n    /// advised to use both `stream_writable_next()` and [`writable()`] on the\n    /// same connection, as it may lead to unexpected results.\n    ///\n    /// The [`stream_writable()`] method can also be used to fine-tune when a\n    /// stream is reported as writable again.\n    ///\n    /// [`stream_writable()`]: struct.Connection.html#method.stream_writable\n    /// [`writable()`]: struct.Connection.html#method.writable\n    pub fn stream_writable_next(&mut self) -> Option<u64> {\n        // If there is not enough connection-level send capacity, none of the\n        // streams are writable.\n        if self.tx_cap == 0 {\n            return None;\n        }\n\n        let mut cursor = self.streams.writable.front();\n\n        while let Some(priority_key) = cursor.clone_pointer() {\n            if let Some(stream) = self.streams.get(priority_key.id) {\n                let cap = match stream.send.cap() {\n                    Ok(v) => v,\n\n                    // Return the stream to the application immediately if it's\n                    // stopped.\n                    Err(_) =>\n                        return {\n                            self.streams.remove_writable(&priority_key);\n\n                            Some(priority_key.id)\n                        },\n                };\n\n                if cmp::min(self.tx_cap, cap) >= stream.send_lowat {\n                    self.streams.remove_writable(&priority_key);\n                    return Some(priority_key.id);\n                }\n            }\n\n            cursor.move_next();\n        }\n\n        None\n    }\n\n    /// Returns true if the stream has enough send capacity.\n    ///\n    /// When `len` more bytes can be buffered into the given stream's send\n    /// buffer, `true` will be returned, `false` otherwise.\n    ///\n    /// In the latter case, if the additional data can't be buffered due to\n    /// flow control limits, the peer will also be notified, and a \"low send\n    /// watermark\" will be set for the stream, such that it is not going to be\n    /// reported as writable again by [`stream_writable_next()`] until its send\n    /// capacity reaches `len`.\n    ///\n    /// If the specified stream doesn't exist (including when it has already\n    /// been completed and closed), the [`InvalidStreamState`] error will be\n    /// returned.\n    ///\n    /// In addition, if the peer has signalled that it doesn't want to receive\n    /// any more data from this stream by sending the `STOP_SENDING` frame, the\n    /// [`StreamStopped`] error will be returned.\n    ///\n    /// [`stream_writable_next()`]: struct.Connection.html#method.stream_writable_next\n    /// [`InvalidStreamState`]: enum.Error.html#variant.InvalidStreamState\n    /// [`StreamStopped`]: enum.Error.html#variant.StreamStopped\n    #[inline]\n    pub fn stream_writable(\n        &mut self, stream_id: u64, len: usize,\n    ) -> Result<bool> {\n        if self.stream_capacity(stream_id)? >= len {\n            return Ok(true);\n        }\n\n        let stream = match self.streams.get_mut(stream_id) {\n            Some(v) => v,\n\n            None => return Err(Error::InvalidStreamState(stream_id)),\n        };\n\n        stream.send_lowat = cmp::max(1, len);\n\n        let is_writable = stream.is_writable();\n\n        let priority_key = Arc::clone(&stream.priority_key);\n\n        if self.max_tx_data - self.tx_data < len as u64 {\n            self.blocked_limit = Some(self.max_tx_data);\n        }\n\n        if stream.send.cap()? < len {\n            let max_off = stream.send.max_off();\n            if stream.send.blocked_at() != Some(max_off) {\n                stream.send.update_blocked_at(Some(max_off));\n                self.streams.insert_blocked(stream_id, max_off);\n            }\n        } else if is_writable {\n            // When `stream_writable_next()` returns a stream, the writable\n            // mark is removed, but because the stream is blocked by the\n            // connection-level send capacity it won't be marked as writable\n            // again once the capacity increases.\n            //\n            // Since the stream is writable already, mark it here instead.\n            self.streams.insert_writable(&priority_key);\n        }\n\n        Ok(false)\n    }\n\n    /// Returns true if all the data has been read from the specified stream.\n    ///\n    /// This instructs the application that all the data received from the\n    /// peer on the stream has been read, and there won't be anymore in the\n    /// future.\n    ///\n    /// Basically this returns true when the peer either set the `fin` flag\n    /// for the stream, or sent `RESET_STREAM`.\n    #[inline]\n    pub fn stream_finished(&self, stream_id: u64) -> bool {\n        let stream = match self.streams.get(stream_id) {\n            Some(v) => v,\n\n            None => return true,\n        };\n\n        stream.recv.is_fin()\n    }\n\n    /// Returns the number of bidirectional streams that can be created\n    /// before the peer's stream count limit is reached.\n    ///\n    /// This can be useful to know if it's possible to create a bidirectional\n    /// stream without trying it first.\n    #[inline]\n    pub fn peer_streams_left_bidi(&self) -> u64 {\n        self.streams.peer_streams_left_bidi()\n    }\n\n    /// Returns the number of unidirectional streams that can be created\n    /// before the peer's stream count limit is reached.\n    ///\n    /// This can be useful to know if it's possible to create a unidirectional\n    /// stream without trying it first.\n    #[inline]\n    pub fn peer_streams_left_uni(&self) -> u64 {\n        self.streams.peer_streams_left_uni()\n    }\n\n    /// Returns an iterator over streams that have outstanding data to read.\n    ///\n    /// Note that the iterator will only include streams that were readable at\n    /// the time the iterator itself was created (i.e. when `readable()` was\n    /// called). To account for newly readable streams, the iterator needs to\n    /// be created again.\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut buf = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// // Iterate over readable streams.\n    /// for stream_id in conn.readable() {\n    ///     // Stream is readable, read until there's no more data.\n    ///     while let Ok((read, fin)) = conn.stream_recv(stream_id, &mut buf) {\n    ///         println!(\"Got {} bytes on stream {}\", read, stream_id);\n    ///     }\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    #[inline]\n    pub fn readable(&self) -> StreamIter {\n        self.streams.readable()\n    }\n\n    /// Returns an iterator over streams that can be written in priority order.\n    ///\n    /// The priority order is based on RFC 9218 scheduling recommendations.\n    /// Stream priority can be controlled using [`stream_priority()`]. In order\n    /// to support fairness requirements, each time this method is called,\n    /// internal state is updated. Therefore the iterator ordering can change\n    /// between calls, even if no streams were added or removed.\n    ///\n    /// A \"writable\" stream is a stream that has enough flow control capacity to\n    /// send data to the peer. To avoid buffering an infinite amount of data,\n    /// streams are only allowed to buffer outgoing data up to the amount that\n    /// the peer allows to send.\n    ///\n    /// Note that the iterator will only include streams that were writable at\n    /// the time the iterator itself was created (i.e. when `writable()` was\n    /// called). To account for newly writable streams, the iterator needs to be\n    /// created again.\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut buf = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let local = socket.local_addr().unwrap();\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// // Iterate over writable streams.\n    /// for stream_id in conn.writable() {\n    ///     // Stream is writable, write some data.\n    ///     if let Ok(written) = conn.stream_send(stream_id, &buf, false) {\n    ///         println!(\"Written {} bytes on stream {}\", written, stream_id);\n    ///     }\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    /// [`stream_priority()`]: struct.Connection.html#method.stream_priority\n    #[inline]\n    pub fn writable(&self) -> StreamIter {\n        // If there is not enough connection-level send capacity, none of the\n        // streams are writable, so return an empty iterator.\n        if self.tx_cap == 0 {\n            return StreamIter::default();\n        }\n\n        self.streams.writable()\n    }\n\n    /// Returns the maximum possible size of egress UDP payloads.\n    ///\n    /// This is the maximum size of UDP payloads that can be sent, and depends\n    /// on both the configured maximum send payload size of the local endpoint\n    /// (as configured with [`set_max_send_udp_payload_size()`]), as well as\n    /// the transport parameter advertised by the remote peer.\n    ///\n    /// Note that this value can change during the lifetime of the connection,\n    /// but should remain stable across consecutive calls to [`send()`].\n    ///\n    /// [`set_max_send_udp_payload_size()`]:\n    ///     struct.Config.html#method.set_max_send_udp_payload_size\n    /// [`send()`]: struct.Connection.html#method.send\n    pub fn max_send_udp_payload_size(&self) -> usize {\n        let max_datagram_size = self\n            .paths\n            .get_active()\n            .ok()\n            .map(|p| p.recovery.max_datagram_size());\n\n        if let Some(max_datagram_size) = max_datagram_size {\n            if self.is_established() {\n                // We cap the maximum packet size to 16KB or so, so that it can be\n                // always encoded with a 2-byte varint.\n                return cmp::min(16383, max_datagram_size);\n            }\n        }\n\n        // Allow for 1200 bytes (minimum QUIC packet size) during the\n        // handshake.\n        MIN_CLIENT_INITIAL_LEN\n    }\n\n    /// Schedule an ack-eliciting packet on the active path.\n    ///\n    /// QUIC packets might not contain ack-eliciting frames during normal\n    /// operating conditions. If the packet would already contain\n    /// ack-eliciting frames, this method does not change any behavior.\n    /// However, if the packet would not ordinarily contain ack-eliciting\n    /// frames, this method ensures that a PING frame sent.\n    ///\n    /// Calling this method multiple times before [`send()`] has no effect.\n    ///\n    /// [`send()`]: struct.Connection.html#method.send\n    pub fn send_ack_eliciting(&mut self) -> Result<()> {\n        if self.is_closed() || self.is_draining() {\n            return Ok(());\n        }\n        self.paths.get_active_mut()?.needs_ack_eliciting = true;\n        Ok(())\n    }\n\n    /// Schedule an ack-eliciting packet on the specified path.\n    ///\n    /// See [`send_ack_eliciting()`] for more detail. [`InvalidState`] is\n    /// returned if there is no record of the path.\n    ///\n    /// [`send_ack_eliciting()`]: struct.Connection.html#method.send_ack_eliciting\n    /// [`InvalidState`]: enum.Error.html#variant.InvalidState\n    pub fn send_ack_eliciting_on_path(\n        &mut self, local: SocketAddr, peer: SocketAddr,\n    ) -> Result<()> {\n        if self.is_closed() || self.is_draining() {\n            return Ok(());\n        }\n        let path_id = self\n            .paths\n            .path_id_from_addrs(&(local, peer))\n            .ok_or(Error::InvalidState)?;\n        self.paths.get_mut(path_id)?.needs_ack_eliciting = true;\n        Ok(())\n    }\n\n    /// Reads the first received DATAGRAM.\n    ///\n    /// On success the DATAGRAM's data is returned along with its size.\n    ///\n    /// [`Done`] is returned if there is no data to read.\n    ///\n    /// [`BufferTooShort`] is returned if the provided buffer is too small for\n    /// the DATAGRAM.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`BufferTooShort`]: enum.Error.html#variant.BufferTooShort\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut buf = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// let mut dgram_buf = [0; 512];\n    /// while let Ok((len)) = conn.dgram_recv(&mut dgram_buf) {\n    ///     println!(\"Got {} bytes of DATAGRAM\", len);\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    #[inline]\n    pub fn dgram_recv(&mut self, buf: &mut [u8]) -> Result<usize> {\n        match self.dgram_recv_queue.pop() {\n            Some(d) => {\n                if d.len() > buf.len() {\n                    return Err(Error::BufferTooShort);\n                }\n\n                buf[..d.len()].copy_from_slice(&d);\n                Ok(d.len())\n            },\n\n            None => Err(Error::Done),\n        }\n    }\n\n    /// Reads the first received DATAGRAM.\n    ///\n    /// This is the same as [`dgram_recv()`] but returns the DATAGRAM as a\n    /// `Vec<u8>` instead of copying into the provided buffer.\n    ///\n    /// [`dgram_recv()`]: struct.Connection.html#method.dgram_recv\n    #[inline]\n    pub fn dgram_recv_vec(&mut self) -> Result<Vec<u8>> {\n        match self.dgram_recv_queue.pop() {\n            Some(d) => Ok(d),\n\n            None => Err(Error::Done),\n        }\n    }\n\n    /// Reads the first received DATAGRAM without removing it from the queue.\n    ///\n    /// On success the DATAGRAM's data is returned along with the actual number\n    /// of bytes peeked. The requested length cannot exceed the DATAGRAM's\n    /// actual length.\n    ///\n    /// [`Done`] is returned if there is no data to read.\n    ///\n    /// [`BufferTooShort`] is returned if the provided buffer is smaller the\n    /// number of bytes to peek.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`BufferTooShort`]: enum.Error.html#variant.BufferTooShort\n    #[inline]\n    pub fn dgram_recv_peek(&self, buf: &mut [u8], len: usize) -> Result<usize> {\n        self.dgram_recv_queue.peek_front_bytes(buf, len)\n    }\n\n    /// Returns the length of the first stored DATAGRAM.\n    #[inline]\n    pub fn dgram_recv_front_len(&self) -> Option<usize> {\n        self.dgram_recv_queue.peek_front_len()\n    }\n\n    /// Returns the number of items in the DATAGRAM receive queue.\n    #[inline]\n    pub fn dgram_recv_queue_len(&self) -> usize {\n        self.dgram_recv_queue.len()\n    }\n\n    /// Returns the total size of all items in the DATAGRAM receive queue.\n    #[inline]\n    pub fn dgram_recv_queue_byte_size(&self) -> usize {\n        self.dgram_recv_queue.byte_size()\n    }\n\n    /// Returns the number of items in the DATAGRAM send queue.\n    #[inline]\n    pub fn dgram_send_queue_len(&self) -> usize {\n        self.dgram_send_queue.len()\n    }\n\n    /// Returns the total size of all items in the DATAGRAM send queue.\n    #[inline]\n    pub fn dgram_send_queue_byte_size(&self) -> usize {\n        self.dgram_send_queue.byte_size()\n    }\n\n    /// Returns whether or not the DATAGRAM send queue is full.\n    #[inline]\n    pub fn is_dgram_send_queue_full(&self) -> bool {\n        self.dgram_send_queue.is_full()\n    }\n\n    /// Returns whether or not the DATAGRAM recv queue is full.\n    #[inline]\n    pub fn is_dgram_recv_queue_full(&self) -> bool {\n        self.dgram_recv_queue.is_full()\n    }\n\n    /// Sends data in a DATAGRAM frame.\n    ///\n    /// [`Done`] is returned if no data was written.\n    /// [`InvalidState`] is returned if the peer does not support DATAGRAM.\n    /// [`BufferTooShort`] is returned if the DATAGRAM frame length is larger\n    /// than peer's supported DATAGRAM frame length. Use\n    /// [`dgram_max_writable_len()`] to get the largest supported DATAGRAM\n    /// frame length.\n    ///\n    /// Note that there is no flow control of DATAGRAM frames, so in order to\n    /// avoid buffering an infinite amount of frames we apply an internal\n    /// limit.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`InvalidState`]: enum.Error.html#variant.InvalidState\n    /// [`BufferTooShort`]: enum.Error.html#variant.BufferTooShort\n    /// [`dgram_max_writable_len()`]:\n    /// struct.Connection.html#method.dgram_max_writable_len\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut buf = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// conn.dgram_send(b\"hello\")?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    pub fn dgram_send(&mut self, buf: &[u8]) -> Result<()> {\n        let max_payload_len = match self.dgram_max_writable_len() {\n            Some(v) => v,\n\n            None => return Err(Error::InvalidState),\n        };\n\n        if buf.len() > max_payload_len {\n            return Err(Error::BufferTooShort);\n        }\n\n        self.dgram_send_queue.push(buf.to_vec())?;\n\n        let active_path = self.paths.get_active_mut()?;\n\n        if self.dgram_send_queue.byte_size() >\n            active_path.recovery.cwnd_available()\n        {\n            active_path.recovery.update_app_limited(false);\n        }\n\n        Ok(())\n    }\n\n    /// Sends data in a DATAGRAM frame.\n    ///\n    /// This is the same as [`dgram_send()`] but takes a `Vec<u8>` instead of\n    /// a slice.\n    ///\n    /// [`dgram_send()`]: struct.Connection.html#method.dgram_send\n    pub fn dgram_send_vec(&mut self, buf: Vec<u8>) -> Result<()> {\n        let max_payload_len = match self.dgram_max_writable_len() {\n            Some(v) => v,\n\n            None => return Err(Error::InvalidState),\n        };\n\n        if buf.len() > max_payload_len {\n            return Err(Error::BufferTooShort);\n        }\n\n        self.dgram_send_queue.push(buf)?;\n\n        let active_path = self.paths.get_active_mut()?;\n\n        if self.dgram_send_queue.byte_size() >\n            active_path.recovery.cwnd_available()\n        {\n            active_path.recovery.update_app_limited(false);\n        }\n\n        Ok(())\n    }\n\n    /// Purges queued outgoing DATAGRAMs matching the predicate.\n    ///\n    /// In other words, remove all elements `e` such that `f(&e)` returns true.\n    ///\n    /// ## Examples:\n    /// ```no_run\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// conn.dgram_send(b\"hello\")?;\n    /// conn.dgram_purge_outgoing(&|d: &[u8]| -> bool { d[0] == 0 });\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    #[inline]\n    pub fn dgram_purge_outgoing<FN: Fn(&[u8]) -> bool>(&mut self, f: FN) {\n        self.dgram_send_queue.purge(f);\n    }\n\n    /// Returns the maximum DATAGRAM payload that can be sent.\n    ///\n    /// [`None`] is returned if the peer hasn't advertised a maximum DATAGRAM\n    /// frame size.\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut buf = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let local = socket.local_addr().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// if let Some(payload_size) = conn.dgram_max_writable_len() {\n    ///     if payload_size > 5 {\n    ///         conn.dgram_send(b\"hello\")?;\n    ///     }\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    #[inline]\n    pub fn dgram_max_writable_len(&self) -> Option<usize> {\n        match self.peer_transport_params.max_datagram_frame_size {\n            None => None,\n            Some(peer_frame_len) => {\n                let dcid = self.destination_id();\n                // Start from the maximum packet size...\n                let mut max_len = self.max_send_udp_payload_size();\n                // ...subtract the Short packet header overhead...\n                // (1 byte of pkt_len + len of dcid)\n                max_len = max_len.saturating_sub(1 + dcid.len());\n                // ...subtract the packet number (max len)...\n                max_len = max_len.saturating_sub(packet::MAX_PKT_NUM_LEN);\n                // ...subtract the crypto overhead...\n                max_len = max_len.saturating_sub(\n                    self.crypto_ctx[packet::Epoch::Application]\n                        .crypto_overhead()?,\n                );\n                // ...clamp to what peer can support...\n                max_len = cmp::min(peer_frame_len as usize, max_len);\n                // ...subtract frame overhead, checked for underflow.\n                // (1 byte of frame type + len of length )\n                max_len.checked_sub(1 + frame::MAX_DGRAM_OVERHEAD)\n            },\n        }\n    }\n\n    fn dgram_enabled(&self) -> bool {\n        self.local_transport_params\n            .max_datagram_frame_size\n            .is_some()\n    }\n\n    /// Returns when the next timeout event will occur.\n    ///\n    /// Once the timeout Instant has been reached, the [`on_timeout()`] method\n    /// should be called. A timeout of `None` means that the timer should be\n    /// disarmed.\n    ///\n    /// [`on_timeout()`]: struct.Connection.html#method.on_timeout\n    pub fn timeout_instant(&self) -> Option<Instant> {\n        if self.is_closed() {\n            return None;\n        }\n\n        if self.is_draining() {\n            // Draining timer takes precedence over all other timers. If it is\n            // set it means the connection is closing so there's no point in\n            // processing the other timers.\n            self.draining_timer\n        } else {\n            // Use the lowest timer value (i.e. \"sooner\") among idle and loss\n            // detection timers. If they are both unset (i.e. `None`) then the\n            // result is `None`, but if at least one of them is set then a\n            // `Some(...)` value is returned.\n            let path_timer = self\n                .paths\n                .iter()\n                .filter_map(|(_, p)| p.recovery.loss_detection_timer())\n                .min();\n\n            let key_update_timer = self.crypto_ctx[packet::Epoch::Application]\n                .key_update\n                .as_ref()\n                .map(|key_update| key_update.timer);\n\n            let timers = [self.idle_timer, path_timer, key_update_timer];\n\n            timers.iter().filter_map(|&x| x).min()\n        }\n    }\n\n    /// Returns the amount of time until the next timeout event.\n    ///\n    /// Once the given duration has elapsed, the [`on_timeout()`] method should\n    /// be called. A timeout of `None` means that the timer should be disarmed.\n    ///\n    /// [`on_timeout()`]: struct.Connection.html#method.on_timeout\n    pub fn timeout(&self) -> Option<Duration> {\n        self.timeout_instant().map(|timeout| {\n            let now = Instant::now();\n\n            if timeout <= now {\n                Duration::ZERO\n            } else {\n                timeout.duration_since(now)\n            }\n        })\n    }\n\n    /// Processes a timeout event.\n    ///\n    /// If no timeout has occurred it does nothing.\n    pub fn on_timeout(&mut self) {\n        let now = Instant::now();\n\n        if let Some(draining_timer) = self.draining_timer {\n            if draining_timer <= now {\n                trace!(\"{} draining timeout expired\", self.trace_id);\n\n                self.mark_closed();\n            }\n\n            // Draining timer takes precedence over all other timers. If it is\n            // set it means the connection is closing so there's no point in\n            // processing the other timers.\n            return;\n        }\n\n        if let Some(timer) = self.idle_timer {\n            if timer <= now {\n                trace!(\"{} idle timeout expired\", self.trace_id);\n\n                self.mark_closed();\n                self.timed_out = true;\n                return;\n            }\n        }\n\n        if let Some(timer) = self.crypto_ctx[packet::Epoch::Application]\n            .key_update\n            .as_ref()\n            .map(|key_update| key_update.timer)\n        {\n            if timer <= now {\n                // Discard previous key once key update timer expired.\n                let _ = self.crypto_ctx[packet::Epoch::Application]\n                    .key_update\n                    .take();\n            }\n        }\n\n        let handshake_status = self.handshake_status();\n\n        for (_, p) in self.paths.iter_mut() {\n            if let Some(timer) = p.recovery.loss_detection_timer() {\n                if timer <= now {\n                    trace!(\"{} loss detection timeout expired\", self.trace_id);\n\n                    let OnLossDetectionTimeoutOutcome {\n                        lost_packets,\n                        lost_bytes,\n                    } = p.on_loss_detection_timeout(\n                        handshake_status,\n                        now,\n                        self.is_server,\n                        &self.trace_id,\n                    );\n\n                    self.lost_count += lost_packets;\n                    self.lost_bytes += lost_bytes as u64;\n\n                    qlog_with_type!(QLOG_METRICS, self.qlog, q, {\n                        p.recovery.maybe_qlog(q, now);\n                    });\n                }\n            }\n        }\n\n        // Notify timeout events to the application.\n        self.paths.notify_failed_validations();\n\n        // If the active path failed, try to find a new candidate.\n        if self.paths.get_active_path_id().is_err() {\n            match self.paths.find_candidate_path() {\n                Some(pid) => {\n                    if self.set_active_path(pid, now).is_err() {\n                        // The connection cannot continue.\n                        self.mark_closed();\n                    }\n                },\n\n                // The connection cannot continue.\n                None => {\n                    self.mark_closed();\n                },\n            }\n        }\n    }\n\n    /// Requests the stack to perform path validation of the proposed 4-tuple.\n    ///\n    /// Probing new paths requires spare Connection IDs at both the host and the\n    /// peer sides. If it is not the case, it raises an [`OutOfIdentifiers`].\n    ///\n    /// The probing of new addresses can only be done by the client. The server\n    /// can only probe network paths that were previously advertised by\n    /// [`PathEvent::New`]. If the server tries to probe such an unseen network\n    /// path, this call raises an [`InvalidState`].\n    ///\n    /// The caller might also want to probe an existing path. In such case, it\n    /// triggers a PATH_CHALLENGE frame, but it does not require spare CIDs.\n    ///\n    /// A server always probes a new path it observes. Calling this method is\n    /// hence not required to validate a new path. However, a server can still\n    /// request an additional path validation of the proposed 4-tuple.\n    ///\n    /// Calling this method several times before calling [`send()`] or\n    /// [`send_on_path()`] results in a single probe being generated. An\n    /// application wanting to send multiple in-flight probes must call this\n    /// method again after having sent packets.\n    ///\n    /// Returns the Destination Connection ID sequence number associated to that\n    /// path.\n    ///\n    /// [`PathEvent::New`]: enum.PathEvent.html#variant.New\n    /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers\n    /// [`InvalidState`]: enum.Error.html#InvalidState\n    /// [`send()`]: struct.Connection.html#method.send\n    /// [`send_on_path()`]: struct.Connection.html#method.send_on_path\n    pub fn probe_path(\n        &mut self, local_addr: SocketAddr, peer_addr: SocketAddr,\n    ) -> Result<u64> {\n        // We may want to probe an existing path.\n        let pid = match self.paths.path_id_from_addrs(&(local_addr, peer_addr)) {\n            Some(pid) => pid,\n            None => self.create_path_on_client(local_addr, peer_addr)?,\n        };\n\n        let path = self.paths.get_mut(pid)?;\n        path.request_validation();\n\n        path.active_dcid_seq.ok_or(Error::InvalidState)\n    }\n\n    /// Migrates the connection to a new local address `local_addr`.\n    ///\n    /// The behavior is similar to [`migrate()`], with the nuance that the\n    /// connection only changes the local address, but not the peer one.\n    ///\n    /// See [`migrate()`] for the full specification of this method.\n    ///\n    /// [`migrate()`]: struct.Connection.html#method.migrate\n    pub fn migrate_source(&mut self, local_addr: SocketAddr) -> Result<u64> {\n        let peer_addr = self.paths.get_active()?.peer_addr();\n        self.migrate(local_addr, peer_addr)\n    }\n\n    /// Migrates the connection over the given network path between `local_addr`\n    /// and `peer_addr`.\n    ///\n    /// Connection migration can only be initiated by the client. Calling this\n    /// method as a server returns [`InvalidState`].\n    ///\n    /// To initiate voluntary migration, there should be enough Connection IDs\n    /// at both sides. If this requirement is not satisfied, this call returns\n    /// [`OutOfIdentifiers`].\n    ///\n    /// Returns the Destination Connection ID associated to that migrated path.\n    ///\n    /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers\n    /// [`InvalidState`]: enum.Error.html#InvalidState\n    pub fn migrate(\n        &mut self, local_addr: SocketAddr, peer_addr: SocketAddr,\n    ) -> Result<u64> {\n        if self.is_server {\n            return Err(Error::InvalidState);\n        }\n\n        // If the path already exists, mark it as the active one.\n        let (pid, dcid_seq) = if let Some(pid) =\n            self.paths.path_id_from_addrs(&(local_addr, peer_addr))\n        {\n            let path = self.paths.get_mut(pid)?;\n\n            // If it is already active, do nothing.\n            if path.active() {\n                return path.active_dcid_seq.ok_or(Error::OutOfIdentifiers);\n            }\n\n            // Ensures that a Source Connection ID has been dedicated to this\n            // path, or a free one is available. This is only required if the\n            // host uses non-zero length Source Connection IDs.\n            if !self.ids.zero_length_scid() &&\n                path.active_scid_seq.is_none() &&\n                self.ids.available_scids() == 0\n            {\n                return Err(Error::OutOfIdentifiers);\n            }\n\n            // Ensures that the migrated path has a Destination Connection ID.\n            let dcid_seq = if let Some(dcid_seq) = path.active_dcid_seq {\n                dcid_seq\n            } else {\n                let dcid_seq = self\n                    .ids\n                    .lowest_available_dcid_seq()\n                    .ok_or(Error::OutOfIdentifiers)?;\n\n                self.ids.link_dcid_to_path_id(dcid_seq, pid)?;\n                path.active_dcid_seq = Some(dcid_seq);\n\n                dcid_seq\n            };\n\n            (pid, dcid_seq)\n        } else {\n            let pid = self.create_path_on_client(local_addr, peer_addr)?;\n\n            let dcid_seq = self\n                .paths\n                .get(pid)?\n                .active_dcid_seq\n                .ok_or(Error::InvalidState)?;\n\n            (pid, dcid_seq)\n        };\n\n        // Change the active path.\n        self.set_active_path(pid, Instant::now())?;\n\n        Ok(dcid_seq)\n    }\n\n    /// Provides additional source Connection IDs that the peer can use to reach\n    /// this host.\n    ///\n    /// This triggers sending NEW_CONNECTION_ID frames if the provided Source\n    /// Connection ID is not already present. In the case the caller tries to\n    /// reuse a Connection ID with a different reset token, this raises an\n    /// `InvalidState`.\n    ///\n    /// At any time, the peer cannot have more Destination Connection IDs than\n    /// the maximum number of active Connection IDs it negotiated. In such case\n    /// (i.e., when [`scids_left()`] returns 0), if the host agrees to\n    /// request the removal of previous connection IDs, it sets the\n    /// `retire_if_needed` parameter. Otherwise, an [`IdLimit`] is returned.\n    ///\n    /// Note that setting `retire_if_needed` does not prevent this function from\n    /// returning an [`IdLimit`] in the case the caller wants to retire still\n    /// unannounced Connection IDs.\n    ///\n    /// The caller is responsible for ensuring that the provided `scid` is not\n    /// repeated several times over the connection. quiche ensures that as long\n    /// as the provided Connection ID is still in use (i.e., not retired), it\n    /// does not assign a different sequence number.\n    ///\n    /// Note that if the host uses zero-length Source Connection IDs, it cannot\n    /// advertise Source Connection IDs and calling this method returns an\n    /// [`InvalidState`].\n    ///\n    /// Returns the sequence number associated to the provided Connection ID.\n    ///\n    /// [`scids_left()`]: struct.Connection.html#method.scids_left\n    /// [`IdLimit`]: enum.Error.html#IdLimit\n    /// [`InvalidState`]: enum.Error.html#InvalidState\n    pub fn new_scid(\n        &mut self, scid: &ConnectionId, reset_token: u128, retire_if_needed: bool,\n    ) -> Result<u64> {\n        self.ids.new_scid(\n            scid.to_vec().into(),\n            Some(reset_token),\n            true,\n            None,\n            retire_if_needed,\n        )\n    }\n\n    /// Returns the number of source Connection IDs that are active. This is\n    /// only meaningful if the host uses non-zero length Source Connection IDs.\n    pub fn active_scids(&self) -> usize {\n        self.ids.active_source_cids()\n    }\n\n    /// Returns the number of source Connection IDs that should be provided\n    /// to the peer without exceeding the limit it advertised.\n    ///\n    /// This will automatically limit the number of Connection IDs to the\n    /// minimum between the locally configured active connection ID limit,\n    /// and the one sent by the peer.\n    ///\n    /// To obtain the maximum possible value allowed by the peer an application\n    /// can instead inspect the [`peer_active_conn_id_limit`] value.\n    ///\n    /// [`peer_active_conn_id_limit`]: struct.Stats.html#structfield.peer_active_conn_id_limit\n    #[inline]\n    pub fn scids_left(&self) -> usize {\n        let max_active_source_cids = cmp::min(\n            self.peer_transport_params.active_conn_id_limit,\n            self.local_transport_params.active_conn_id_limit,\n        ) as usize;\n\n        max_active_source_cids - self.active_scids()\n    }\n\n    /// Requests the retirement of the destination Connection ID used by the\n    /// host to reach its peer.\n    ///\n    /// This triggers sending RETIRE_CONNECTION_ID frames.\n    ///\n    /// If the application tries to retire a non-existing Destination Connection\n    /// ID sequence number, or if it uses zero-length Destination Connection ID,\n    /// this method returns an [`InvalidState`].\n    ///\n    /// At any time, the host must have at least one Destination ID. If the\n    /// application tries to retire the last one, or if the caller tries to\n    /// retire the destination Connection ID used by the current active path\n    /// while having neither spare Destination Connection IDs nor validated\n    /// network paths, this method returns an [`OutOfIdentifiers`]. This\n    /// behavior prevents the caller from stalling the connection due to the\n    /// lack of validated path to send non-probing packets.\n    ///\n    /// [`InvalidState`]: enum.Error.html#InvalidState\n    /// [`OutOfIdentifiers`]: enum.Error.html#OutOfIdentifiers\n    pub fn retire_dcid(&mut self, dcid_seq: u64) -> Result<()> {\n        if self.ids.zero_length_dcid() {\n            return Err(Error::InvalidState);\n        }\n\n        let active_path_dcid_seq = self\n            .paths\n            .get_active()?\n            .active_dcid_seq\n            .ok_or(Error::InvalidState)?;\n\n        let active_path_id = self.paths.get_active_path_id()?;\n\n        if active_path_dcid_seq == dcid_seq &&\n            self.ids.lowest_available_dcid_seq().is_none() &&\n            !self\n                .paths\n                .iter()\n                .any(|(pid, p)| pid != active_path_id && p.usable())\n        {\n            return Err(Error::OutOfIdentifiers);\n        }\n\n        if let Some(pid) = self.ids.retire_dcid(dcid_seq)? {\n            // The retired Destination CID was associated to a given path. Let's\n            // find an available DCID to associate to that path.\n            let path = self.paths.get_mut(pid)?;\n            let dcid_seq = self.ids.lowest_available_dcid_seq();\n\n            if let Some(dcid_seq) = dcid_seq {\n                self.ids.link_dcid_to_path_id(dcid_seq, pid)?;\n            }\n\n            path.active_dcid_seq = dcid_seq;\n        }\n\n        Ok(())\n    }\n\n    /// Processes path-specific events.\n    ///\n    /// On success it returns a [`PathEvent`], or `None` when there are no\n    /// events to report. Please refer to [`PathEvent`] for the exhaustive event\n    /// list.\n    ///\n    /// Note that all events are edge-triggered, meaning that once reported they\n    /// will not be reported again by calling this method again, until the event\n    /// is re-armed.\n    ///\n    /// [`PathEvent`]: enum.PathEvent.html\n    pub fn path_event_next(&mut self) -> Option<PathEvent> {\n        self.paths.pop_event()\n    }\n\n    /// Returns the number of source Connection IDs that are retired.\n    pub fn retired_scids(&self) -> usize {\n        self.ids.retired_source_cids()\n    }\n\n    /// Returns a source `ConnectionId` that has been retired.\n    ///\n    /// On success it returns a [`ConnectionId`], or `None` when there are no\n    /// more retired connection IDs.\n    ///\n    /// [`ConnectionId`]: struct.ConnectionId.html\n    pub fn retired_scid_next(&mut self) -> Option<ConnectionId<'static>> {\n        self.ids.pop_retired_scid()\n    }\n\n    /// Returns the number of spare Destination Connection IDs, i.e.,\n    /// Destination Connection IDs that are still unused.\n    ///\n    /// Note that this function returns 0 if the host uses zero length\n    /// Destination Connection IDs.\n    pub fn available_dcids(&self) -> usize {\n        self.ids.available_dcids()\n    }\n\n    /// Returns an iterator over destination `SockAddr`s whose association\n    /// with `from` forms a known QUIC path on which packets can be sent to.\n    ///\n    /// This function is typically used in combination with [`send_on_path()`].\n    ///\n    /// Note that the iterator includes all the possible combination of\n    /// destination `SockAddr`s, even those whose sending is not required now.\n    /// In other words, this is another way for the application to recall from\n    /// past [`PathEvent::New`] events.\n    ///\n    /// [`PathEvent::New`]: enum.PathEvent.html#variant.New\n    /// [`send_on_path()`]: struct.Connection.html#method.send_on_path\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # let mut out = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;\n    /// # let scid = quiche::ConnectionId::from_ref(&[0xba; 16]);\n    /// # let local = socket.local_addr().unwrap();\n    /// # let peer = \"127.0.0.1:1234\".parse().unwrap();\n    /// # let mut conn = quiche::accept(&scid, None, local, peer, &mut config)?;\n    /// // Iterate over possible destinations for the given local `SockAddr`.\n    /// for dest in conn.paths_iter(local) {\n    ///     loop {\n    ///         let (write, send_info) =\n    ///             match conn.send_on_path(&mut out, Some(local), Some(dest)) {\n    ///                 Ok(v) => v,\n    ///\n    ///                 Err(quiche::Error::Done) => {\n    ///                     // Done writing for this destination.\n    ///                     break;\n    ///                 },\n    ///\n    ///                 Err(e) => {\n    ///                     // An error occurred, handle it.\n    ///                     break;\n    ///                 },\n    ///             };\n    ///\n    ///         socket.send_to(&out[..write], &send_info.to).unwrap();\n    ///     }\n    /// }\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    #[inline]\n    pub fn paths_iter(&self, from: SocketAddr) -> SocketAddrIter {\n        // Instead of trying to identify whether packets will be sent on the\n        // given 4-tuple, simply filter paths that cannot be used.\n        SocketAddrIter {\n            sockaddrs: self\n                .paths\n                .iter()\n                .filter(|(_, p)| p.active_dcid_seq.is_some())\n                .filter(|(_, p)| p.usable() || p.probing_required())\n                .filter(|(_, p)| p.local_addr() == from)\n                .map(|(_, p)| p.peer_addr())\n                .collect(),\n\n            index: 0,\n        }\n    }\n\n    /// Closes the connection with the given error and reason.\n    ///\n    /// The `app` parameter specifies whether an application close should be\n    /// sent to the peer. Otherwise a normal connection close is sent.\n    ///\n    /// If `app` is true but the connection is not in a state that is safe to\n    /// send an application error (not established nor in early data), in\n    /// accordance with [RFC\n    /// 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.3-3), the\n    /// error code is changed to APPLICATION_ERROR and the reason phrase is\n    /// cleared.\n    ///\n    /// Returns [`Done`] if the connection had already been closed.\n    ///\n    /// Note that the connection will not be closed immediately. An application\n    /// should continue calling the [`recv()`], [`send()`], [`timeout()`] and\n    /// [`on_timeout()`] methods as normal, until the [`is_closed()`] method\n    /// returns `true`.\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    /// [`recv()`]: struct.Connection.html#method.recv\n    /// [`send()`]: struct.Connection.html#method.send\n    /// [`timeout()`]: struct.Connection.html#method.timeout\n    /// [`on_timeout()`]: struct.Connection.html#method.on_timeout\n    /// [`is_closed()`]: struct.Connection.html#method.is_closed\n    pub fn close(&mut self, app: bool, err: u64, reason: &[u8]) -> Result<()> {\n        if self.is_closed() || self.is_draining() {\n            return Err(Error::Done);\n        }\n\n        if self.local_error.is_some() {\n            return Err(Error::Done);\n        }\n\n        let is_safe_to_send_app_data =\n            self.is_established() || self.is_in_early_data();\n\n        if app && !is_safe_to_send_app_data {\n            // Clear error information.\n            self.local_error = Some(ConnectionError {\n                is_app: false,\n                error_code: 0x0c,\n                reason: vec![],\n            });\n        } else {\n            self.local_error = Some(ConnectionError {\n                is_app: app,\n                error_code: err,\n                reason: reason.to_vec(),\n            });\n        }\n\n        // When no packet was successfully processed close connection immediately.\n        if self.recv_count == 0 {\n            self.mark_closed();\n        }\n\n        Ok(())\n    }\n\n    /// Returns a string uniquely representing the connection.\n    ///\n    /// This can be used for logging purposes to differentiate between multiple\n    /// connections.\n    #[inline]\n    pub fn trace_id(&self) -> &str {\n        &self.trace_id\n    }\n\n    /// Returns the negotiated ALPN protocol.\n    ///\n    /// If no protocol has been negotiated, the returned value is empty.\n    #[inline]\n    pub fn application_proto(&self) -> &[u8] {\n        self.alpn.as_ref()\n    }\n\n    /// Returns the server name requested by the client.\n    #[inline]\n    pub fn server_name(&self) -> Option<&str> {\n        self.handshake.server_name()\n    }\n\n    /// Returns the peer's leaf certificate (if any) as a DER-encoded buffer.\n    #[inline]\n    pub fn peer_cert(&self) -> Option<&[u8]> {\n        self.handshake.peer_cert()\n    }\n\n    /// Returns the peer's certificate chain (if any) as a vector of DER-encoded\n    /// buffers.\n    ///\n    /// The certificate at index 0 is the peer's leaf certificate, the other\n    /// certificates (if any) are the chain certificate authorities used to\n    /// sign the leaf certificate.\n    #[inline]\n    pub fn peer_cert_chain(&self) -> Option<Vec<&[u8]>> {\n        self.handshake.peer_cert_chain()\n    }\n\n    /// Returns the serialized cryptographic session for the connection.\n    ///\n    /// This can be used by a client to cache a connection's session, and resume\n    /// it later using the [`set_session()`] method.\n    ///\n    /// [`set_session()`]: struct.Connection.html#method.set_session\n    #[inline]\n    pub fn session(&self) -> Option<&[u8]> {\n        self.session.as_deref()\n    }\n\n    /// Returns the source connection ID.\n    ///\n    /// When there are multiple IDs, and if there is an active path, the ID used\n    /// on that path is returned. Otherwise the oldest ID is returned.\n    ///\n    /// Note that the value returned can change throughout the connection's\n    /// lifetime.\n    #[inline]\n    pub fn source_id(&self) -> ConnectionId<'_> {\n        if let Ok(path) = self.paths.get_active() {\n            if let Some(active_scid_seq) = path.active_scid_seq {\n                if let Ok(e) = self.ids.get_scid(active_scid_seq) {\n                    return ConnectionId::from_ref(e.cid.as_ref());\n                }\n            }\n        }\n\n        let e = self.ids.oldest_scid();\n        ConnectionId::from_ref(e.cid.as_ref())\n    }\n\n    /// Returns all active source connection IDs.\n    ///\n    /// An iterator is returned for all active IDs (i.e. ones that have not\n    /// been explicitly retired yet).\n    #[inline]\n    pub fn source_ids(&self) -> impl Iterator<Item = &ConnectionId<'_>> {\n        self.ids.scids_iter()\n    }\n\n    /// Returns the destination connection ID.\n    ///\n    /// Note that the value returned can change throughout the connection's\n    /// lifetime.\n    #[inline]\n    pub fn destination_id(&self) -> ConnectionId<'_> {\n        if let Ok(path) = self.paths.get_active() {\n            if let Some(active_dcid_seq) = path.active_dcid_seq {\n                if let Ok(e) = self.ids.get_dcid(active_dcid_seq) {\n                    return ConnectionId::from_ref(e.cid.as_ref());\n                }\n            }\n        }\n\n        let e = self.ids.oldest_dcid();\n        ConnectionId::from_ref(e.cid.as_ref())\n    }\n\n    /// Returns the PMTU for the active path if it exists.\n    ///\n    /// This requires no additonal packets to be sent but simply checks if PMTUD\n    /// has completed and has found a valid PMTU.\n    #[inline]\n    pub fn pmtu(&self) -> Option<usize> {\n        if let Ok(path) = self.paths.get_active() {\n            path.pmtud.as_ref().and_then(|pmtud| pmtud.get_pmtu())\n        } else {\n            None\n        }\n    }\n\n    /// Revalidates the PMTU for the active path by sending a new probe packet\n    /// of PMTU size. If the probe is dropped PMTUD will restart and find a new\n    /// valid PMTU.\n    #[inline]\n    pub fn revalidate_pmtu(&mut self) {\n        if let Ok(active_path) = self.paths.get_active_mut() {\n            if let Some(pmtud) = active_path.pmtud.as_mut() {\n                pmtud.revalidate_pmtu();\n            }\n        }\n    }\n\n    /// Returns true if the connection handshake is complete.\n    #[inline]\n    pub fn is_established(&self) -> bool {\n        self.handshake_completed\n    }\n\n    /// Returns true if the connection is resumed.\n    #[inline]\n    pub fn is_resumed(&self) -> bool {\n        self.handshake.is_resumed()\n    }\n\n    /// Returns true if the connection has a pending handshake that has\n    /// progressed enough to send or receive early data.\n    #[inline]\n    pub fn is_in_early_data(&self) -> bool {\n        self.handshake.is_in_early_data()\n    }\n\n    /// Returns the early data reason for the connection.\n    ///\n    /// This status can be useful for logging and debugging. See [BoringSSL]\n    /// documentation for a definition of the reasons.\n    ///\n    /// [BoringSSL]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#ssl_early_data_reason_t\n    #[inline]\n    pub fn early_data_reason(&self) -> u32 {\n        self.handshake.early_data_reason()\n    }\n\n    /// Returns whether there is stream or DATAGRAM data available to read.\n    #[inline]\n    pub fn is_readable(&self) -> bool {\n        self.streams.has_readable() || self.dgram_recv_front_len().is_some()\n    }\n\n    /// Returns whether the network path with local address `from` and remote\n    /// address `peer` has been validated.\n    ///\n    /// If the 4-tuple does not exist over the connection, returns an\n    /// [`InvalidState`].\n    ///\n    /// [`InvalidState`]: enum.Error.html#variant.InvalidState\n    pub fn is_path_validated(\n        &self, from: SocketAddr, to: SocketAddr,\n    ) -> Result<bool> {\n        let pid = self\n            .paths\n            .path_id_from_addrs(&(from, to))\n            .ok_or(Error::InvalidState)?;\n\n        Ok(self.paths.get(pid)?.validated())\n    }\n\n    /// Returns true if the connection is draining.\n    ///\n    /// If this returns `true`, the connection object cannot yet be dropped, but\n    /// no new application data can be sent or received. An application should\n    /// continue calling the [`recv()`], [`timeout()`], and [`on_timeout()`]\n    /// methods as normal, until the [`is_closed()`] method returns `true`.\n    ///\n    /// In contrast, once `is_draining()` returns `true`, calling [`send()`]\n    /// is not required because no new outgoing packets will be generated.\n    ///\n    /// [`recv()`]: struct.Connection.html#method.recv\n    /// [`send()`]: struct.Connection.html#method.send\n    /// [`timeout()`]: struct.Connection.html#method.timeout\n    /// [`on_timeout()`]: struct.Connection.html#method.on_timeout\n    /// [`is_closed()`]: struct.Connection.html#method.is_closed\n    #[inline]\n    pub fn is_draining(&self) -> bool {\n        self.draining_timer.is_some()\n    }\n\n    /// Returns true if the connection is closed.\n    ///\n    /// If this returns true, the connection object can be dropped.\n    #[inline]\n    pub fn is_closed(&self) -> bool {\n        self.closed\n    }\n\n    /// Returns true if the connection was closed due to the idle timeout.\n    #[inline]\n    pub fn is_timed_out(&self) -> bool {\n        self.timed_out\n    }\n\n    /// Returns the error received from the peer, if any.\n    ///\n    /// Note that a `Some` return value does not necessarily imply\n    /// [`is_closed()`] or any other connection state.\n    ///\n    /// [`is_closed()`]: struct.Connection.html#method.is_closed\n    #[inline]\n    pub fn peer_error(&self) -> Option<&ConnectionError> {\n        self.peer_error.as_ref()\n    }\n\n    /// Returns the error [`close()`] was called with, or internally\n    /// created quiche errors, if any.\n    ///\n    /// Note that a `Some` return value does not necessarily imply\n    /// [`is_closed()`] or any other connection state.\n    /// `Some` also does not guarantee that the error has been sent to\n    /// or received by the peer.\n    ///\n    /// [`close()`]: struct.Connection.html#method.close\n    /// [`is_closed()`]: struct.Connection.html#method.is_closed\n    #[inline]\n    pub fn local_error(&self) -> Option<&ConnectionError> {\n        self.local_error.as_ref()\n    }\n\n    /// Collects and returns statistics about the connection.\n    #[inline]\n    pub fn stats(&self) -> Stats {\n        Stats {\n            recv: self.recv_count,\n            sent: self.sent_count,\n            lost: self.lost_count,\n            spurious_lost: self.spurious_lost_count,\n            retrans: self.retrans_count,\n            sent_bytes: self.sent_bytes,\n            recv_bytes: self.recv_bytes,\n            acked_bytes: self.acked_bytes,\n            lost_bytes: self.lost_bytes,\n            stream_retrans_bytes: self.stream_retrans_bytes,\n            dgram_recv: self.dgram_recv_count,\n            dgram_sent: self.dgram_sent_count,\n            paths_count: self.paths.len(),\n            reset_stream_count_local: self.reset_stream_local_count,\n            stopped_stream_count_local: self.stopped_stream_local_count,\n            reset_stream_count_remote: self.reset_stream_remote_count,\n            stopped_stream_count_remote: self.stopped_stream_remote_count,\n            data_blocked_sent_count: self.data_blocked_sent_count,\n            stream_data_blocked_sent_count: self.stream_data_blocked_sent_count,\n            data_blocked_recv_count: self.data_blocked_recv_count,\n            stream_data_blocked_recv_count: self.stream_data_blocked_recv_count,\n            streams_blocked_bidi_recv_count: self.streams_blocked_bidi_recv_count,\n            streams_blocked_uni_recv_count: self.streams_blocked_uni_recv_count,\n            path_challenge_rx_count: self.path_challenge_rx_count,\n            bytes_in_flight_duration: self.bytes_in_flight_duration(),\n            tx_buffered_state: self.tx_buffered_state,\n        }\n    }\n\n    /// Returns the sum of the durations when each path in the\n    /// connection was actively sending bytes or waiting for acks.\n    /// Note that this could result in a duration that is longer than\n    /// the actual connection duration in cases where multiple paths\n    /// are active for extended periods of time.  In practice only 1\n    /// path is typically active at a time.\n    /// TODO revisit computation if in the future multiple paths are\n    /// often active at the same time.\n    fn bytes_in_flight_duration(&self) -> Duration {\n        self.paths.iter().fold(Duration::ZERO, |acc, (_, path)| {\n            acc + path.bytes_in_flight_duration()\n        })\n    }\n\n    /// Returns reference to peer's transport parameters. Returns `None` if we\n    /// have not yet processed the peer's transport parameters.\n    pub fn peer_transport_params(&self) -> Option<&TransportParams> {\n        if !self.parsed_peer_transport_params {\n            return None;\n        }\n\n        Some(&self.peer_transport_params)\n    }\n\n    /// Collects and returns statistics about each known path for the\n    /// connection.\n    pub fn path_stats(&self) -> impl Iterator<Item = PathStats> + '_ {\n        self.paths.iter().map(|(_, p)| p.stats())\n    }\n\n    /// Returns whether or not this is a server-side connection.\n    pub fn is_server(&self) -> bool {\n        self.is_server\n    }\n\n    fn encode_transport_params(&mut self) -> Result<()> {\n        self.handshake.set_quic_transport_params(\n            &self.local_transport_params,\n            self.is_server,\n        )\n    }\n\n    fn parse_peer_transport_params(\n        &mut self, peer_params: TransportParams,\n    ) -> Result<()> {\n        // Validate initial_source_connection_id.\n        match &peer_params.initial_source_connection_id {\n            Some(v) if v != &self.destination_id() =>\n                return Err(Error::InvalidTransportParam),\n\n            Some(_) => (),\n\n            // initial_source_connection_id must be sent by\n            // both endpoints.\n            None => return Err(Error::InvalidTransportParam),\n        }\n\n        // Validate original_destination_connection_id.\n        if let Some(odcid) = &self.odcid {\n            match &peer_params.original_destination_connection_id {\n                Some(v) if v != odcid =>\n                    return Err(Error::InvalidTransportParam),\n\n                Some(_) => (),\n\n                // original_destination_connection_id must be\n                // sent by the server.\n                None if !self.is_server =>\n                    return Err(Error::InvalidTransportParam),\n\n                None => (),\n            }\n        }\n\n        // Validate retry_source_connection_id.\n        if let Some(rscid) = &self.rscid {\n            match &peer_params.retry_source_connection_id {\n                Some(v) if v != rscid =>\n                    return Err(Error::InvalidTransportParam),\n\n                Some(_) => (),\n\n                // retry_source_connection_id must be sent by\n                // the server.\n                None => return Err(Error::InvalidTransportParam),\n            }\n        }\n\n        self.process_peer_transport_params(peer_params)?;\n\n        self.parsed_peer_transport_params = true;\n\n        Ok(())\n    }\n\n    fn process_peer_transport_params(\n        &mut self, peer_params: TransportParams,\n    ) -> Result<()> {\n        self.max_tx_data = peer_params.initial_max_data;\n\n        // Update send capacity.\n        self.update_tx_cap();\n\n        self.streams\n            .update_peer_max_streams_bidi(peer_params.initial_max_streams_bidi);\n        self.streams\n            .update_peer_max_streams_uni(peer_params.initial_max_streams_uni);\n\n        let max_ack_delay = Duration::from_millis(peer_params.max_ack_delay);\n\n        self.recovery_config.max_ack_delay = max_ack_delay;\n\n        let active_path = self.paths.get_active_mut()?;\n\n        active_path.recovery.update_max_ack_delay(max_ack_delay);\n\n        if active_path\n            .pmtud\n            .as_ref()\n            .map(|pmtud| pmtud.should_probe())\n            .unwrap_or(false)\n        {\n            active_path.recovery.pmtud_update_max_datagram_size(\n                active_path\n                    .pmtud\n                    .as_mut()\n                    .expect(\"PMTUD existence verified above\")\n                    .get_probe_size()\n                    .min(peer_params.max_udp_payload_size as usize),\n            );\n        } else {\n            active_path.recovery.update_max_datagram_size(\n                peer_params.max_udp_payload_size as usize,\n            );\n        }\n\n        // Record the max_active_conn_id parameter advertised by the peer.\n        self.ids\n            .set_source_conn_id_limit(peer_params.active_conn_id_limit);\n\n        self.peer_transport_params = peer_params;\n\n        Ok(())\n    }\n\n    /// Continues the handshake.\n    ///\n    /// If the connection is already established, it does nothing.\n    fn do_handshake(&mut self, now: Instant) -> Result<()> {\n        let mut ex_data = tls::ExData {\n            application_protos: &self.application_protos,\n\n            crypto_ctx: &mut self.crypto_ctx,\n\n            session: &mut self.session,\n\n            local_error: &mut self.local_error,\n\n            keylog: self.keylog.as_mut(),\n\n            trace_id: &self.trace_id,\n\n            local_transport_params: self.local_transport_params.clone(),\n\n            recovery_config: self.recovery_config,\n\n            tx_cap_factor: self.tx_cap_factor,\n\n            pmtud: None,\n\n            is_server: self.is_server,\n        };\n\n        if self.handshake_completed {\n            return self.handshake.process_post_handshake(&mut ex_data);\n        }\n\n        match self.handshake.do_handshake(&mut ex_data) {\n            Ok(_) => (),\n\n            Err(Error::Done) => {\n                // Apply in-handshake configuration from callbacks if the path's\n                // Recovery module can still be reinitilized.\n                if self\n                    .paths\n                    .get_active()\n                    .map(|p| p.can_reinit_recovery())\n                    .unwrap_or(false)\n                {\n                    if ex_data.recovery_config != self.recovery_config {\n                        if let Ok(path) = self.paths.get_active_mut() {\n                            self.recovery_config = ex_data.recovery_config;\n                            path.reinit_recovery(&self.recovery_config);\n                        }\n                    }\n\n                    if ex_data.tx_cap_factor != self.tx_cap_factor {\n                        self.tx_cap_factor = ex_data.tx_cap_factor;\n                    }\n\n                    if let Some((discover, max_probes)) = ex_data.pmtud {\n                        self.paths.set_discover_pmtu_on_existing_paths(\n                            discover,\n                            self.recovery_config.max_send_udp_payload_size,\n                            max_probes,\n                        );\n                    }\n\n                    if ex_data.local_transport_params !=\n                        self.local_transport_params\n                    {\n                        self.streams.set_max_streams_bidi(\n                            ex_data\n                                .local_transport_params\n                                .initial_max_streams_bidi,\n                        );\n\n                        self.local_transport_params =\n                            ex_data.local_transport_params;\n                    }\n                }\n\n                // Try to parse transport parameters as soon as the first flight\n                // of handshake data is processed.\n                //\n                // This is potentially dangerous as the handshake hasn't been\n                // completed yet, though it's required to be able to send data\n                // in 0.5 RTT.\n                let raw_params = self.handshake.quic_transport_params();\n\n                if !self.parsed_peer_transport_params && !raw_params.is_empty() {\n                    let peer_params = TransportParams::decode(\n                        raw_params,\n                        self.is_server,\n                        self.peer_transport_params_track_unknown,\n                    )?;\n\n                    self.parse_peer_transport_params(peer_params)?;\n                }\n\n                return Ok(());\n            },\n\n            Err(e) => return Err(e),\n        };\n\n        self.handshake_completed = self.handshake.is_completed();\n\n        self.alpn = self.handshake.alpn_protocol().to_vec();\n\n        let raw_params = self.handshake.quic_transport_params();\n\n        if !self.parsed_peer_transport_params && !raw_params.is_empty() {\n            let peer_params = TransportParams::decode(\n                raw_params,\n                self.is_server,\n                self.peer_transport_params_track_unknown,\n            )?;\n\n            self.parse_peer_transport_params(peer_params)?;\n        }\n\n        if self.handshake_completed {\n            // The handshake is considered confirmed at the server when the\n            // handshake completes, at which point we can also drop the\n            // handshake epoch.\n            if self.is_server {\n                self.handshake_confirmed = true;\n\n                self.drop_epoch_state(packet::Epoch::Handshake, now);\n            }\n\n            // Once the handshake is completed there's no point in processing\n            // 0-RTT packets anymore, so clear the buffer now.\n            self.undecryptable_pkts.clear();\n\n            trace!(\"{} connection established: proto={:?} cipher={:?} curve={:?} sigalg={:?} resumed={} {:?}\",\n                   &self.trace_id,\n                   std::str::from_utf8(self.application_proto()),\n                   self.handshake.cipher(),\n                   self.handshake.curve(),\n                   self.handshake.sigalg(),\n                   self.handshake.is_resumed(),\n                   self.peer_transport_params);\n        }\n\n        Ok(())\n    }\n\n    /// Selects the packet type for the next outgoing packet.\n    fn write_pkt_type(&self, send_pid: usize) -> Result<Type> {\n        // On error send packet in the latest epoch available, but only send\n        // 1-RTT ones when the handshake is completed.\n        if self\n            .local_error\n            .as_ref()\n            .is_some_and(|conn_err| !conn_err.is_app)\n        {\n            let epoch = match self.handshake.write_level() {\n                crypto::Level::Initial => packet::Epoch::Initial,\n                crypto::Level::ZeroRTT => unreachable!(),\n                crypto::Level::Handshake => packet::Epoch::Handshake,\n                crypto::Level::OneRTT => packet::Epoch::Application,\n            };\n\n            if !self.handshake_confirmed {\n                match epoch {\n                    // Downgrade the epoch to Handshake as the handshake is not\n                    // completed yet.\n                    packet::Epoch::Application => return Ok(Type::Handshake),\n\n                    // Downgrade the epoch to Initial as the remote peer might\n                    // not be able to decrypt handshake packets yet.\n                    packet::Epoch::Handshake\n                        if self.crypto_ctx[packet::Epoch::Initial].has_keys() =>\n                        return Ok(Type::Initial),\n\n                    _ => (),\n                };\n            }\n\n            return Ok(Type::from_epoch(epoch));\n        }\n\n        for &epoch in packet::Epoch::epochs(\n            packet::Epoch::Initial..=packet::Epoch::Application,\n        ) {\n            let crypto_ctx = &self.crypto_ctx[epoch];\n            let pkt_space = &self.pkt_num_spaces[epoch];\n\n            // Only send packets in a space when we have the send keys for it.\n            if crypto_ctx.crypto_seal.is_none() {\n                continue;\n            }\n\n            // We are ready to send data for this packet number space.\n            if crypto_ctx.data_available() || pkt_space.ready() {\n                return Ok(Type::from_epoch(epoch));\n            }\n\n            // There are lost frames in this packet number space.\n            for (_, p) in self.paths.iter() {\n                if p.recovery.has_lost_frames(epoch) {\n                    return Ok(Type::from_epoch(epoch));\n                }\n\n                // We need to send PTO probe packets.\n                if p.recovery.loss_probes(epoch) > 0 {\n                    return Ok(Type::from_epoch(epoch));\n                }\n            }\n        }\n\n        // If there are flushable, almost full or blocked streams, use the\n        // Application epoch.\n        let send_path = self.paths.get(send_pid)?;\n        if (self.is_established() || self.is_in_early_data()) &&\n            (self.should_send_handshake_done() ||\n                self.flow_control.should_update_max_data() ||\n                self.should_send_max_data ||\n                self.blocked_limit.is_some() ||\n                self.streams_blocked_bidi_state\n                    .has_pending_stream_blocked_frame() ||\n                self.streams_blocked_uni_state\n                    .has_pending_stream_blocked_frame() ||\n                self.dgram_send_queue.has_pending() ||\n                self.local_error\n                    .as_ref()\n                    .is_some_and(|conn_err| conn_err.is_app) ||\n                self.should_send_max_streams_bidi ||\n                self.streams.should_update_max_streams_bidi() ||\n                self.should_send_max_streams_uni ||\n                self.streams.should_update_max_streams_uni() ||\n                self.streams.has_flushable() ||\n                self.streams.has_almost_full() ||\n                self.streams.has_blocked() ||\n                self.streams.has_reset() ||\n                self.streams.has_stopped() ||\n                self.ids.has_new_scids() ||\n                self.ids.has_retire_dcids() ||\n                send_path\n                    .pmtud\n                    .as_ref()\n                    .is_some_and(|pmtud| pmtud.should_probe()) ||\n                send_path.needs_ack_eliciting ||\n                send_path.probing_required())\n        {\n            // Only clients can send 0-RTT packets.\n            if !self.is_server && self.is_in_early_data() {\n                return Ok(Type::ZeroRTT);\n            }\n\n            return Ok(Type::Short);\n        }\n\n        Err(Error::Done)\n    }\n\n    /// Returns the mutable stream with the given ID if it exists, or creates\n    /// a new one otherwise.\n    fn get_or_create_stream(\n        &mut self, id: u64, local: bool,\n    ) -> Result<&mut stream::Stream<F>> {\n        self.streams.get_or_create(\n            id,\n            &self.local_transport_params,\n            &self.peer_transport_params,\n            local,\n            self.is_server,\n        )\n    }\n\n    /// Processes an incoming frame.\n    fn process_frame(\n        &mut self, frame: frame::Frame, hdr: &Header, recv_path_id: usize,\n        epoch: packet::Epoch, now: Instant,\n    ) -> Result<()> {\n        trace!(\"{} rx frm {:?}\", self.trace_id, frame);\n\n        match frame {\n            frame::Frame::Padding { .. } => (),\n\n            frame::Frame::Ping { .. } => (),\n\n            frame::Frame::ACK {\n                ranges, ack_delay, ..\n            } => {\n                let ack_delay = ack_delay\n                    .checked_mul(2_u64.pow(\n                        self.peer_transport_params.ack_delay_exponent as u32,\n                    ))\n                    .ok_or(Error::InvalidFrame)?;\n\n                if epoch == packet::Epoch::Handshake ||\n                    (epoch == packet::Epoch::Application &&\n                        self.is_established())\n                {\n                    self.peer_verified_initial_address = true;\n                }\n\n                let handshake_status = self.handshake_status();\n\n                let is_app_limited = self.delivery_rate_check_if_app_limited();\n\n                let largest_acked = ranges.last().expect(\n                    \"ACK frames should always have at least one ack range\",\n                );\n\n                for (_, p) in self.paths.iter_mut() {\n                    if self.pkt_num_spaces[epoch]\n                        .largest_tx_pkt_num\n                        .is_some_and(|largest_sent| largest_sent < largest_acked)\n                    {\n                        // https://www.rfc-editor.org/rfc/rfc9000#section-13.1\n                        // An endpoint SHOULD treat receipt of an acknowledgment\n                        // for a packet it did not send as\n                        // a connection error of type PROTOCOL_VIOLATION\n                        return Err(Error::InvalidAckRange);\n                    }\n\n                    if is_app_limited {\n                        p.recovery.delivery_rate_update_app_limited(true);\n                    }\n\n                    let OnAckReceivedOutcome {\n                        lost_packets,\n                        lost_bytes,\n                        acked_bytes,\n                        spurious_losses,\n                    } = p.recovery.on_ack_received(\n                        &ranges,\n                        ack_delay,\n                        epoch,\n                        handshake_status,\n                        now,\n                        self.pkt_num_manager.skip_pn(),\n                        &self.trace_id,\n                    )?;\n\n                    let skip_pn = self.pkt_num_manager.skip_pn();\n                    let largest_acked =\n                        p.recovery.get_largest_acked_on_epoch(epoch);\n\n                    // Consider the skip_pn validated if the peer has sent an ack\n                    // for a larger pkt number.\n                    if let Some((largest_acked, skip_pn)) =\n                        largest_acked.zip(skip_pn)\n                    {\n                        if largest_acked > skip_pn {\n                            self.pkt_num_manager.set_skip_pn(None);\n                        }\n                    }\n\n                    self.lost_count += lost_packets;\n                    self.lost_bytes += lost_bytes as u64;\n                    self.acked_bytes += acked_bytes as u64;\n                    self.spurious_lost_count += spurious_losses;\n                }\n            },\n\n            frame::Frame::ResetStream {\n                stream_id,\n                error_code,\n                final_size,\n            } => {\n                // Peer can't send on our unidirectional streams.\n                if !stream::is_bidi(stream_id) &&\n                    stream::is_local(stream_id, self.is_server)\n                {\n                    return Err(Error::InvalidStreamState(stream_id));\n                }\n\n                let max_rx_data_left = self.max_rx_data() - self.rx_data;\n\n                // Get existing stream or create a new one, but if the stream\n                // has already been closed and collected, ignore the frame.\n                //\n                // This can happen if e.g. an ACK frame is lost, and the peer\n                // retransmits another frame before it realizes that the stream\n                // is gone.\n                //\n                // Note that it makes it impossible to check if the frame is\n                // illegal, since we have no state, but since we ignore the\n                // frame, it should be fine.\n                let stream = match self.get_or_create_stream(stream_id, false) {\n                    Ok(v) => v,\n\n                    Err(Error::Done) => return Ok(()),\n\n                    Err(e) => return Err(e),\n                };\n\n                let was_readable = stream.is_readable();\n                let priority_key = Arc::clone(&stream.priority_key);\n\n                let stream::RecvBufResetReturn {\n                    max_data_delta,\n                    consumed_flowcontrol,\n                } = stream.recv.reset(error_code, final_size)?;\n\n                if max_data_delta > max_rx_data_left {\n                    return Err(Error::FlowControl);\n                }\n\n                if !was_readable && stream.is_readable() {\n                    self.streams.insert_readable(&priority_key);\n                }\n\n                self.rx_data += max_data_delta;\n                // We dropped the receive buffer, return connection level\n                // flow-control\n                self.flow_control.add_consumed(consumed_flowcontrol);\n\n                self.reset_stream_remote_count =\n                    self.reset_stream_remote_count.saturating_add(1);\n            },\n\n            frame::Frame::StopSending {\n                stream_id,\n                error_code,\n            } => {\n                // STOP_SENDING on a receive-only stream is a fatal error.\n                if !stream::is_local(stream_id, self.is_server) &&\n                    !stream::is_bidi(stream_id)\n                {\n                    return Err(Error::InvalidStreamState(stream_id));\n                }\n\n                // Get existing stream or create a new one, but if the stream\n                // has already been closed and collected, ignore the frame.\n                //\n                // This can happen if e.g. an ACK frame is lost, and the peer\n                // retransmits another frame before it realizes that the stream\n                // is gone.\n                //\n                // Note that it makes it impossible to check if the frame is\n                // illegal, since we have no state, but since we ignore the\n                // frame, it should be fine.\n                let stream = match self.get_or_create_stream(stream_id, false) {\n                    Ok(v) => v,\n\n                    Err(Error::Done) => return Ok(()),\n\n                    Err(e) => return Err(e),\n                };\n\n                let was_writable = stream.is_writable();\n\n                let priority_key = Arc::clone(&stream.priority_key);\n\n                // Try stopping the stream.\n                if let Ok((final_size, unsent)) = stream.send.stop(error_code) {\n                    // Claw back some flow control allowance from data that was\n                    // buffered but not actually sent before the stream was\n                    // reset.\n                    //\n                    // Note that `tx_cap` will be updated later on, so no need\n                    // to touch it here.\n                    self.tx_data = self.tx_data.saturating_sub(unsent);\n\n                    self.tx_buffered =\n                        self.tx_buffered.saturating_sub(unsent as usize);\n\n                    // These drops in qlog are a bit weird, but the only way to\n                    // ensure that all bytes that are moved from App to Transport\n                    // in stream_do_send are eventually moved from Transport to\n                    // Dropped.  Ideally we would add a Transport to Network\n                    // transition also as a way to indicate when bytes were\n                    // transmitted vs dropped without ever being sent.\n                    qlog_with_type!(QLOG_DATA_MV, self.qlog, q, {\n                        let ev_data = EventData::QuicStreamDataMoved(\n                            qlog::events::quic::StreamDataMoved {\n                                stream_id: Some(stream_id),\n                                offset: Some(final_size),\n                                raw: Some(RawInfo {\n                                    length: Some(unsent),\n                                    ..Default::default()\n                                }),\n                                from: Some(DataRecipient::Transport),\n                                to: Some(DataRecipient::Dropped),\n                                ..Default::default()\n                            },\n                        );\n\n                        q.add_event_data_with_instant(ev_data, now).ok();\n                    });\n\n                    self.streams.insert_reset(stream_id, error_code, final_size);\n\n                    if !was_writable {\n                        self.streams.insert_writable(&priority_key);\n                    }\n\n                    self.stopped_stream_remote_count =\n                        self.stopped_stream_remote_count.saturating_add(1);\n                    self.reset_stream_local_count =\n                        self.reset_stream_local_count.saturating_add(1);\n                }\n            },\n\n            frame::Frame::Crypto { data } => {\n                if data.max_off() >= MAX_CRYPTO_STREAM_OFFSET {\n                    return Err(Error::CryptoBufferExceeded);\n                }\n\n                // Push the data to the stream so it can be re-ordered.\n                self.crypto_ctx[epoch].crypto_stream.recv.write(data)?;\n\n                // Feed crypto data to the TLS state, if there's data\n                // available at the expected offset.\n                let mut crypto_buf = [0; 512];\n\n                let level = crypto::Level::from_epoch(epoch);\n\n                let stream = &mut self.crypto_ctx[epoch].crypto_stream;\n\n                while let Ok((read, _)) = stream.recv.emit(&mut crypto_buf) {\n                    let recv_buf = &crypto_buf[..read];\n                    self.handshake.provide_data(level, recv_buf)?;\n                }\n\n                self.do_handshake(now)?;\n            },\n\n            frame::Frame::CryptoHeader { .. } => unreachable!(),\n\n            // TODO: implement stateless retry\n            frame::Frame::NewToken { .. } =>\n                if self.is_server {\n                    return Err(Error::InvalidPacket);\n                },\n\n            frame::Frame::Stream { stream_id, data } => {\n                // Peer can't send on our unidirectional streams.\n                if !stream::is_bidi(stream_id) &&\n                    stream::is_local(stream_id, self.is_server)\n                {\n                    return Err(Error::InvalidStreamState(stream_id));\n                }\n\n                let max_rx_data_left = self.max_rx_data() - self.rx_data;\n\n                // Get existing stream or create a new one, but if the stream\n                // has already been closed and collected, ignore the frame.\n                //\n                // This can happen if e.g. an ACK frame is lost, and the peer\n                // retransmits another frame before it realizes that the stream\n                // is gone.\n                //\n                // Note that it makes it impossible to check if the frame is\n                // illegal, since we have no state, but since we ignore the\n                // frame, it should be fine.\n                let stream = match self.get_or_create_stream(stream_id, false) {\n                    Ok(v) => v,\n\n                    Err(Error::Done) => return Ok(()),\n\n                    Err(e) => return Err(e),\n                };\n\n                // Check for the connection-level flow control limit.\n                let max_off_delta =\n                    data.max_off().saturating_sub(stream.recv.max_off());\n\n                if max_off_delta > max_rx_data_left {\n                    return Err(Error::FlowControl);\n                }\n\n                let was_readable = stream.is_readable();\n                let priority_key = Arc::clone(&stream.priority_key);\n\n                let was_draining = stream.recv.is_draining();\n\n                stream.recv.write(data)?;\n\n                if !was_readable && stream.is_readable() {\n                    self.streams.insert_readable(&priority_key);\n                }\n\n                self.rx_data += max_off_delta;\n\n                if was_draining {\n                    // When a stream is in draining state it will not queue\n                    // incoming data for the application to read, so consider\n                    // the received data as consumed, which might trigger a flow\n                    // control update.\n                    self.flow_control.add_consumed(max_off_delta);\n                }\n            },\n\n            frame::Frame::StreamHeader { .. } => unreachable!(),\n\n            frame::Frame::MaxData { max } => {\n                self.max_tx_data = cmp::max(self.max_tx_data, max);\n            },\n\n            frame::Frame::MaxStreamData { stream_id, max } => {\n                // Peer can't receive on its own unidirectional streams.\n                if !stream::is_bidi(stream_id) &&\n                    !stream::is_local(stream_id, self.is_server)\n                {\n                    return Err(Error::InvalidStreamState(stream_id));\n                }\n\n                // Get existing stream or create a new one, but if the stream\n                // has already been closed and collected, ignore the frame.\n                //\n                // This can happen if e.g. an ACK frame is lost, and the peer\n                // retransmits another frame before it realizes that the stream\n                // is gone.\n                //\n                // Note that it makes it impossible to check if the frame is\n                // illegal, since we have no state, but since we ignore the\n                // frame, it should be fine.\n                let stream = match self.get_or_create_stream(stream_id, false) {\n                    Ok(v) => v,\n\n                    Err(Error::Done) => return Ok(()),\n\n                    Err(e) => return Err(e),\n                };\n\n                let was_flushable = stream.is_flushable();\n\n                stream.send.update_max_data(max);\n\n                let writable = stream.is_writable();\n\n                let priority_key = Arc::clone(&stream.priority_key);\n\n                // If the stream is now flushable push it to the flushable queue,\n                // but only if it wasn't already queued.\n                if stream.is_flushable() && !was_flushable {\n                    let priority_key = Arc::clone(&stream.priority_key);\n                    self.streams.insert_flushable(&priority_key);\n                }\n\n                if writable {\n                    self.streams.insert_writable(&priority_key);\n                }\n            },\n\n            frame::Frame::MaxStreamsBidi { max } => {\n                if max > MAX_STREAM_ID {\n                    return Err(Error::InvalidFrame);\n                }\n\n                self.streams.update_peer_max_streams_bidi(max);\n            },\n\n            frame::Frame::MaxStreamsUni { max } => {\n                if max > MAX_STREAM_ID {\n                    return Err(Error::InvalidFrame);\n                }\n\n                self.streams.update_peer_max_streams_uni(max);\n            },\n\n            frame::Frame::DataBlocked { .. } => {\n                self.data_blocked_recv_count =\n                    self.data_blocked_recv_count.saturating_add(1);\n            },\n\n            frame::Frame::StreamDataBlocked { .. } => {\n                self.stream_data_blocked_recv_count =\n                    self.stream_data_blocked_recv_count.saturating_add(1);\n            },\n\n            frame::Frame::StreamsBlockedBidi { limit } => {\n                if limit > MAX_STREAM_ID {\n                    return Err(Error::InvalidFrame);\n                }\n\n                self.streams_blocked_bidi_recv_count =\n                    self.streams_blocked_bidi_recv_count.saturating_add(1);\n            },\n\n            frame::Frame::StreamsBlockedUni { limit } => {\n                if limit > MAX_STREAM_ID {\n                    return Err(Error::InvalidFrame);\n                }\n\n                self.streams_blocked_uni_recv_count =\n                    self.streams_blocked_uni_recv_count.saturating_add(1);\n            },\n\n            frame::Frame::NewConnectionId {\n                seq_num,\n                retire_prior_to,\n                conn_id,\n                reset_token,\n            } => {\n                if self.ids.zero_length_dcid() {\n                    return Err(Error::InvalidState);\n                }\n\n                let mut retired_path_ids = SmallVec::new();\n\n                // Retire pending path IDs before propagating the error code to\n                // make sure retired connection IDs are not in use anymore.\n                let new_dcid_res = self.ids.new_dcid(\n                    conn_id.into(),\n                    seq_num,\n                    u128::from_be_bytes(reset_token),\n                    retire_prior_to,\n                    &mut retired_path_ids,\n                );\n\n                for (dcid_seq, pid) in retired_path_ids {\n                    let path = self.paths.get_mut(pid)?;\n\n                    // Maybe the path already switched to another DCID.\n                    if path.active_dcid_seq != Some(dcid_seq) {\n                        continue;\n                    }\n\n                    if let Some(new_dcid_seq) =\n                        self.ids.lowest_available_dcid_seq()\n                    {\n                        path.active_dcid_seq = Some(new_dcid_seq);\n\n                        self.ids.link_dcid_to_path_id(new_dcid_seq, pid)?;\n\n                        trace!(\n                            \"{} path ID {} changed DCID: old seq num {} new seq num {}\",\n                            self.trace_id, pid, dcid_seq, new_dcid_seq,\n                        );\n                    } else {\n                        // We cannot use this path anymore for now.\n                        path.active_dcid_seq = None;\n\n                        trace!(\n                            \"{} path ID {} cannot be used; DCID seq num {} has been retired\",\n                            self.trace_id, pid, dcid_seq,\n                        );\n                    }\n                }\n\n                // Propagate error (if any) now...\n                new_dcid_res?;\n            },\n\n            frame::Frame::RetireConnectionId { seq_num } => {\n                if self.ids.zero_length_scid() {\n                    return Err(Error::InvalidState);\n                }\n\n                if let Some(pid) = self.ids.retire_scid(seq_num, &hdr.dcid)? {\n                    let path = self.paths.get_mut(pid)?;\n\n                    // Maybe we already linked a new SCID to that path.\n                    if path.active_scid_seq == Some(seq_num) {\n                        // XXX: We do not remove unused paths now, we instead\n                        // wait until we need to maintain more paths than the\n                        // host is willing to.\n                        path.active_scid_seq = None;\n                    }\n                }\n            },\n\n            frame::Frame::PathChallenge { data } => {\n                self.path_challenge_rx_count += 1;\n\n                self.paths\n                    .get_mut(recv_path_id)?\n                    .on_challenge_received(data);\n            },\n\n            frame::Frame::PathResponse { data } => {\n                self.paths.on_response_received(data)?;\n            },\n\n            frame::Frame::ConnectionClose {\n                error_code, reason, ..\n            } => {\n                self.peer_error = Some(ConnectionError {\n                    is_app: false,\n                    error_code,\n                    reason,\n                });\n\n                let path = self.paths.get_active()?;\n                self.draining_timer = Some(now + (path.recovery.pto() * 3));\n            },\n\n            frame::Frame::ApplicationClose { error_code, reason } => {\n                self.peer_error = Some(ConnectionError {\n                    is_app: true,\n                    error_code,\n                    reason,\n                });\n\n                let path = self.paths.get_active()?;\n                self.draining_timer = Some(now + (path.recovery.pto() * 3));\n            },\n\n            frame::Frame::HandshakeDone => {\n                if self.is_server {\n                    return Err(Error::InvalidPacket);\n                }\n\n                self.peer_verified_initial_address = true;\n\n                self.handshake_confirmed = true;\n\n                // Once the handshake is confirmed, we can drop Handshake keys.\n                self.drop_epoch_state(packet::Epoch::Handshake, now);\n            },\n\n            frame::Frame::Datagram { data } => {\n                // Close the connection if DATAGRAMs are not enabled.\n                // quiche always advertises support for 64K sized DATAGRAM\n                // frames, as recommended by the standard, so we don't need a\n                // size check.\n                if !self.dgram_enabled() {\n                    return Err(Error::InvalidState);\n                }\n\n                // If recv queue is full, discard oldest\n                if self.dgram_recv_queue.is_full() {\n                    self.dgram_recv_queue.pop();\n                }\n\n                self.dgram_recv_queue.push(data)?;\n\n                self.dgram_recv_count = self.dgram_recv_count.saturating_add(1);\n\n                let path = self.paths.get_mut(recv_path_id)?;\n                path.dgram_recv_count = path.dgram_recv_count.saturating_add(1);\n            },\n\n            frame::Frame::DatagramHeader { .. } => unreachable!(),\n        }\n\n        Ok(())\n    }\n\n    /// Drops the keys and recovery state for the given epoch.\n    fn drop_epoch_state(&mut self, epoch: packet::Epoch, now: Instant) {\n        let crypto_ctx = &mut self.crypto_ctx[epoch];\n        if crypto_ctx.crypto_open.is_none() {\n            return;\n        }\n        crypto_ctx.clear();\n        self.pkt_num_spaces[epoch].clear();\n\n        let handshake_status = self.handshake_status();\n        for (_, p) in self.paths.iter_mut() {\n            p.recovery\n                .on_pkt_num_space_discarded(epoch, handshake_status, now);\n        }\n\n        trace!(\"{} dropped epoch {} state\", self.trace_id, epoch);\n    }\n\n    /// Returns the connection level flow control limit.\n    fn max_rx_data(&self) -> u64 {\n        self.flow_control.max_data()\n    }\n\n    /// Returns true if the HANDSHAKE_DONE frame needs to be sent.\n    fn should_send_handshake_done(&self) -> bool {\n        self.is_established() && !self.handshake_done_sent && self.is_server\n    }\n\n    /// Returns the idle timeout value.\n    ///\n    /// `None` is returned if both end-points disabled the idle timeout.\n    fn idle_timeout(&self) -> Option<Duration> {\n        // If the transport parameter is set to 0, then the respective endpoint\n        // decided to disable the idle timeout. If both are disabled we should\n        // not set any timeout.\n        if self.local_transport_params.max_idle_timeout == 0 &&\n            self.peer_transport_params.max_idle_timeout == 0\n        {\n            return None;\n        }\n\n        // If the local endpoint or the peer disabled the idle timeout, use the\n        // other peer's value, otherwise use the minimum of the two values.\n        let idle_timeout = if self.local_transport_params.max_idle_timeout == 0 {\n            self.peer_transport_params.max_idle_timeout\n        } else if self.peer_transport_params.max_idle_timeout == 0 {\n            self.local_transport_params.max_idle_timeout\n        } else {\n            cmp::min(\n                self.local_transport_params.max_idle_timeout,\n                self.peer_transport_params.max_idle_timeout,\n            )\n        };\n\n        let path_pto = match self.paths.get_active() {\n            Ok(p) => p.recovery.pto(),\n            Err(_) => Duration::ZERO,\n        };\n\n        let idle_timeout = Duration::from_millis(idle_timeout);\n        let idle_timeout = cmp::max(idle_timeout, 3 * path_pto);\n\n        Some(idle_timeout)\n    }\n\n    /// Returns the connection's handshake status for use in loss recovery.\n    fn handshake_status(&self) -> recovery::HandshakeStatus {\n        recovery::HandshakeStatus {\n            has_handshake_keys: self.crypto_ctx[packet::Epoch::Handshake]\n                .has_keys(),\n\n            peer_verified_address: self.peer_verified_initial_address,\n\n            completed: self.is_established(),\n        }\n    }\n\n    /// Updates send capacity.\n    fn update_tx_cap(&mut self) {\n        let cwin_available = match self.paths.get_active() {\n            Ok(p) => p.recovery.cwnd_available() as u64,\n            Err(_) => 0,\n        };\n\n        let cap =\n            cmp::min(cwin_available, self.max_tx_data - self.tx_data) as usize;\n        self.tx_cap = (cap as f64 * self.tx_cap_factor).ceil() as usize;\n    }\n\n    fn delivery_rate_check_if_app_limited(&self) -> bool {\n        // Enter the app-limited phase of delivery rate when these conditions\n        // are met:\n        //\n        // - The remaining capacity is higher than available bytes in cwnd (there\n        //   is more room to send).\n        // - New data since the last send() is smaller than available bytes in\n        //   cwnd (we queued less than what we can send).\n        // - There is room to send more data in cwnd.\n        //\n        // In application-limited phases the transmission rate is limited by the\n        // application rather than the congestion control algorithm.\n        //\n        // Note that this is equivalent to CheckIfApplicationLimited() from the\n        // delivery rate draft. This is also separate from `recovery.app_limited`\n        // and only applies to delivery rate calculation.\n        let cwin_available = self\n            .paths\n            .iter()\n            .filter(|&(_, p)| p.active())\n            .map(|(_, p)| p.recovery.cwnd_available())\n            .sum();\n\n        ((self.tx_buffered + self.dgram_send_queue_byte_size()) < cwin_available) &&\n            (self.tx_data.saturating_sub(self.last_tx_data)) <\n                cwin_available as u64 &&\n            cwin_available > 0\n    }\n\n    fn check_tx_buffered_invariant(&mut self) {\n        // tx_buffered should track bytes queued in the stream buffers\n        // and unacked retransmitable bytes in the network.\n        // If tx_buffered > 0 mark the tx_buffered_state if there are no\n        // flushable streams and there no inflight bytes.\n        //\n        // It is normal to have tx_buffered == 0 while there are inflight bytes\n        // since not QUIC frames are retransmittable; inflight tracks all bytes\n        // on the network which are subject to congestion control.\n        if self.tx_buffered > 0 &&\n            !self.streams.has_flushable() &&\n            !self\n                .paths\n                .iter()\n                .any(|(_, p)| p.recovery.bytes_in_flight() > 0)\n        {\n            self.tx_buffered_state = TxBufferTrackingState::Inconsistent;\n        }\n    }\n\n    fn set_initial_dcid(\n        &mut self, cid: ConnectionId<'static>, reset_token: Option<u128>,\n        path_id: usize,\n    ) -> Result<()> {\n        self.ids.set_initial_dcid(cid, reset_token, Some(path_id));\n        self.paths.get_mut(path_id)?.active_dcid_seq = Some(0);\n\n        Ok(())\n    }\n\n    /// Selects the path that the incoming packet belongs to, or creates a new\n    /// one if no existing path matches.\n    fn get_or_create_recv_path_id(\n        &mut self, recv_pid: Option<usize>, dcid: &ConnectionId, buf_len: usize,\n        info: &RecvInfo,\n    ) -> Result<usize> {\n        let ids = &mut self.ids;\n\n        let (in_scid_seq, mut in_scid_pid) =\n            ids.find_scid_seq(dcid).ok_or(Error::InvalidState)?;\n\n        if let Some(recv_pid) = recv_pid {\n            // If the path observes a change of SCID used, note it.\n            let recv_path = self.paths.get_mut(recv_pid)?;\n\n            let cid_entry =\n                recv_path.active_scid_seq.and_then(|v| ids.get_scid(v).ok());\n\n            if cid_entry.map(|e| &e.cid) != Some(dcid) {\n                let incoming_cid_entry = ids.get_scid(in_scid_seq)?;\n\n                let prev_recv_pid =\n                    incoming_cid_entry.path_id.unwrap_or(recv_pid);\n\n                if prev_recv_pid != recv_pid {\n                    trace!(\n                        \"{} peer reused CID {:?} from path {} on path {}\",\n                        self.trace_id,\n                        dcid,\n                        prev_recv_pid,\n                        recv_pid\n                    );\n\n                    // TODO: reset congestion control.\n                }\n\n                trace!(\n                    \"{} path ID {} now see SCID with seq num {}\",\n                    self.trace_id,\n                    recv_pid,\n                    in_scid_seq\n                );\n\n                recv_path.active_scid_seq = Some(in_scid_seq);\n                ids.link_scid_to_path_id(in_scid_seq, recv_pid)?;\n            }\n\n            return Ok(recv_pid);\n        }\n\n        // This is a new 4-tuple. See if the CID has not been assigned on\n        // another path.\n\n        // Ignore this step if are using zero-length SCID.\n        if ids.zero_length_scid() {\n            in_scid_pid = None;\n        }\n\n        if let Some(in_scid_pid) = in_scid_pid {\n            // This CID has been used by another path. If we have the\n            // room to do so, create a new `Path` structure holding this\n            // new 4-tuple. Otherwise, drop the packet.\n            let old_path = self.paths.get_mut(in_scid_pid)?;\n            let old_local_addr = old_path.local_addr();\n            let old_peer_addr = old_path.peer_addr();\n\n            trace!(\n                \"{} reused CID seq {} of ({},{}) (path {}) on ({},{})\",\n                self.trace_id,\n                in_scid_seq,\n                old_local_addr,\n                old_peer_addr,\n                in_scid_pid,\n                info.to,\n                info.from\n            );\n\n            // Notify the application.\n            self.paths.notify_event(PathEvent::ReusedSourceConnectionId(\n                in_scid_seq,\n                (old_local_addr, old_peer_addr),\n                (info.to, info.from),\n            ));\n        }\n\n        // This is a new path using an unassigned CID; create it!\n        let mut path = path::Path::new(\n            info.to,\n            info.from,\n            &self.recovery_config,\n            self.path_challenge_recv_max_queue_len,\n            false,\n            None,\n        );\n\n        path.max_send_bytes = buf_len * self.max_amplification_factor;\n        path.active_scid_seq = Some(in_scid_seq);\n\n        // Automatically probes the new path.\n        path.request_validation();\n\n        let pid = self.paths.insert_path(path, self.is_server)?;\n\n        // Do not record path reuse.\n        if in_scid_pid.is_none() {\n            ids.link_scid_to_path_id(in_scid_seq, pid)?;\n        }\n\n        Ok(pid)\n    }\n\n    /// Selects the path on which the next packet must be sent.\n    fn get_send_path_id(\n        &self, from: Option<SocketAddr>, to: Option<SocketAddr>,\n    ) -> Result<usize> {\n        // A probing packet must be sent, but only if the connection is fully\n        // established.\n        if self.is_established() {\n            let mut probing = self\n                .paths\n                .iter()\n                .filter(|(_, p)| from.is_none() || Some(p.local_addr()) == from)\n                .filter(|(_, p)| to.is_none() || Some(p.peer_addr()) == to)\n                .filter(|(_, p)| p.active_dcid_seq.is_some())\n                .filter(|(_, p)| p.probing_required())\n                .map(|(pid, _)| pid);\n\n            if let Some(pid) = probing.next() {\n                return Ok(pid);\n            }\n        }\n\n        if let Some((pid, p)) = self.paths.get_active_with_pid() {\n            if from.is_some() && Some(p.local_addr()) != from {\n                return Err(Error::Done);\n            }\n\n            if to.is_some() && Some(p.peer_addr()) != to {\n                return Err(Error::Done);\n            }\n\n            return Ok(pid);\n        };\n\n        Err(Error::InvalidState)\n    }\n\n    /// Sets the path with identifier 'path_id' to be active.\n    fn set_active_path(&mut self, path_id: usize, now: Instant) -> Result<()> {\n        if let Ok(old_active_path) = self.paths.get_active_mut() {\n            for &e in packet::Epoch::epochs(\n                packet::Epoch::Initial..=packet::Epoch::Application,\n            ) {\n                let (lost_packets, lost_bytes) = old_active_path\n                    .recovery\n                    .on_path_change(e, now, &self.trace_id);\n\n                self.lost_count += lost_packets;\n                self.lost_bytes += lost_bytes as u64;\n            }\n        }\n\n        self.paths.set_active_path(path_id)\n    }\n\n    /// Handles potential connection migration.\n    fn on_peer_migrated(\n        &mut self, new_pid: usize, disable_dcid_reuse: bool, now: Instant,\n    ) -> Result<()> {\n        let active_path_id = self.paths.get_active_path_id()?;\n\n        if active_path_id == new_pid {\n            return Ok(());\n        }\n\n        self.set_active_path(new_pid, now)?;\n\n        let no_spare_dcid =\n            self.paths.get_mut(new_pid)?.active_dcid_seq.is_none();\n\n        if no_spare_dcid && !disable_dcid_reuse {\n            self.paths.get_mut(new_pid)?.active_dcid_seq =\n                self.paths.get_mut(active_path_id)?.active_dcid_seq;\n        }\n\n        Ok(())\n    }\n\n    /// Creates a new client-side path.\n    fn create_path_on_client(\n        &mut self, local_addr: SocketAddr, peer_addr: SocketAddr,\n    ) -> Result<usize> {\n        if self.is_server {\n            return Err(Error::InvalidState);\n        }\n\n        // If we use zero-length SCID and go over our local active CID limit,\n        // the `insert_path()` call will raise an error.\n        if !self.ids.zero_length_scid() && self.ids.available_scids() == 0 {\n            return Err(Error::OutOfIdentifiers);\n        }\n\n        // Do we have a spare DCID? If we are using zero-length DCID, just use\n        // the default having sequence 0 (note that if we exceed our local CID\n        // limit, the `insert_path()` call will raise an error.\n        let dcid_seq = if self.ids.zero_length_dcid() {\n            0\n        } else {\n            self.ids\n                .lowest_available_dcid_seq()\n                .ok_or(Error::OutOfIdentifiers)?\n        };\n\n        let mut path = path::Path::new(\n            local_addr,\n            peer_addr,\n            &self.recovery_config,\n            self.path_challenge_recv_max_queue_len,\n            false,\n            None,\n        );\n        path.active_dcid_seq = Some(dcid_seq);\n\n        let pid = self\n            .paths\n            .insert_path(path, false)\n            .map_err(|_| Error::OutOfIdentifiers)?;\n        self.ids.link_dcid_to_path_id(dcid_seq, pid)?;\n\n        Ok(pid)\n    }\n\n    // Marks the connection as closed and does any related tidyup.\n    fn mark_closed(&mut self) {\n        #[cfg(feature = \"qlog\")]\n        {\n            let cc = match (self.is_established(), self.timed_out, &self.peer_error, &self.local_error) {\n                (false, _, _, _) => qlog::events::quic::ConnectionClosed {\n                    initiator: Some(TransportInitiator::Local),\n                    connection_error: None,\n                    application_error: None,\n                    error_code: None,\n                    internal_code: None,\n                    reason: Some(\"Failed to establish connection\".to_string()),\n                    trigger: Some(qlog::events::quic::ConnectionClosedTrigger::HandshakeTimeout)\n                },\n\n                (true, true, _, _) => qlog::events::quic::ConnectionClosed {\n                    initiator: Some(TransportInitiator::Local),\n                    connection_error: None,\n                    application_error: None,\n                    error_code: None,\n                    internal_code: None,\n                    reason: Some(\"Idle timeout\".to_string()),\n                    trigger: Some(qlog::events::quic::ConnectionClosedTrigger::IdleTimeout)\n                },\n\n                (true, false, Some(peer_error), None) => {\n                    let (connection_code, application_error, trigger) = if peer_error.is_app {\n                        (None, Some(qlog::events::ApplicationError::Unknown), None)\n                    } else {\n                        let trigger = if peer_error.error_code == WireErrorCode::NoError as u64 {\n                            Some(qlog::events::quic::ConnectionClosedTrigger::Clean)\n                        } else {\n                            Some(qlog::events::quic::ConnectionClosedTrigger::Error)\n                        };\n\n                        (Some(qlog::events::ConnectionClosedEventError::TransportError(qlog::events::quic::TransportError::Unknown)), None, trigger)\n                    };\n\n                    // TODO: select more appopriate connection_code and application_error than unknown.\n                    qlog::events::quic::ConnectionClosed {\n                        initiator: Some(TransportInitiator::Remote),\n                        connection_error: connection_code,\n                        application_error,\n                        error_code: Some(peer_error.error_code),\n                        internal_code: None,\n                        reason: Some(String::from_utf8_lossy(&peer_error.reason).to_string()),\n                        trigger,\n                    }\n                },\n\n                (true, false, None, Some(local_error)) => {\n                    let (connection_code, application_error, trigger) = if local_error.is_app {\n                        (None, Some(qlog::events::ApplicationError::Unknown), None)\n                    } else {\n                        let trigger = if local_error.error_code == WireErrorCode::NoError as u64 {\n                            Some(qlog::events::quic::ConnectionClosedTrigger::Clean)\n                        } else {\n                            Some(qlog::events::quic::ConnectionClosedTrigger::Error)\n                        };\n\n                        (Some(qlog::events::ConnectionClosedEventError::TransportError(qlog::events::quic::TransportError::Unknown)), None, trigger)\n                    };\n\n                    // TODO: select more appopriate connection_code and application_error than unknown.\n                    qlog::events::quic::ConnectionClosed {\n                        initiator: Some(TransportInitiator::Local),\n                        connection_error: connection_code,\n                        application_error,\n                        error_code: Some(local_error.error_code),\n                        internal_code: None,\n                        reason: Some(String::from_utf8_lossy(&local_error.reason).to_string()),\n                        trigger,\n                    }\n                },\n\n                _ => qlog::events::quic::ConnectionClosed {\n                    initiator: None,\n                    connection_error: None,\n                    application_error: None,\n                    error_code: None,\n                    internal_code: None,\n                    reason: None,\n                    trigger: None,\n                },\n            };\n\n            qlog_with_type!(QLOG_CONNECTION_CLOSED, self.qlog, q, {\n                let ev_data = EventData::QuicConnectionClosed(cc);\n\n                q.add_event_data_now(ev_data).ok();\n            });\n            self.qlog.streamer = None;\n        }\n        self.closed = true;\n    }\n}\n\n#[cfg(feature = \"boringssl-boring-crate\")]\nimpl<F: BufFactory> AsMut<boring::ssl::SslRef> for Connection<F> {\n    fn as_mut(&mut self) -> &mut boring::ssl::SslRef {\n        self.handshake.ssl_mut()\n    }\n}\n\n/// Maps an `Error` to `Error::Done`, or itself.\n///\n/// When a received packet that hasn't yet been authenticated triggers a failure\n/// it should, in most cases, be ignored, instead of raising a connection error,\n/// to avoid potential man-in-the-middle and man-on-the-side attacks.\n///\n/// However, if no other packet was previously received, the connection should\n/// indeed be closed as the received packet might just be network background\n/// noise, and it shouldn't keep resources occupied indefinitely.\n///\n/// This function maps an error to `Error::Done` to ignore a packet failure\n/// without aborting the connection, except when no other packet was previously\n/// received, in which case the error itself is returned, but only on the\n/// server-side as the client will already have armed the idle timer.\n///\n/// This must only be used for errors preceding packet authentication. Failures\n/// happening after a packet has been authenticated should still cause the\n/// connection to be aborted.\nfn drop_pkt_on_err(\n    e: Error, recv_count: usize, is_server: bool, trace_id: &str,\n) -> Error {\n    // On the server, if no other packet has been successfully processed, abort\n    // the connection to avoid keeping the connection open when only junk is\n    // received.\n    if is_server && recv_count == 0 {\n        return e;\n    }\n\n    trace!(\"{trace_id} dropped invalid packet\");\n\n    // Ignore other invalid packets that haven't been authenticated to prevent\n    // man-in-the-middle and man-on-the-side attacks.\n    Error::Done\n}\n\nstruct AddrTupleFmt(SocketAddr, SocketAddr);\n\nimpl std::fmt::Display for AddrTupleFmt {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        let AddrTupleFmt(src, dst) = &self;\n\n        if src.ip().is_unspecified() || dst.ip().is_unspecified() {\n            return Ok(());\n        }\n\n        f.write_fmt(format_args!(\"src:{src} dst:{dst}\"))\n    }\n}\n\n/// Statistics about the connection.\n///\n/// A connection's statistics can be collected using the [`stats()`] method.\n///\n/// [`stats()`]: struct.Connection.html#method.stats\n#[derive(Clone, Default)]\npub struct Stats {\n    /// The number of QUIC packets received.\n    pub recv: usize,\n\n    /// The number of QUIC packets sent.\n    pub sent: usize,\n\n    /// The number of QUIC packets that were lost.\n    pub lost: usize,\n\n    /// The number of QUIC packets that were marked as lost but later acked.\n    pub spurious_lost: usize,\n\n    /// The number of sent QUIC packets with retransmitted data.\n    pub retrans: usize,\n\n    /// The number of sent bytes.\n    pub sent_bytes: u64,\n\n    /// The number of received bytes.\n    pub recv_bytes: u64,\n\n    /// The number of bytes sent acked.\n    pub acked_bytes: u64,\n\n    /// The number of bytes sent lost.\n    pub lost_bytes: u64,\n\n    /// The number of stream bytes retransmitted.\n    pub stream_retrans_bytes: u64,\n\n    /// The number of DATAGRAM frames received.\n    pub dgram_recv: usize,\n\n    /// The number of DATAGRAM frames sent.\n    pub dgram_sent: usize,\n\n    /// The number of known paths for the connection.\n    pub paths_count: usize,\n\n    /// The number of streams reset by local.\n    pub reset_stream_count_local: u64,\n\n    /// The number of streams stopped by local.\n    pub stopped_stream_count_local: u64,\n\n    /// The number of streams reset by remote.\n    pub reset_stream_count_remote: u64,\n\n    /// The number of streams stopped by remote.\n    pub stopped_stream_count_remote: u64,\n\n    /// The number of DATA_BLOCKED frames sent due to hitting the connection\n    /// flow control limit.\n    pub data_blocked_sent_count: u64,\n\n    /// The number of STREAM_DATA_BLOCKED frames sent due to a stream hitting\n    /// the stream flow control limit.\n    pub stream_data_blocked_sent_count: u64,\n\n    /// The number of DATA_BLOCKED frames received from the remote.\n    pub data_blocked_recv_count: u64,\n\n    /// The number of STREAM_DATA_BLOCKED frames received from the remote.\n    pub stream_data_blocked_recv_count: u64,\n\n    /// The number of STREAMS_BLOCKED frames for bidirectional streams received\n    /// from the remote, indicating the peer is blocked on opening new\n    /// bidirectional streams.\n    pub streams_blocked_bidi_recv_count: u64,\n\n    /// The number of STREAMS_BLOCKED frames for unidirectional streams received\n    /// from the remote, indicating the peer is blocked on opening new\n    /// unidirectional streams.\n    pub streams_blocked_uni_recv_count: u64,\n\n    /// The total number of PATH_CHALLENGE frames that were received.\n    pub path_challenge_rx_count: u64,\n\n    /// Total duration during which this side of the connection was\n    /// actively sending bytes or waiting for those bytes to be acked.\n    pub bytes_in_flight_duration: Duration,\n\n    /// Health state of the connection's tx_buffered.\n    pub tx_buffered_state: TxBufferTrackingState,\n}\n\nimpl std::fmt::Debug for Stats {\n    #[inline]\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(\n            f,\n            \"recv={} sent={} lost={} retrans={}\",\n            self.recv, self.sent, self.lost, self.retrans,\n        )?;\n\n        write!(\n            f,\n            \" sent_bytes={} recv_bytes={} lost_bytes={}\",\n            self.sent_bytes, self.recv_bytes, self.lost_bytes,\n        )?;\n\n        Ok(())\n    }\n}\n\n#[doc(hidden)]\n#[cfg(any(test, feature = \"internal\"))]\npub mod test_utils;\n\n#[cfg(test)]\nmod tests;\n\npub use crate::packet::ConnectionId;\npub use crate::packet::Header;\npub use crate::packet::Type;\n\npub use crate::path::PathEvent;\npub use crate::path::PathStats;\npub use crate::path::SocketAddrIter;\n\npub use crate::recovery::BbrBwLoReductionStrategy;\npub use crate::recovery::BbrParams;\npub use crate::recovery::CongestionControlAlgorithm;\npub use crate::recovery::StartupExit;\npub use crate::recovery::StartupExitReason;\n\npub use crate::stream::StreamIter;\n\npub use crate::transport_params::TransportParams;\npub use crate::transport_params::UnknownTransportParameter;\npub use crate::transport_params::UnknownTransportParameterIterator;\npub use crate::transport_params::UnknownTransportParameters;\n\npub use crate::buffers::BufFactory;\npub use crate::buffers::BufSplit;\n\npub use crate::error::ConnectionError;\npub use crate::error::Error;\npub use crate::error::Result;\npub use crate::error::WireErrorCode;\n\nmod buffers;\nmod cid;\nmod crypto;\nmod dgram;\nmod error;\n#[cfg(feature = \"ffi\")]\nmod ffi;\nmod flowcontrol;\nmod frame;\npub mod h3;\nmod minmax;\nmod packet;\nmod path;\nmod pmtud;\nmod rand;\nmod range_buf;\nmod ranges;\nmod recovery;\nmod stream;\nmod tls;\nmod transport_params;\n"
  },
  {
    "path": "quiche/src/minmax.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\n// Copyright (C) 2017, Google, Inc.\n//\n// Use of this source code is governed by the following BSD-style license:\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//    * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//    * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//\n//    * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n// lib/minmax.c: windowed min/max tracker\n//\n// Kathleen Nichols' algorithm for tracking the minimum (or maximum)\n// value of a data stream over some fixed time interval.  (E.g.,\n// the minimum RTT over the past five minutes.) It uses constant\n// space and constant time per update yet almost always delivers\n// the same minimum as an implementation that has to keep all the\n// data in the window.\n//\n// The algorithm keeps track of the best, 2nd best & 3rd best min\n// values, maintaining an invariant that the measurement time of\n// the n'th best >= n-1'th best. It also makes sure that the three\n// values are widely separated in the time window since that bounds\n// the worse case error when that data is monotonically increasing\n// over the window.\n//\n// Upon getting a new min, we can forget everything earlier because\n// it has no value - the new min is <= everything else in the window\n// by definition and it's the most recent. So we restart fresh on\n// every new min and overwrites 2nd & 3rd choices. The same property\n// holds for 2nd & 3rd best.\n\nuse std::ops::Deref;\n\nuse std::time::Duration;\nuse std::time::Instant;\n\n#[derive(Copy, Clone)]\nstruct MinmaxSample<T> {\n    time: Instant,\n    value: T,\n}\n\npub struct Minmax<T> {\n    estimate: [MinmaxSample<T>; 3],\n}\n\nimpl<T> Deref for Minmax<T> {\n    type Target = T;\n\n    fn deref(&self) -> &Self::Target {\n        &self.estimate[0].value\n    }\n}\n\nimpl<T: PartialOrd + Copy> Minmax<T> {\n    pub fn new(val: T) -> Self {\n        Minmax {\n            estimate: [MinmaxSample {\n                time: Instant::now(),\n                value: val,\n            }; 3],\n        }\n    }\n\n    /// Resets the estimates to the given value.\n    pub fn reset(&mut self, time: Instant, meas: T) -> T {\n        let val = MinmaxSample { time, value: meas };\n\n        for i in self.estimate.iter_mut() {\n            *i = val;\n        }\n\n        self.estimate[0].value\n    }\n\n    /// Updates the min estimate based on the given measurement, and returns it.\n    pub fn running_min(&mut self, win: Duration, time: Instant, meas: T) -> T {\n        let val = MinmaxSample { time, value: meas };\n\n        let delta_time = time.duration_since(self.estimate[2].time);\n\n        // Reset if there's nothing in the window or a new min value is found.\n        if val.value <= self.estimate[0].value || delta_time > win {\n            return self.reset(time, meas);\n        }\n\n        if val.value <= self.estimate[1].value {\n            self.estimate[2] = val;\n            self.estimate[1] = val;\n        } else if val.value <= self.estimate[2].value {\n            self.estimate[2] = val;\n        }\n\n        self.subwin_update(win, time, meas)\n    }\n\n    /// Updates the max estimate based on the given measurement, and returns it.\n    #[cfg(test)]\n    pub fn running_max(&mut self, win: Duration, time: Instant, meas: T) -> T {\n        let val = MinmaxSample { time, value: meas };\n\n        let delta_time = time.duration_since(self.estimate[2].time);\n\n        // Reset if there's nothing in the window or a new max value is found.\n        if val.value >= self.estimate[0].value || delta_time > win {\n            return self.reset(time, meas);\n        }\n\n        if val.value >= self.estimate[1].value {\n            self.estimate[2] = val;\n            self.estimate[1] = val;\n        } else if val.value >= self.estimate[2].value {\n            self.estimate[2] = val\n        }\n\n        self.subwin_update(win, time, meas)\n    }\n\n    /// As time advances, update the 1st, 2nd and 3rd estimates.\n    fn subwin_update(&mut self, win: Duration, time: Instant, meas: T) -> T {\n        let val = MinmaxSample { time, value: meas };\n\n        let delta_time = time.duration_since(self.estimate[0].time);\n\n        if delta_time > win {\n            // Passed entire window without a new val so make 2nd estimate the\n            // new val & 3rd estimate the new 2nd choice. we may have to iterate\n            // this since our 2nd estimate may also be outside the window (we\n            // checked on entry that the third estimate was in the window).\n            self.estimate[0] = self.estimate[1];\n            self.estimate[1] = self.estimate[2];\n            self.estimate[2] = val;\n\n            if time.duration_since(self.estimate[0].time) > win {\n                self.estimate[0] = self.estimate[1];\n                self.estimate[1] = self.estimate[2];\n                self.estimate[2] = val;\n            }\n        } else if self.estimate[1].time == self.estimate[0].time &&\n            delta_time > win.div_f32(4.0)\n        {\n            // We've passed a quarter of the window without a new val so take a\n            // 2nd estimate from the 2nd quarter of the window.\n            self.estimate[2] = val;\n            self.estimate[1] = val;\n        } else if self.estimate[2].time == self.estimate[1].time &&\n            delta_time > win.div_f32(2.0)\n        {\n            // We've passed half the window without finding a new val so take a\n            // 3rd estimate from the last half of the window.\n            self.estimate[2] = val;\n        }\n\n        self.estimate[0].value\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn reset_filter_rtt() {\n        let mut f = Minmax::new(Duration::ZERO);\n        let now = Instant::now();\n        let rtt = Duration::from_millis(50);\n\n        let rtt_min = f.reset(now, rtt);\n        assert_eq!(rtt_min, rtt);\n\n        assert_eq!(f.estimate[0].time, now);\n        assert_eq!(f.estimate[0].value, rtt);\n\n        assert_eq!(f.estimate[1].time, now);\n        assert_eq!(f.estimate[1].value, rtt);\n\n        assert_eq!(f.estimate[2].time, now);\n        assert_eq!(f.estimate[2].value, rtt);\n    }\n\n    #[test]\n    fn reset_filter_bandwidth() {\n        let mut f = Minmax::new(0);\n        let now = Instant::now();\n        let bw = 2000;\n\n        let bw_min = f.reset(now, bw);\n        assert_eq!(bw_min, bw);\n\n        assert_eq!(f.estimate[0].time, now);\n        assert_eq!(f.estimate[0].value, bw);\n\n        assert_eq!(f.estimate[1].time, now);\n        assert_eq!(f.estimate[1].value, bw);\n\n        assert_eq!(f.estimate[2].time, now);\n        assert_eq!(f.estimate[2].value, bw);\n    }\n\n    #[test]\n    fn get_windowed_min_rtt() {\n        let mut f = Minmax::new(Duration::ZERO);\n        let rtt_25 = Duration::from_millis(25);\n        let rtt_24 = Duration::from_millis(24);\n        let win = Duration::from_millis(500);\n        let mut time = Instant::now();\n\n        let mut rtt_min = f.reset(time, rtt_25);\n        assert_eq!(rtt_min, rtt_25);\n\n        time += Duration::from_millis(250);\n        rtt_min = f.running_min(win, time, rtt_24);\n        assert_eq!(rtt_min, rtt_24);\n        assert_eq!(f.estimate[1].value, rtt_24);\n        assert_eq!(f.estimate[2].value, rtt_24);\n\n        time += Duration::from_millis(600);\n        rtt_min = f.running_min(win, time, rtt_25);\n        assert_eq!(rtt_min, rtt_25);\n        assert_eq!(f.estimate[1].value, rtt_25);\n        assert_eq!(f.estimate[2].value, rtt_25);\n    }\n\n    #[test]\n    fn get_windowed_min_bandwidth() {\n        let mut f = Minmax::new(0);\n        let bw_200 = 200;\n        let bw_500 = 500;\n        let win = Duration::from_millis(500);\n        let mut time = Instant::now();\n\n        let mut bw_min = f.reset(time, bw_500);\n        assert_eq!(bw_min, bw_500);\n\n        time += Duration::from_millis(250);\n        bw_min = f.running_min(win, time, bw_200);\n        assert_eq!(bw_min, bw_200);\n        assert_eq!(f.estimate[1].value, bw_200);\n        assert_eq!(f.estimate[2].value, bw_200);\n\n        time += Duration::from_millis(600);\n        bw_min = f.running_min(win, time, bw_500);\n        assert_eq!(bw_min, bw_500);\n        assert_eq!(f.estimate[1].value, bw_500);\n        assert_eq!(f.estimate[2].value, bw_500);\n    }\n\n    #[test]\n    fn get_windowed_max_rtt() {\n        let mut f = Minmax::new(Duration::ZERO);\n        let rtt_25 = Duration::from_millis(25);\n        let rtt_24 = Duration::from_millis(24);\n        let win = Duration::from_millis(500);\n        let mut time = Instant::now();\n\n        let mut rtt_max = f.reset(time, rtt_24);\n        assert_eq!(rtt_max, rtt_24);\n\n        time += Duration::from_millis(250);\n        rtt_max = f.running_max(win, time, rtt_25);\n        assert_eq!(rtt_max, rtt_25);\n        assert_eq!(f.estimate[1].value, rtt_25);\n        assert_eq!(f.estimate[2].value, rtt_25);\n\n        time += Duration::from_millis(600);\n        rtt_max = f.running_max(win, time, rtt_24);\n        assert_eq!(rtt_max, rtt_24);\n        assert_eq!(f.estimate[1].value, rtt_24);\n        assert_eq!(f.estimate[2].value, rtt_24);\n    }\n\n    #[test]\n    fn get_windowed_max_bandwidth() {\n        let mut f = Minmax::new(0);\n        let bw_200 = 200;\n        let bw_500 = 500;\n        let win = Duration::from_millis(500);\n        let mut time = Instant::now();\n\n        let mut bw_max = f.reset(time, bw_200);\n        assert_eq!(bw_max, bw_200);\n\n        time += Duration::from_millis(5000);\n        bw_max = f.running_max(win, time, bw_500);\n        assert_eq!(bw_max, bw_500);\n        assert_eq!(f.estimate[1].value, bw_500);\n        assert_eq!(f.estimate[2].value, bw_500);\n\n        time += Duration::from_millis(600);\n        bw_max = f.running_max(win, time, bw_200);\n        assert_eq!(bw_max, bw_200);\n        assert_eq!(f.estimate[1].value, bw_200);\n        assert_eq!(f.estimate[2].value, bw_200);\n    }\n\n    #[test]\n    fn get_windowed_min_estimates_rtt() {\n        let mut f = Minmax::new(Duration::ZERO);\n        let rtt_25 = Duration::from_millis(25);\n        let rtt_24 = Duration::from_millis(24);\n        let rtt_23 = Duration::from_millis(23);\n        let rtt_22 = Duration::from_millis(22);\n        let win = Duration::from_secs(1);\n        let mut time = Instant::now();\n\n        let mut rtt_min = f.reset(time, rtt_23);\n        assert_eq!(rtt_min, rtt_23);\n\n        time += Duration::from_millis(300);\n        rtt_min = f.running_min(win, time, rtt_24);\n        assert_eq!(rtt_min, rtt_23);\n        assert_eq!(f.estimate[1].value, rtt_24);\n        assert_eq!(f.estimate[2].value, rtt_24);\n\n        time += Duration::from_millis(300);\n        rtt_min = f.running_min(win, time, rtt_25);\n        assert_eq!(rtt_min, rtt_23);\n        assert_eq!(f.estimate[1].value, rtt_24);\n        assert_eq!(f.estimate[2].value, rtt_25);\n\n        time += Duration::from_millis(300);\n        rtt_min = f.running_min(win, time, rtt_22);\n        assert_eq!(rtt_min, rtt_22);\n        assert_eq!(f.estimate[1].value, rtt_22);\n        assert_eq!(f.estimate[2].value, rtt_22);\n    }\n\n    #[test]\n    fn get_windowed_min_estimates_bandwidth() {\n        let mut f = Minmax::new(0);\n        let bw_500 = 500;\n        let bw_400 = 400;\n        let bw_300 = 300;\n        let bw_200 = 200;\n        let win = Duration::from_secs(1);\n        let mut time = Instant::now();\n\n        let mut bw_min = f.reset(time, bw_300);\n        assert_eq!(bw_min, bw_300);\n\n        time += Duration::from_millis(300);\n        bw_min = f.running_min(win, time, bw_400);\n        assert_eq!(bw_min, bw_300);\n        assert_eq!(f.estimate[1].value, bw_400);\n        assert_eq!(f.estimate[2].value, bw_400);\n\n        time += Duration::from_millis(300);\n        bw_min = f.running_min(win, time, bw_500);\n        assert_eq!(bw_min, bw_300);\n        assert_eq!(f.estimate[1].value, bw_400);\n        assert_eq!(f.estimate[2].value, bw_500);\n\n        time += Duration::from_millis(300);\n        bw_min = f.running_min(win, time, bw_200);\n        assert_eq!(bw_min, bw_200);\n        assert_eq!(f.estimate[1].value, bw_200);\n        assert_eq!(f.estimate[2].value, bw_200);\n    }\n\n    #[test]\n    fn get_windowed_max_estimates_rtt() {\n        let mut f = Minmax::new(Duration::ZERO);\n        let rtt_25 = Duration::from_millis(25);\n        let rtt_24 = Duration::from_millis(24);\n        let rtt_23 = Duration::from_millis(23);\n        let rtt_26 = Duration::from_millis(26);\n        let win = Duration::from_secs(1);\n        let mut time = Instant::now();\n\n        let mut rtt_max = f.reset(time, rtt_25);\n        assert_eq!(rtt_max, rtt_25);\n\n        time += Duration::from_millis(300);\n        rtt_max = f.running_max(win, time, rtt_24);\n        assert_eq!(rtt_max, rtt_25);\n        assert_eq!(f.estimate[1].value, rtt_24);\n        assert_eq!(f.estimate[2].value, rtt_24);\n\n        time += Duration::from_millis(300);\n        rtt_max = f.running_max(win, time, rtt_23);\n        assert_eq!(rtt_max, rtt_25);\n        assert_eq!(f.estimate[1].value, rtt_24);\n        assert_eq!(f.estimate[2].value, rtt_23);\n\n        time += Duration::from_millis(300);\n        rtt_max = f.running_max(win, time, rtt_26);\n        assert_eq!(rtt_max, rtt_26);\n        assert_eq!(f.estimate[1].value, rtt_26);\n        assert_eq!(f.estimate[2].value, rtt_26);\n    }\n\n    #[test]\n    fn get_windowed_max_estimates_bandwidth() {\n        let mut f = Minmax::new(0);\n        let bw_500 = 500;\n        let bw_400 = 400;\n        let bw_300 = 300;\n        let bw_600 = 600;\n        let win = Duration::from_secs(1);\n        let mut time = Instant::now();\n\n        let mut bw_max = f.reset(time, bw_500);\n        assert_eq!(bw_max, bw_500);\n\n        time += Duration::from_millis(300);\n        bw_max = f.running_max(win, time, bw_400);\n        assert_eq!(bw_max, bw_500);\n        assert_eq!(f.estimate[1].value, bw_400);\n        assert_eq!(f.estimate[2].value, bw_400);\n\n        time += Duration::from_millis(300);\n        bw_max = f.running_max(win, time, bw_300);\n        assert_eq!(bw_max, bw_500);\n        assert_eq!(f.estimate[1].value, bw_400);\n        assert_eq!(f.estimate[2].value, bw_300);\n\n        time += Duration::from_millis(300);\n        bw_max = f.running_max(win, time, bw_600);\n        assert_eq!(bw_max, bw_600);\n        assert_eq!(f.estimate[1].value, bw_600);\n        assert_eq!(f.estimate[2].value, bw_600);\n    }\n}\n"
  },
  {
    "path": "quiche/src/packet.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::fmt::Display;\n\nuse std::ops::Index;\nuse std::ops::IndexMut;\nuse std::ops::RangeInclusive;\n\nuse std::time::Instant;\n\nuse crate::Error;\nuse crate::Result;\nuse crate::DEFAULT_INITIAL_CONGESTION_WINDOW_PACKETS;\n\nuse crate::crypto;\nuse crate::rand;\nuse crate::ranges;\nuse crate::recovery;\nuse crate::stream;\n\nconst FORM_BIT: u8 = 0x80;\nconst FIXED_BIT: u8 = 0x40;\nconst KEY_PHASE_BIT: u8 = 0x04;\n\nconst TYPE_MASK: u8 = 0x30;\nconst PKT_NUM_MASK: u8 = 0x03;\n\npub const MAX_CID_LEN: u8 = 20;\n\npub const MAX_PKT_NUM_LEN: usize = 4;\n\nconst SAMPLE_LEN: usize = 16;\n\n// Set the min skip skip interval to 2x the default number of initial packet\n// count.\nconst MIN_SKIP_COUNTER_VALUE: u64 =\n    DEFAULT_INITIAL_CONGESTION_WINDOW_PACKETS as u64 * 2;\n\nconst RETRY_AEAD_ALG: crypto::Algorithm = crypto::Algorithm::AES128_GCM;\n\n#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]\npub enum Epoch {\n    Initial     = 0,\n    Handshake   = 1,\n    Application = 2,\n}\n\nstatic EPOCHS: [Epoch; 3] =\n    [Epoch::Initial, Epoch::Handshake, Epoch::Application];\n\nimpl Epoch {\n    /// Returns an ordered slice containing the `Epoch`s that fit in the\n    /// provided `range`.\n    pub fn epochs(range: RangeInclusive<Epoch>) -> &'static [Epoch] {\n        &EPOCHS[*range.start() as usize..=*range.end() as usize]\n    }\n\n    pub const fn count() -> usize {\n        3\n    }\n}\n\nimpl Display for Epoch {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", usize::from(*self))\n    }\n}\n\nimpl From<Epoch> for usize {\n    fn from(e: Epoch) -> Self {\n        e as usize\n    }\n}\n\nimpl<T> Index<Epoch> for [T]\nwhere\n    T: Sized,\n{\n    type Output = T;\n\n    fn index(&self, index: Epoch) -> &Self::Output {\n        self.index(usize::from(index))\n    }\n}\n\nimpl<T> IndexMut<Epoch> for [T]\nwhere\n    T: Sized,\n{\n    fn index_mut(&mut self, index: Epoch) -> &mut Self::Output {\n        self.index_mut(usize::from(index))\n    }\n}\n\n/// QUIC packet type.\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum Type {\n    /// Initial packet.\n    Initial,\n\n    /// Retry packet.\n    Retry,\n\n    /// Handshake packet.\n    Handshake,\n\n    /// 0-RTT packet.\n    ZeroRTT,\n\n    /// Version negotiation packet.\n    VersionNegotiation,\n\n    /// 1-RTT short header packet.\n    Short,\n}\n\nimpl Type {\n    pub(crate) fn from_epoch(e: Epoch) -> Type {\n        match e {\n            Epoch::Initial => Type::Initial,\n\n            Epoch::Handshake => Type::Handshake,\n\n            Epoch::Application => Type::Short,\n        }\n    }\n\n    pub(crate) fn to_epoch(self) -> Result<Epoch> {\n        match self {\n            Type::Initial => Ok(Epoch::Initial),\n\n            Type::ZeroRTT => Ok(Epoch::Application),\n\n            Type::Handshake => Ok(Epoch::Handshake),\n\n            Type::Short => Ok(Epoch::Application),\n\n            _ => Err(Error::InvalidPacket),\n        }\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub(crate) fn to_qlog(self) -> qlog::events::quic::PacketType {\n        match self {\n            Type::Initial => qlog::events::quic::PacketType::Initial,\n\n            Type::Retry => qlog::events::quic::PacketType::Retry,\n\n            Type::Handshake => qlog::events::quic::PacketType::Handshake,\n\n            Type::ZeroRTT => qlog::events::quic::PacketType::ZeroRtt,\n\n            Type::VersionNegotiation =>\n                qlog::events::quic::PacketType::VersionNegotiation,\n\n            Type::Short => qlog::events::quic::PacketType::OneRtt,\n        }\n    }\n}\n\n/// A QUIC connection ID.\npub struct ConnectionId<'a>(ConnectionIdInner<'a>);\n\nenum ConnectionIdInner<'a> {\n    Vec(Vec<u8>),\n    Ref(&'a [u8]),\n}\n\nimpl<'a> ConnectionId<'a> {\n    /// Creates a new connection ID from the given vector.\n    #[inline]\n    pub const fn from_vec(cid: Vec<u8>) -> Self {\n        Self(ConnectionIdInner::Vec(cid))\n    }\n\n    /// Creates a new connection ID from the given slice.\n    #[inline]\n    pub const fn from_ref(cid: &'a [u8]) -> Self {\n        Self(ConnectionIdInner::Ref(cid))\n    }\n\n    /// Returns a new owning connection ID from the given existing one.\n    #[inline]\n    pub fn into_owned(self) -> ConnectionId<'static> {\n        ConnectionId::from_vec(self.into())\n    }\n}\n\nimpl Default for ConnectionId<'_> {\n    #[inline]\n    fn default() -> Self {\n        Self::from_vec(Vec::new())\n    }\n}\n\nimpl From<Vec<u8>> for ConnectionId<'_> {\n    #[inline]\n    fn from(v: Vec<u8>) -> Self {\n        Self::from_vec(v)\n    }\n}\n\nimpl From<ConnectionId<'_>> for Vec<u8> {\n    #[inline]\n    fn from(id: ConnectionId<'_>) -> Self {\n        match id.0 {\n            ConnectionIdInner::Vec(cid) => cid,\n            ConnectionIdInner::Ref(cid) => cid.to_vec(),\n        }\n    }\n}\n\nimpl PartialEq for ConnectionId<'_> {\n    #[inline]\n    fn eq(&self, other: &Self) -> bool {\n        self.as_ref() == other.as_ref()\n    }\n}\n\nimpl Eq for ConnectionId<'_> {}\n\nimpl AsRef<[u8]> for ConnectionId<'_> {\n    #[inline]\n    fn as_ref(&self) -> &[u8] {\n        match &self.0 {\n            ConnectionIdInner::Vec(v) => v.as_ref(),\n            ConnectionIdInner::Ref(v) => v,\n        }\n    }\n}\n\nimpl std::hash::Hash for ConnectionId<'_> {\n    #[inline]\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.as_ref().hash(state);\n    }\n}\n\nimpl std::ops::Deref for ConnectionId<'_> {\n    type Target = [u8];\n\n    #[inline]\n    fn deref(&self) -> &[u8] {\n        match &self.0 {\n            ConnectionIdInner::Vec(v) => v.as_ref(),\n            ConnectionIdInner::Ref(v) => v,\n        }\n    }\n}\n\nimpl Clone for ConnectionId<'_> {\n    #[inline]\n    fn clone(&self) -> Self {\n        Self::from_vec(self.as_ref().to_vec())\n    }\n}\n\nimpl std::fmt::Debug for ConnectionId<'_> {\n    #[inline]\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        for c in self.as_ref() {\n            write!(f, \"{c:02x}\")?;\n        }\n\n        Ok(())\n    }\n}\n\n/// A QUIC packet's header.\n#[derive(Clone, PartialEq, Eq)]\npub struct Header<'a> {\n    /// The type of the packet.\n    pub ty: Type,\n\n    /// The version of the packet.\n    pub version: u32,\n\n    /// The destination connection ID of the packet.\n    pub dcid: ConnectionId<'a>,\n\n    /// The source connection ID of the packet.\n    pub scid: ConnectionId<'a>,\n\n    /// The packet number. It's only meaningful after the header protection is\n    /// removed.\n    pub(crate) pkt_num: u64,\n\n    /// The length of the packet number. It's only meaningful after the header\n    /// protection is removed.\n    pub(crate) pkt_num_len: usize,\n\n    /// The address verification token of the packet. Only present in `Initial`\n    /// and `Retry` packets.\n    pub token: Option<Vec<u8>>,\n\n    /// The list of versions in the packet. Only present in\n    /// `VersionNegotiation` packets.\n    pub versions: Option<Vec<u32>>,\n\n    /// The key phase bit of the packet. It's only meaningful after the header\n    /// protection is removed.\n    pub(crate) key_phase: bool,\n}\n\nimpl<'a> Header<'a> {\n    /// Parses a QUIC packet header from the given buffer.\n    ///\n    /// The `dcid_len` parameter is the length of the destination connection ID,\n    /// required to parse short header packets.\n    ///\n    /// ## Examples:\n    ///\n    /// ```no_run\n    /// # const LOCAL_CONN_ID_LEN: usize = 16;\n    /// # let mut buf = [0; 512];\n    /// # let mut out = [0; 512];\n    /// # let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    /// let (len, src) = socket.recv_from(&mut buf).unwrap();\n    ///\n    /// let hdr = quiche::Header::from_slice(&mut buf[..len], LOCAL_CONN_ID_LEN)?;\n    /// # Ok::<(), quiche::Error>(())\n    /// ```\n    #[inline]\n    pub fn from_slice<'b>(\n        buf: &'b mut [u8], dcid_len: usize,\n    ) -> Result<Header<'a>> {\n        let mut b = octets::OctetsMut::with_slice(buf);\n        Header::from_bytes(&mut b, dcid_len)\n    }\n\n    pub(crate) fn from_bytes<'b>(\n        b: &'b mut octets::OctetsMut, dcid_len: usize,\n    ) -> Result<Header<'a>> {\n        let first = b.get_u8()?;\n\n        if !Header::is_long(first) {\n            // Decode short header.\n            let dcid = b.get_bytes(dcid_len)?;\n\n            return Ok(Header {\n                ty: Type::Short,\n                version: 0,\n                dcid: dcid.to_vec().into(),\n                scid: ConnectionId::default(),\n                pkt_num: 0,\n                pkt_num_len: 0,\n                token: None,\n                versions: None,\n                key_phase: false,\n            });\n        }\n\n        // Decode long header.\n        let version = b.get_u32()?;\n\n        let ty = if version == 0 {\n            Type::VersionNegotiation\n        } else {\n            match (first & TYPE_MASK) >> 4 {\n                0x00 => Type::Initial,\n                0x01 => Type::ZeroRTT,\n                0x02 => Type::Handshake,\n                0x03 => Type::Retry,\n                _ => return Err(Error::InvalidPacket),\n            }\n        };\n\n        let dcid_len = b.get_u8()?;\n        if crate::version_is_supported(version) && dcid_len > MAX_CID_LEN {\n            return Err(Error::InvalidPacket);\n        }\n        let dcid = b.get_bytes(dcid_len as usize)?.to_vec();\n\n        let scid_len = b.get_u8()?;\n        if crate::version_is_supported(version) && scid_len > MAX_CID_LEN {\n            return Err(Error::InvalidPacket);\n        }\n        let scid = b.get_bytes(scid_len as usize)?.to_vec();\n\n        // End of invariants.\n\n        let mut token: Option<Vec<u8>> = None;\n        let mut versions: Option<Vec<u32>> = None;\n\n        match ty {\n            Type::Initial => {\n                token = Some(b.get_bytes_with_varint_length()?.to_vec());\n            },\n\n            Type::Retry => {\n                const TAG_LEN: usize = RETRY_AEAD_ALG.tag_len();\n\n                // Exclude the integrity tag from the token.\n                if b.cap() < TAG_LEN {\n                    return Err(Error::InvalidPacket);\n                }\n\n                let token_len = b.cap() - TAG_LEN;\n                token = Some(b.get_bytes(token_len)?.to_vec());\n            },\n\n            Type::VersionNegotiation => {\n                let mut list: Vec<u32> = Vec::new();\n\n                while b.cap() > 0 {\n                    let version = b.get_u32()?;\n                    list.push(version);\n                }\n\n                versions = Some(list);\n            },\n\n            _ => (),\n        };\n\n        Ok(Header {\n            ty,\n            version,\n            dcid: dcid.into(),\n            scid: scid.into(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token,\n            versions,\n            key_phase: false,\n        })\n    }\n\n    pub(crate) fn to_bytes(&self, out: &mut octets::OctetsMut) -> Result<()> {\n        let mut first = 0;\n\n        // Encode pkt num length.\n        first |= self.pkt_num_len.saturating_sub(1) as u8;\n\n        // Encode short header.\n        if self.ty == Type::Short {\n            // Unset form bit for short header.\n            first &= !FORM_BIT;\n\n            // Set fixed bit.\n            first |= FIXED_BIT;\n\n            // Set key phase bit.\n            if self.key_phase {\n                first |= KEY_PHASE_BIT;\n            } else {\n                first &= !KEY_PHASE_BIT;\n            }\n\n            out.put_u8(first)?;\n            out.put_bytes(&self.dcid)?;\n\n            return Ok(());\n        }\n\n        // Encode long header.\n        let ty: u8 = match self.ty {\n            Type::Initial => 0x00,\n            Type::ZeroRTT => 0x01,\n            Type::Handshake => 0x02,\n            Type::Retry => 0x03,\n            _ => return Err(Error::InvalidPacket),\n        };\n\n        first |= FORM_BIT | FIXED_BIT | (ty << 4);\n\n        out.put_u8(first)?;\n\n        out.put_u32(self.version)?;\n\n        out.put_u8(self.dcid.len() as u8)?;\n        out.put_bytes(&self.dcid)?;\n\n        out.put_u8(self.scid.len() as u8)?;\n        out.put_bytes(&self.scid)?;\n\n        // Only Initial and Retry packets have a token.\n        match self.ty {\n            Type::Initial => {\n                match self.token {\n                    Some(ref v) => {\n                        out.put_varint(v.len() as u64)?;\n                        out.put_bytes(v)?;\n                    },\n\n                    // No token, so length = 0.\n                    None => {\n                        out.put_varint(0)?;\n                    },\n                }\n            },\n\n            Type::Retry => {\n                // Retry packets don't have a token length.\n                out.put_bytes(self.token.as_ref().unwrap())?;\n            },\n\n            _ => (),\n        }\n\n        Ok(())\n    }\n\n    /// Returns true if the packet has a long header.\n    ///\n    /// The `b` parameter represents the first byte of the QUIC header.\n    fn is_long(b: u8) -> bool {\n        b & FORM_BIT != 0\n    }\n}\n\nimpl std::fmt::Debug for Header<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{:?}\", self.ty)?;\n\n        if self.ty != Type::Short {\n            write!(f, \" version={:x}\", self.version)?;\n        }\n\n        write!(f, \" dcid={:?}\", self.dcid)?;\n\n        if self.ty != Type::Short {\n            write!(f, \" scid={:?}\", self.scid)?;\n        }\n\n        if let Some(ref token) = self.token {\n            write!(f, \" token=\")?;\n            for b in token {\n                write!(f, \"{b:02x}\")?;\n            }\n        }\n\n        if let Some(ref versions) = self.versions {\n            write!(f, \" versions={versions:x?}\")?;\n        }\n\n        if self.ty == Type::Short {\n            write!(f, \" key_phase={}\", self.key_phase)?;\n        }\n\n        Ok(())\n    }\n}\n\npub fn pkt_num_len(pn: u64, largest_acked: u64) -> usize {\n    let num_unacked: u64 = pn.saturating_sub(largest_acked) + 1;\n    // computes ceil of num_unacked.log2() + 1\n    let min_bits = u64::BITS - num_unacked.leading_zeros() + 1;\n    // get the num len in bytes\n    min_bits.div_ceil(8) as usize\n}\n\npub fn decrypt_hdr(\n    b: &mut octets::OctetsMut, hdr: &mut Header, aead: &crypto::Open,\n) -> Result<()> {\n    let mut first = {\n        let (first_buf, _) = b.split_at(1)?;\n        first_buf.as_ref()[0]\n    };\n\n    let mut pn_and_sample = b.peek_bytes_mut(MAX_PKT_NUM_LEN + SAMPLE_LEN)?;\n\n    let (mut ciphertext, sample) = pn_and_sample.split_at(MAX_PKT_NUM_LEN)?;\n\n    let ciphertext = ciphertext.as_mut();\n\n    let mask = aead.new_mask(sample.as_ref())?;\n\n    if Header::is_long(first) {\n        first ^= mask[0] & 0x0f;\n    } else {\n        first ^= mask[0] & 0x1f;\n    }\n\n    let pn_len = usize::from((first & PKT_NUM_MASK) + 1);\n\n    let ciphertext = &mut ciphertext[..pn_len];\n\n    for i in 0..pn_len {\n        ciphertext[i] ^= mask[i + 1];\n    }\n\n    // Extract packet number corresponding to the decoded length.\n    let pn = match pn_len {\n        1 => u64::from(b.get_u8()?),\n\n        2 => u64::from(b.get_u16()?),\n\n        3 => u64::from(b.get_u24()?),\n\n        4 => u64::from(b.get_u32()?),\n\n        _ => return Err(Error::InvalidPacket),\n    };\n\n    // Write decrypted first byte back into the input buffer.\n    let (mut first_buf, _) = b.split_at(1)?;\n    first_buf.as_mut()[0] = first;\n\n    hdr.pkt_num = pn;\n    hdr.pkt_num_len = pn_len;\n\n    if hdr.ty == Type::Short {\n        hdr.key_phase = (first & KEY_PHASE_BIT) != 0;\n    }\n\n    Ok(())\n}\n\npub fn decode_pkt_num(largest_pn: u64, truncated_pn: u64, pn_len: usize) -> u64 {\n    let pn_nbits = pn_len * 8;\n    let expected_pn = largest_pn + 1;\n    let pn_win = 1 << pn_nbits;\n    let pn_hwin = pn_win / 2;\n    let pn_mask = pn_win - 1;\n    let candidate_pn = (expected_pn & !pn_mask) | truncated_pn;\n\n    if candidate_pn + pn_hwin <= expected_pn && candidate_pn < (1 << 62) - pn_win\n    {\n        return candidate_pn + pn_win;\n    }\n\n    if candidate_pn > expected_pn + pn_hwin && candidate_pn >= pn_win {\n        return candidate_pn - pn_win;\n    }\n\n    candidate_pn\n}\n\npub fn decrypt_pkt<'a>(\n    b: &'a mut octets::OctetsMut, pn: u64, pn_len: usize, payload_len: usize,\n    aead: &crypto::Open,\n) -> Result<octets::Octets<'a>> {\n    let payload_offset = b.off();\n\n    let (header, mut payload) = b.split_at(payload_offset)?;\n\n    let payload_len = payload_len\n        .checked_sub(pn_len)\n        .ok_or(Error::InvalidPacket)?;\n\n    let mut ciphertext = payload.peek_bytes_mut(payload_len)?;\n\n    let payload_len =\n        aead.open_with_u64_counter(pn, header.as_ref(), ciphertext.as_mut())?;\n\n    Ok(b.get_bytes(payload_len)?)\n}\n\npub fn encrypt_hdr(\n    b: &mut octets::OctetsMut, pn_len: usize, payload: &[u8], aead: &crypto::Seal,\n) -> Result<()> {\n    let sample = &payload\n        [MAX_PKT_NUM_LEN - pn_len..SAMPLE_LEN + (MAX_PKT_NUM_LEN - pn_len)];\n\n    let mask = aead.new_mask(sample)?;\n\n    let (mut first, mut rest) = b.split_at(1)?;\n\n    let first = first.as_mut();\n\n    if Header::is_long(first[0]) {\n        first[0] ^= mask[0] & 0x0f;\n    } else {\n        first[0] ^= mask[0] & 0x1f;\n    }\n\n    let pn_buf = rest.slice_last(pn_len)?;\n    for i in 0..pn_len {\n        pn_buf[i] ^= mask[i + 1];\n    }\n\n    Ok(())\n}\n\npub fn encrypt_pkt(\n    b: &mut octets::OctetsMut, pn: u64, pn_len: usize, payload_len: usize,\n    payload_offset: usize, extra_in: Option<&[u8]>, aead: &mut crypto::Seal,\n) -> Result<usize> {\n    let (mut header, mut payload) = b.split_at(payload_offset)?;\n\n    let ciphertext_len = aead.seal_with_u64_counter(\n        pn,\n        header.as_ref(),\n        payload.as_mut(),\n        payload_len,\n        extra_in,\n    )?;\n\n    encrypt_hdr(&mut header, pn_len, payload.as_ref(), aead)?;\n\n    Ok(payload_offset + ciphertext_len)\n}\n\npub fn encode_pkt_num(\n    pn: u64, pn_len: usize, b: &mut octets::OctetsMut,\n) -> Result<()> {\n    match pn_len {\n        1 => b.put_u8(pn as u8)?,\n\n        2 => b.put_u16(pn as u16)?,\n\n        3 => b.put_u24(pn as u32)?,\n\n        4 => b.put_u32(pn as u32)?,\n\n        _ => return Err(Error::InvalidPacket),\n    };\n\n    Ok(())\n}\n\npub fn negotiate_version(\n    scid: &[u8], dcid: &[u8], out: &mut [u8],\n) -> Result<usize> {\n    let mut b = octets::OctetsMut::with_slice(out);\n\n    let first = rand::rand_u8() | FORM_BIT;\n\n    b.put_u8(first)?;\n    b.put_u32(0)?;\n\n    b.put_u8(scid.len() as u8)?;\n    b.put_bytes(scid)?;\n    b.put_u8(dcid.len() as u8)?;\n    b.put_bytes(dcid)?;\n    b.put_u32(crate::PROTOCOL_VERSION_V1)?;\n\n    Ok(b.off())\n}\n\npub fn retry(\n    scid: &[u8], dcid: &[u8], new_scid: &[u8], token: &[u8], version: u32,\n    out: &mut [u8],\n) -> Result<usize> {\n    let mut b = octets::OctetsMut::with_slice(out);\n\n    if !crate::version_is_supported(version) {\n        return Err(Error::UnknownVersion);\n    }\n\n    let hdr = Header {\n        ty: Type::Retry,\n        version,\n        dcid: ConnectionId::from_ref(scid),\n        scid: ConnectionId::from_ref(new_scid),\n        pkt_num: 0,\n        pkt_num_len: 0,\n        token: Some(token.to_vec()),\n        versions: None,\n        key_phase: false,\n    };\n\n    hdr.to_bytes(&mut b)?;\n\n    let tag = compute_retry_integrity_tag(&b, dcid, version)?;\n\n    b.put_bytes(tag.as_ref())?;\n\n    Ok(b.off())\n}\n\npub fn verify_retry_integrity(\n    b: &octets::OctetsMut, odcid: &[u8], version: u32,\n) -> Result<()> {\n    const TAG_LEN: usize = RETRY_AEAD_ALG.tag_len();\n\n    let tag = compute_retry_integrity_tag(b, odcid, version)?;\n\n    crypto::verify_slices_are_equal(&b.as_ref()[..TAG_LEN], tag.as_ref())\n}\n\nfn compute_retry_integrity_tag(\n    b: &octets::OctetsMut, odcid: &[u8], version: u32,\n) -> Result<Vec<u8>> {\n    const KEY_LEN: usize = RETRY_AEAD_ALG.key_len();\n    const TAG_LEN: usize = RETRY_AEAD_ALG.tag_len();\n\n    const RETRY_INTEGRITY_KEY_V1: [u8; KEY_LEN] = [\n        0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54,\n        0xe3, 0x68, 0xc8, 0x4e,\n    ];\n\n    const RETRY_INTEGRITY_NONCE_V1: [u8; crypto::MAX_NONCE_LEN] = [\n        0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,\n    ];\n\n    let (key, nonce) = match version {\n        crate::PROTOCOL_VERSION_V1 =>\n            (&RETRY_INTEGRITY_KEY_V1, RETRY_INTEGRITY_NONCE_V1),\n\n        _ => (&RETRY_INTEGRITY_KEY_V1, RETRY_INTEGRITY_NONCE_V1),\n    };\n\n    let hdr_len = b.off();\n\n    let mut pseudo = vec![0; 1 + odcid.len() + hdr_len];\n\n    let mut pb = octets::OctetsMut::with_slice(&mut pseudo);\n\n    pb.put_u8(odcid.len() as u8)?;\n    pb.put_bytes(odcid)?;\n    pb.put_bytes(&b.buf()[..hdr_len])?;\n\n    let mut key = crypto::PacketKey::new(\n        RETRY_AEAD_ALG,\n        key.to_vec(),\n        nonce.to_vec(),\n        crypto::Seal::ENCRYPT,\n    )?;\n\n    let mut out_tag = vec![0_u8; TAG_LEN];\n\n    let out_len = key.seal_with_u64_counter(0, &pseudo, &mut out_tag, 0, None)?;\n\n    // Ensure that the output only contains the AEAD tag.\n    if out_len != out_tag.len() {\n        return Err(Error::CryptoFail);\n    }\n\n    Ok(out_tag)\n}\n\npub struct KeyUpdate {\n    /// 1-RTT key used prior to a key update.\n    pub crypto_open: crypto::Open,\n\n    /// The packet number triggered the latest key-update.\n    ///\n    /// Incoming packets with lower pn should use this (prev) crypto key.\n    pub pn_on_update: u64,\n\n    /// Whether ACK frame for key-update has been sent.\n    pub update_acked: bool,\n\n    /// When the old key should be discarded.\n    pub timer: Instant,\n}\n\npub struct PktNumSpace {\n    /// The largest packet number received.\n    pub largest_rx_pkt_num: u64,\n\n    /// Time the largest packet number received.\n    pub largest_rx_pkt_time: Instant,\n\n    /// The largest non-probing packet number.\n    pub largest_rx_non_probing_pkt_num: u64,\n\n    /// The largest packet number send in the packet number space so far.\n    pub largest_tx_pkt_num: Option<u64>,\n\n    /// Range of packet numbers that we need to send an ACK for.\n    pub recv_pkt_need_ack: ranges::RangeSet,\n\n    /// Tracks received packet numbers.\n    pub recv_pkt_num: PktNumWindow,\n\n    /// Track if a received packet is ack eliciting.\n    pub ack_elicited: bool,\n}\n\nimpl PktNumSpace {\n    pub fn new() -> PktNumSpace {\n        PktNumSpace {\n            largest_rx_pkt_num: 0,\n            largest_rx_pkt_time: Instant::now(),\n            largest_rx_non_probing_pkt_num: 0,\n            largest_tx_pkt_num: None,\n            recv_pkt_need_ack: ranges::RangeSet::new(crate::MAX_ACK_RANGES),\n            recv_pkt_num: PktNumWindow::default(),\n            ack_elicited: false,\n        }\n    }\n\n    pub fn clear(&mut self) {\n        self.ack_elicited = false;\n    }\n\n    pub fn ready(&self) -> bool {\n        self.ack_elicited\n    }\n\n    pub fn on_packet_sent(&mut self, sent_pkt: &recovery::Sent) {\n        // Track the largest packet number sent\n        self.largest_tx_pkt_num =\n            self.largest_tx_pkt_num.max(Some(sent_pkt.pkt_num));\n    }\n}\n\npub struct CryptoContext {\n    pub key_update: Option<KeyUpdate>,\n    pub crypto_open: Option<crypto::Open>,\n    pub crypto_seal: Option<crypto::Seal>,\n    pub crypto_0rtt_open: Option<crypto::Open>,\n    pub crypto_stream: stream::Stream,\n}\n\nimpl CryptoContext {\n    pub fn new() -> CryptoContext {\n        let crypto_stream = stream::Stream::new(\n            0, // dummy\n            u64::MAX,\n            u64::MAX,\n            true,\n            true,\n            stream::MAX_STREAM_WINDOW,\n        );\n        CryptoContext {\n            key_update: None,\n            crypto_open: None,\n            crypto_seal: None,\n            crypto_0rtt_open: None,\n            crypto_stream,\n        }\n    }\n\n    pub fn clear(&mut self) {\n        self.crypto_open = None;\n        self.crypto_seal = None;\n        self.crypto_stream = <stream::Stream>::new(\n            0, // dummy\n            u64::MAX,\n            u64::MAX,\n            true,\n            true,\n            stream::MAX_STREAM_WINDOW,\n        );\n    }\n\n    pub fn data_available(&self) -> bool {\n        self.crypto_stream.is_flushable()\n    }\n\n    pub fn crypto_overhead(&self) -> Option<usize> {\n        Some(self.crypto_seal.as_ref()?.alg().tag_len())\n    }\n\n    pub fn has_keys(&self) -> bool {\n        self.crypto_open.is_some() && self.crypto_seal.is_some()\n    }\n}\n\n/// QUIC recommends skipping packet numbers to elicit a [faster ACK] or to\n/// mitigate an [optimistic ACK attack] (OACK attack). quiche currently skips\n/// packets only for the purposes of optimistic attack mitigation.\n///\n/// ## What is an Optimistic ACK attack\n/// A typical endpoint is responsible for making concurrent progress on multiple\n/// connections and needs to fairly allocate resources across those connection.\n/// In order to ensure fairness, an endpoint relies on \"recovery signals\" to\n/// determine the optimal sending rate per connection. For example, when a new\n/// flow joins a shared network, it might induce packet loss for other flows and\n/// cause those flows to yield bandwidth on the network. ACKs are the primary\n/// source of recovery signals for a QUIC connection.\n///\n/// The goal of an OACK attack is to mount a DDoS attack by exploiting recovery\n/// signals and causing a server to expand its sending rate. A server with an\n/// inflated sending rate would then be able to flood a shared network and\n/// cripple all other flows. The fundamental reason that makes OACK\n/// attach possible is the use of unvalidated ACK data, which is then used to\n/// modify internal state. Therefore at a high level, a mitigation should\n/// validate the incoming ACK data before use.\n///\n/// ## Optimistic ACK attack mitigation\n/// quiche follows the RFC's recommendation for mitigating an [optimistic ACK\n/// attack] by skipping packets and validating that the peer does NOT send ACKs\n/// for those skipped packets. If an ACK for a skipped packet is received, the\n/// connection is closed with a [PROTOCOL_VIOLATION] error.\n///\n/// A robust mitigation should skip packets randomly to ensure that an attacker\n/// can't predict which packet number was skipped. Skip/validation should happen\n/// \"periodically\" over the lifetime of the connection. Since skipping packets\n/// also elicits a faster ACK, we need to balance the skip frequency to\n/// sufficiently validate the peer without impacting other aspects of recovery.\n///\n/// A naive approach could be to skip a random packet number in the range\n/// 200-500 (pick some static range). While this might work, its not apparent if\n/// a static range is effective for all networks with varying bandwidths/RTTs.\n///\n/// Since an attacker can potentially influence the sending rate once per\n/// \"round\", it would be ideal to validate the peer once per round. Therefore,\n/// an ideal range seems to be one that dynamically adjusts based on packets\n/// sent per round, ie. adjust skip range based on the current CWND.\n///\n/// [faster ACK]: https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.4\n/// [optimistic ACK attack]: https://www.rfc-editor.org/rfc/rfc9000.html#section-21.4\n/// [PROTOCOL_VIOLATION]: https://www.rfc-editor.org/rfc/rfc9000#section-13.1\npub struct PktNumManager {\n    // TODO:\n    // Defer including next_pkt_num in order to reduce the size of this patch\n    // /// Next packet number.\n    // next_pkt_num: u64,\n    /// Track if we have skipped a packet number.\n    skip_pn: Option<u64>,\n\n    /// Track when to skip the next packet number\n    ///\n    /// None indicates the counter is not armed while Some(0) indicates that the\n    /// counter has expired.\n    pub skip_pn_counter: Option<u64>,\n}\n\nimpl PktNumManager {\n    pub fn new() -> Self {\n        PktNumManager {\n            skip_pn: None,\n            skip_pn_counter: None,\n        }\n    }\n\n    pub fn on_packet_sent(\n        &mut self, cwnd: usize, max_datagram_size: usize,\n        handshake_completed: bool,\n    ) {\n        // Decrement skip_pn_counter for each packet sent\n        if let Some(counter) = &mut self.skip_pn_counter {\n            *counter = counter.saturating_sub(1);\n        } else if self.should_arm_skip_counter(handshake_completed) {\n            self.arm_skip_counter(cwnd, max_datagram_size);\n        }\n    }\n\n    fn should_arm_skip_counter(&self, handshake_completed: bool) -> bool {\n        // Arm if the counter is not set\n        let counter_not_set = self.skip_pn_counter.is_none();\n        // Don't arm until we have verified the current skip_pn. Rearming the\n        // counter could result in overwriting the skip_pn before\n        // validating the current skip_pn.\n        let no_current_skip_packet = self.skip_pn.is_none();\n\n        // Skip pn only after the handshake has completed\n        counter_not_set && no_current_skip_packet && handshake_completed\n    }\n\n    pub fn should_skip_pn(&self, handshake_completed: bool) -> bool {\n        // Only skip a new packet once we have validated  the peer hasn't sent the\n        // current skip packet.  For the purposes of OACK, a skip_pn is\n        // considered validated once we receive an ACK for pn > skip_pn.\n        let no_current_skip_packet = self.skip_pn.is_none();\n        let counter_expired = match self.skip_pn_counter {\n            // Skip if counter has expired\n            Some(counter) => counter == 0,\n            // Don't skip if the counter has not been set\n            None => false,\n        };\n\n        // Skip pn only after the handshake has completed\n        counter_expired && no_current_skip_packet && handshake_completed\n    }\n\n    pub fn skip_pn(&self) -> Option<u64> {\n        self.skip_pn\n    }\n\n    pub fn set_skip_pn(&mut self, skip_pn: Option<u64>) {\n        if skip_pn.is_some() {\n            // Never overwrite skip_pn until the previous one has been verified\n            debug_assert!(self.skip_pn.is_none());\n            // The skip_pn_counter should be expired\n            debug_assert_eq!(self.skip_pn_counter.unwrap(), 0);\n        }\n\n        self.skip_pn = skip_pn;\n        // unset the counter\n        self.skip_pn_counter = None;\n    }\n\n    // Dynamically vary the skip counter based on the CWND.\n    fn arm_skip_counter(&mut self, cwnd: usize, max_datagram_size: usize) {\n        let packets_per_cwnd = (cwnd / max_datagram_size) as u64;\n        let lower = packets_per_cwnd / 2;\n        let upper = packets_per_cwnd * 2;\n        // rand_u64_uniform requires a non-zero value so add 1\n        let skip_range = upper - lower + 1;\n        let rand_skip_value = rand::rand_u64_uniform(skip_range);\n\n        // Skip calculation:\n        // skip_counter = min_skip\n        //                + lower\n        //                + rand(skip_range.lower, skip_range.upper)\n        //\n        //```\n        // c: the current packet number\n        // s: range of random packet number to skip from\n        //\n        // curr_pn\n        //  |\n        //  v                 |--- (upper - lower) ---|\n        // [c x x x x x x x x s s s s s s s s s s s s s x x]\n        //    |--min_skip---| |------skip_range-------|\n        //\n        //```\n        let skip_pn_counter = MIN_SKIP_COUNTER_VALUE + lower + rand_skip_value;\n\n        self.skip_pn_counter = Some(skip_pn_counter);\n    }\n}\n\n#[derive(Clone, Copy, Default)]\npub struct PktNumWindow {\n    lower: u64,\n    window: u128,\n}\n\nimpl PktNumWindow {\n    pub fn insert(&mut self, seq: u64) {\n        // Packet is on the left end of the window.\n        if seq < self.lower {\n            return;\n        }\n\n        // Packet is on the right end of the window.\n        if seq > self.upper() {\n            let diff = seq - self.upper();\n            self.lower += diff;\n\n            self.window = self.window.checked_shl(diff as u32).unwrap_or(0);\n        }\n\n        let mask = 1_u128 << (self.upper() - seq);\n        self.window |= mask;\n    }\n\n    pub fn contains(&mut self, seq: u64) -> bool {\n        // Packet is on the right end of the window.\n        if seq > self.upper() {\n            return false;\n        }\n\n        // Packet is on the left end of the window.\n        if seq < self.lower {\n            return true;\n        }\n\n        let mask = 1_u128 << (self.upper() - seq);\n        self.window & mask != 0\n    }\n\n    fn upper(&self) -> u64 {\n        self.lower.saturating_add(size_of::<u128>() as u64 * 8) - 1\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::test_utils;\n    use crate::MAX_SEND_UDP_PAYLOAD_SIZE;\n\n    #[test]\n    fn retry() {\n        let hdr = Header {\n            ty: Type::Retry,\n            version: 0xafafafaf,\n            dcid: vec![0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba]\n                .into(),\n            scid: vec![0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb].into(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: Some(vec![0xba; 24]),\n            versions: None,\n            key_phase: false,\n        };\n\n        let mut d = [0; 63];\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert!(hdr.to_bytes(&mut b).is_ok());\n\n        // Add fake retry integrity token.\n        b.put_bytes(&[0xba; 16]).unwrap();\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert_eq!(Header::from_bytes(&mut b, 9).unwrap(), hdr);\n    }\n\n    #[test]\n    fn initial() {\n        let hdr = Header {\n            ty: Type::Initial,\n            version: 0xafafafaf,\n            dcid: vec![0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba]\n                .into(),\n            scid: vec![0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb].into(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: Some(vec![0x05, 0x06, 0x07, 0x08]),\n            versions: None,\n            key_phase: false,\n        };\n\n        let mut d = [0; 50];\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert!(hdr.to_bytes(&mut b).is_ok());\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert_eq!(Header::from_bytes(&mut b, 9).unwrap(), hdr);\n    }\n\n    #[test]\n    fn initial_v1_dcid_too_long() {\n        let hdr = Header {\n            ty: Type::Initial,\n            version: crate::PROTOCOL_VERSION,\n            dcid: vec![\n                0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,\n                0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,\n            ]\n            .into(),\n            scid: vec![0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb].into(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: Some(vec![0x05, 0x06, 0x07, 0x08]),\n            versions: None,\n            key_phase: false,\n        };\n\n        let mut d = [0; 50];\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert!(hdr.to_bytes(&mut b).is_ok());\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert_eq!(Header::from_bytes(&mut b, 21), Err(Error::InvalidPacket));\n    }\n\n    #[test]\n    fn initial_v1_scid_too_long() {\n        let hdr = Header {\n            ty: Type::Initial,\n            version: crate::PROTOCOL_VERSION,\n            dcid: vec![0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba]\n                .into(),\n            scid: vec![\n                0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,\n                0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,\n            ]\n            .into(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: Some(vec![0x05, 0x06, 0x07, 0x08]),\n            versions: None,\n            key_phase: false,\n        };\n\n        let mut d = [0; 50];\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert!(hdr.to_bytes(&mut b).is_ok());\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert_eq!(Header::from_bytes(&mut b, 9), Err(Error::InvalidPacket));\n    }\n\n    #[test]\n    fn initial_non_v1_scid_long() {\n        let hdr = Header {\n            ty: Type::Initial,\n            version: 0xafafafaf,\n            dcid: vec![0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba]\n                .into(),\n            scid: vec![\n                0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,\n                0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,\n            ]\n            .into(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: Some(vec![0x05, 0x06, 0x07, 0x08]),\n            versions: None,\n            key_phase: false,\n        };\n\n        let mut d = [0; 50];\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert!(hdr.to_bytes(&mut b).is_ok());\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert_eq!(Header::from_bytes(&mut b, 9).unwrap(), hdr);\n    }\n\n    #[test]\n    fn handshake() {\n        let hdr = Header {\n            ty: Type::Handshake,\n            version: 0xafafafaf,\n            dcid: vec![0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba]\n                .into(),\n            scid: vec![0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb].into(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: None,\n            versions: None,\n            key_phase: false,\n        };\n\n        let mut d = [0; 50];\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert!(hdr.to_bytes(&mut b).is_ok());\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert_eq!(Header::from_bytes(&mut b, 9).unwrap(), hdr);\n    }\n\n    #[test]\n    fn application() {\n        let hdr = Header {\n            ty: Type::Short,\n            version: 0,\n            dcid: vec![0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba]\n                .into(),\n            scid: ConnectionId::default(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: None,\n            versions: None,\n            key_phase: false,\n        };\n\n        let mut d = [0; 50];\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert!(hdr.to_bytes(&mut b).is_ok());\n\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        assert_eq!(Header::from_bytes(&mut b, 9).unwrap(), hdr);\n    }\n\n    #[test]\n    fn pkt_num_encode_decode() {\n        let num_len = pkt_num_len(0, 0);\n        assert_eq!(num_len, 1);\n        let pn = decode_pkt_num(0xa82f30ea, 0x9b32, 2);\n        assert_eq!(pn, 0xa82f9b32);\n        let mut d = [0; 10];\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        let num_len = pkt_num_len(0xac5c02, 0xabe8b3);\n        assert_eq!(num_len, 2);\n        encode_pkt_num(0xac5c02, num_len, &mut b).unwrap();\n        // reading\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        let hdr_num = u64::from(b.get_u16().unwrap());\n        let pn = decode_pkt_num(0xac5c01, hdr_num, num_len);\n        assert_eq!(pn, 0xac5c02);\n        // sending 0xace8fe while having 0xabe8b3 acked\n        let num_len = pkt_num_len(0xace9fe, 0xabe8b3);\n        assert_eq!(num_len, 3);\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        encode_pkt_num(0xace9fe, num_len, &mut b).unwrap();\n        // reading\n        let mut b = octets::OctetsMut::with_slice(&mut d);\n        let hdr_num = u64::from(b.get_u24().unwrap());\n        let pn = decode_pkt_num(0xace9fa, hdr_num, num_len);\n        assert_eq!(pn, 0xace9fe);\n        // roundtrip\n        let base = 0xdeadbeef;\n        for i in 1..255 {\n            let pn = base + i;\n            let num_len = pkt_num_len(pn, base);\n            if num_len == 1 {\n                let decoded = decode_pkt_num(base, pn & 0xff, num_len);\n                assert_eq!(decoded, pn);\n            } else {\n                assert_eq!(num_len, 2);\n                let decoded = decode_pkt_num(base, pn & 0xffff, num_len);\n                assert_eq!(decoded, pn);\n            }\n        }\n    }\n\n    #[test]\n    fn pkt_num_window() {\n        let mut win = PktNumWindow::default();\n        assert_eq!(win.lower, 0);\n        assert!(!win.contains(0));\n        assert!(!win.contains(1));\n\n        win.insert(0);\n        assert_eq!(win.lower, 0);\n        assert!(win.contains(0));\n        assert!(!win.contains(1));\n\n        win.insert(1);\n        assert_eq!(win.lower, 0);\n        assert!(win.contains(0));\n        assert!(win.contains(1));\n\n        win.insert(3);\n        assert_eq!(win.lower, 0);\n        assert!(win.contains(0));\n        assert!(win.contains(1));\n        assert!(!win.contains(2));\n        assert!(win.contains(3));\n\n        win.insert(10);\n        assert_eq!(win.lower, 0);\n        assert!(win.contains(0));\n        assert!(win.contains(1));\n        assert!(!win.contains(2));\n        assert!(win.contains(3));\n        assert!(!win.contains(4));\n        assert!(!win.contains(5));\n        assert!(!win.contains(6));\n        assert!(!win.contains(7));\n        assert!(!win.contains(8));\n        assert!(!win.contains(9));\n        assert!(win.contains(10));\n\n        win.insert(132);\n        assert_eq!(win.lower, 5);\n        assert!(win.contains(0));\n        assert!(win.contains(1));\n        assert!(win.contains(2));\n        assert!(win.contains(3));\n        assert!(win.contains(4));\n        assert!(!win.contains(5));\n        assert!(!win.contains(6));\n        assert!(!win.contains(7));\n        assert!(!win.contains(8));\n        assert!(!win.contains(9));\n        assert!(win.contains(10));\n        assert!(!win.contains(128));\n        assert!(!win.contains(130));\n        assert!(!win.contains(131));\n        assert!(win.contains(132));\n\n        win.insert(1024);\n        assert_eq!(win.lower, 897);\n        assert!(win.contains(0));\n        assert!(win.contains(1));\n        assert!(win.contains(2));\n        assert!(win.contains(3));\n        assert!(win.contains(4));\n        assert!(win.contains(5));\n        assert!(win.contains(6));\n        assert!(win.contains(7));\n        assert!(win.contains(8));\n        assert!(win.contains(9));\n        assert!(win.contains(10));\n        assert!(win.contains(128));\n        assert!(win.contains(130));\n        assert!(win.contains(132));\n        assert!(win.contains(896));\n        assert!(!win.contains(897));\n        assert!(!win.contains(1022));\n        assert!(!win.contains(1023));\n        assert!(win.contains(1024));\n        assert!(!win.contains(1025));\n        assert!(!win.contains(1026));\n\n        win.insert(u64::MAX - 1);\n        assert!(win.contains(0));\n        assert!(win.contains(1));\n        assert!(win.contains(2));\n        assert!(win.contains(3));\n        assert!(win.contains(4));\n        assert!(win.contains(5));\n        assert!(win.contains(6));\n        assert!(win.contains(7));\n        assert!(win.contains(8));\n        assert!(win.contains(9));\n        assert!(win.contains(10));\n        assert!(win.contains(128));\n        assert!(win.contains(130));\n        assert!(win.contains(132));\n        assert!(win.contains(896));\n        assert!(win.contains(897));\n        assert!(win.contains(1022));\n        assert!(win.contains(1023));\n        assert!(win.contains(1024));\n        assert!(win.contains(1025));\n        assert!(win.contains(1026));\n        assert!(!win.contains(u64::MAX - 2));\n        assert!(win.contains(u64::MAX - 1));\n    }\n\n    fn assert_decrypt_initial_pkt(\n        pkt: &mut [u8], dcid: &[u8], is_server: bool, expected_frames: &[u8],\n        expected_pn: u64, expected_pn_len: usize,\n    ) {\n        let mut b = octets::OctetsMut::with_slice(pkt);\n\n        let mut hdr = Header::from_bytes(&mut b, 0).unwrap();\n        assert_eq!(hdr.ty, Type::Initial);\n\n        let payload_len = b.get_varint().unwrap() as usize;\n\n        let (aead, _) = crypto::derive_initial_key_material(\n            dcid,\n            hdr.version,\n            is_server,\n            false,\n        )\n        .unwrap();\n\n        decrypt_hdr(&mut b, &mut hdr, &aead).unwrap();\n        assert_eq!(hdr.pkt_num_len, expected_pn_len);\n\n        let pn = decode_pkt_num(0, hdr.pkt_num, hdr.pkt_num_len);\n        assert_eq!(pn, expected_pn);\n\n        let payload =\n            decrypt_pkt(&mut b, pn, hdr.pkt_num_len, payload_len, &aead).unwrap();\n\n        let payload = payload.as_ref();\n        assert_eq!(&payload[..expected_frames.len()], expected_frames);\n    }\n\n    #[test]\n    fn decrypt_client_initial_v1() {\n        let mut pkt = [\n            0xc0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e,\n            0x51, 0x57, 0x08, 0x00, 0x00, 0x44, 0x9e, 0x7b, 0x9a, 0xec, 0x34,\n            0xd1, 0xb1, 0xc9, 0x8d, 0xd7, 0x68, 0x9f, 0xb8, 0xec, 0x11, 0xd2,\n            0x42, 0xb1, 0x23, 0xdc, 0x9b, 0xd8, 0xba, 0xb9, 0x36, 0xb4, 0x7d,\n            0x92, 0xec, 0x35, 0x6c, 0x0b, 0xab, 0x7d, 0xf5, 0x97, 0x6d, 0x27,\n            0xcd, 0x44, 0x9f, 0x63, 0x30, 0x00, 0x99, 0xf3, 0x99, 0x1c, 0x26,\n            0x0e, 0xc4, 0xc6, 0x0d, 0x17, 0xb3, 0x1f, 0x84, 0x29, 0x15, 0x7b,\n            0xb3, 0x5a, 0x12, 0x82, 0xa6, 0x43, 0xa8, 0xd2, 0x26, 0x2c, 0xad,\n            0x67, 0x50, 0x0c, 0xad, 0xb8, 0xe7, 0x37, 0x8c, 0x8e, 0xb7, 0x53,\n            0x9e, 0xc4, 0xd4, 0x90, 0x5f, 0xed, 0x1b, 0xee, 0x1f, 0xc8, 0xaa,\n            0xfb, 0xa1, 0x7c, 0x75, 0x0e, 0x2c, 0x7a, 0xce, 0x01, 0xe6, 0x00,\n            0x5f, 0x80, 0xfc, 0xb7, 0xdf, 0x62, 0x12, 0x30, 0xc8, 0x37, 0x11,\n            0xb3, 0x93, 0x43, 0xfa, 0x02, 0x8c, 0xea, 0x7f, 0x7f, 0xb5, 0xff,\n            0x89, 0xea, 0xc2, 0x30, 0x82, 0x49, 0xa0, 0x22, 0x52, 0x15, 0x5e,\n            0x23, 0x47, 0xb6, 0x3d, 0x58, 0xc5, 0x45, 0x7a, 0xfd, 0x84, 0xd0,\n            0x5d, 0xff, 0xfd, 0xb2, 0x03, 0x92, 0x84, 0x4a, 0xe8, 0x12, 0x15,\n            0x46, 0x82, 0xe9, 0xcf, 0x01, 0x2f, 0x90, 0x21, 0xa6, 0xf0, 0xbe,\n            0x17, 0xdd, 0xd0, 0xc2, 0x08, 0x4d, 0xce, 0x25, 0xff, 0x9b, 0x06,\n            0xcd, 0xe5, 0x35, 0xd0, 0xf9, 0x20, 0xa2, 0xdb, 0x1b, 0xf3, 0x62,\n            0xc2, 0x3e, 0x59, 0x6d, 0xee, 0x38, 0xf5, 0xa6, 0xcf, 0x39, 0x48,\n            0x83, 0x8a, 0x3a, 0xec, 0x4e, 0x15, 0xda, 0xf8, 0x50, 0x0a, 0x6e,\n            0xf6, 0x9e, 0xc4, 0xe3, 0xfe, 0xb6, 0xb1, 0xd9, 0x8e, 0x61, 0x0a,\n            0xc8, 0xb7, 0xec, 0x3f, 0xaf, 0x6a, 0xd7, 0x60, 0xb7, 0xba, 0xd1,\n            0xdb, 0x4b, 0xa3, 0x48, 0x5e, 0x8a, 0x94, 0xdc, 0x25, 0x0a, 0xe3,\n            0xfd, 0xb4, 0x1e, 0xd1, 0x5f, 0xb6, 0xa8, 0xe5, 0xeb, 0xa0, 0xfc,\n            0x3d, 0xd6, 0x0b, 0xc8, 0xe3, 0x0c, 0x5c, 0x42, 0x87, 0xe5, 0x38,\n            0x05, 0xdb, 0x05, 0x9a, 0xe0, 0x64, 0x8d, 0xb2, 0xf6, 0x42, 0x64,\n            0xed, 0x5e, 0x39, 0xbe, 0x2e, 0x20, 0xd8, 0x2d, 0xf5, 0x66, 0xda,\n            0x8d, 0xd5, 0x99, 0x8c, 0xca, 0xbd, 0xae, 0x05, 0x30, 0x60, 0xae,\n            0x6c, 0x7b, 0x43, 0x78, 0xe8, 0x46, 0xd2, 0x9f, 0x37, 0xed, 0x7b,\n            0x4e, 0xa9, 0xec, 0x5d, 0x82, 0xe7, 0x96, 0x1b, 0x7f, 0x25, 0xa9,\n            0x32, 0x38, 0x51, 0xf6, 0x81, 0xd5, 0x82, 0x36, 0x3a, 0xa5, 0xf8,\n            0x99, 0x37, 0xf5, 0xa6, 0x72, 0x58, 0xbf, 0x63, 0xad, 0x6f, 0x1a,\n            0x0b, 0x1d, 0x96, 0xdb, 0xd4, 0xfa, 0xdd, 0xfc, 0xef, 0xc5, 0x26,\n            0x6b, 0xa6, 0x61, 0x17, 0x22, 0x39, 0x5c, 0x90, 0x65, 0x56, 0xbe,\n            0x52, 0xaf, 0xe3, 0xf5, 0x65, 0x63, 0x6a, 0xd1, 0xb1, 0x7d, 0x50,\n            0x8b, 0x73, 0xd8, 0x74, 0x3e, 0xeb, 0x52, 0x4b, 0xe2, 0x2b, 0x3d,\n            0xcb, 0xc2, 0xc7, 0x46, 0x8d, 0x54, 0x11, 0x9c, 0x74, 0x68, 0x44,\n            0x9a, 0x13, 0xd8, 0xe3, 0xb9, 0x58, 0x11, 0xa1, 0x98, 0xf3, 0x49,\n            0x1d, 0xe3, 0xe7, 0xfe, 0x94, 0x2b, 0x33, 0x04, 0x07, 0xab, 0xf8,\n            0x2a, 0x4e, 0xd7, 0xc1, 0xb3, 0x11, 0x66, 0x3a, 0xc6, 0x98, 0x90,\n            0xf4, 0x15, 0x70, 0x15, 0x85, 0x3d, 0x91, 0xe9, 0x23, 0x03, 0x7c,\n            0x22, 0x7a, 0x33, 0xcd, 0xd5, 0xec, 0x28, 0x1c, 0xa3, 0xf7, 0x9c,\n            0x44, 0x54, 0x6b, 0x9d, 0x90, 0xca, 0x00, 0xf0, 0x64, 0xc9, 0x9e,\n            0x3d, 0xd9, 0x79, 0x11, 0xd3, 0x9f, 0xe9, 0xc5, 0xd0, 0xb2, 0x3a,\n            0x22, 0x9a, 0x23, 0x4c, 0xb3, 0x61, 0x86, 0xc4, 0x81, 0x9e, 0x8b,\n            0x9c, 0x59, 0x27, 0x72, 0x66, 0x32, 0x29, 0x1d, 0x6a, 0x41, 0x82,\n            0x11, 0xcc, 0x29, 0x62, 0xe2, 0x0f, 0xe4, 0x7f, 0xeb, 0x3e, 0xdf,\n            0x33, 0x0f, 0x2c, 0x60, 0x3a, 0x9d, 0x48, 0xc0, 0xfc, 0xb5, 0x69,\n            0x9d, 0xbf, 0xe5, 0x89, 0x64, 0x25, 0xc5, 0xba, 0xc4, 0xae, 0xe8,\n            0x2e, 0x57, 0xa8, 0x5a, 0xaf, 0x4e, 0x25, 0x13, 0xe4, 0xf0, 0x57,\n            0x96, 0xb0, 0x7b, 0xa2, 0xee, 0x47, 0xd8, 0x05, 0x06, 0xf8, 0xd2,\n            0xc2, 0x5e, 0x50, 0xfd, 0x14, 0xde, 0x71, 0xe6, 0xc4, 0x18, 0x55,\n            0x93, 0x02, 0xf9, 0x39, 0xb0, 0xe1, 0xab, 0xd5, 0x76, 0xf2, 0x79,\n            0xc4, 0xb2, 0xe0, 0xfe, 0xb8, 0x5c, 0x1f, 0x28, 0xff, 0x18, 0xf5,\n            0x88, 0x91, 0xff, 0xef, 0x13, 0x2e, 0xef, 0x2f, 0xa0, 0x93, 0x46,\n            0xae, 0xe3, 0x3c, 0x28, 0xeb, 0x13, 0x0f, 0xf2, 0x8f, 0x5b, 0x76,\n            0x69, 0x53, 0x33, 0x41, 0x13, 0x21, 0x19, 0x96, 0xd2, 0x00, 0x11,\n            0xa1, 0x98, 0xe3, 0xfc, 0x43, 0x3f, 0x9f, 0x25, 0x41, 0x01, 0x0a,\n            0xe1, 0x7c, 0x1b, 0xf2, 0x02, 0x58, 0x0f, 0x60, 0x47, 0x47, 0x2f,\n            0xb3, 0x68, 0x57, 0xfe, 0x84, 0x3b, 0x19, 0xf5, 0x98, 0x40, 0x09,\n            0xdd, 0xc3, 0x24, 0x04, 0x4e, 0x84, 0x7a, 0x4f, 0x4a, 0x0a, 0xb3,\n            0x4f, 0x71, 0x95, 0x95, 0xde, 0x37, 0x25, 0x2d, 0x62, 0x35, 0x36,\n            0x5e, 0x9b, 0x84, 0x39, 0x2b, 0x06, 0x10, 0x85, 0x34, 0x9d, 0x73,\n            0x20, 0x3a, 0x4a, 0x13, 0xe9, 0x6f, 0x54, 0x32, 0xec, 0x0f, 0xd4,\n            0xa1, 0xee, 0x65, 0xac, 0xcd, 0xd5, 0xe3, 0x90, 0x4d, 0xf5, 0x4c,\n            0x1d, 0xa5, 0x10, 0xb0, 0xff, 0x20, 0xdc, 0xc0, 0xc7, 0x7f, 0xcb,\n            0x2c, 0x0e, 0x0e, 0xb6, 0x05, 0xcb, 0x05, 0x04, 0xdb, 0x87, 0x63,\n            0x2c, 0xf3, 0xd8, 0xb4, 0xda, 0xe6, 0xe7, 0x05, 0x76, 0x9d, 0x1d,\n            0xe3, 0x54, 0x27, 0x01, 0x23, 0xcb, 0x11, 0x45, 0x0e, 0xfc, 0x60,\n            0xac, 0x47, 0x68, 0x3d, 0x7b, 0x8d, 0x0f, 0x81, 0x13, 0x65, 0x56,\n            0x5f, 0xd9, 0x8c, 0x4c, 0x8e, 0xb9, 0x36, 0xbc, 0xab, 0x8d, 0x06,\n            0x9f, 0xc3, 0x3b, 0xd8, 0x01, 0xb0, 0x3a, 0xde, 0xa2, 0xe1, 0xfb,\n            0xc5, 0xaa, 0x46, 0x3d, 0x08, 0xca, 0x19, 0x89, 0x6d, 0x2b, 0xf5,\n            0x9a, 0x07, 0x1b, 0x85, 0x1e, 0x6c, 0x23, 0x90, 0x52, 0x17, 0x2f,\n            0x29, 0x6b, 0xfb, 0x5e, 0x72, 0x40, 0x47, 0x90, 0xa2, 0x18, 0x10,\n            0x14, 0xf3, 0xb9, 0x4a, 0x4e, 0x97, 0xd1, 0x17, 0xb4, 0x38, 0x13,\n            0x03, 0x68, 0xcc, 0x39, 0xdb, 0xb2, 0xd1, 0x98, 0x06, 0x5a, 0xe3,\n            0x98, 0x65, 0x47, 0x92, 0x6c, 0xd2, 0x16, 0x2f, 0x40, 0xa2, 0x9f,\n            0x0c, 0x3c, 0x87, 0x45, 0xc0, 0xf5, 0x0f, 0xba, 0x38, 0x52, 0xe5,\n            0x66, 0xd4, 0x45, 0x75, 0xc2, 0x9d, 0x39, 0xa0, 0x3f, 0x0c, 0xda,\n            0x72, 0x19, 0x84, 0xb6, 0xf4, 0x40, 0x59, 0x1f, 0x35, 0x5e, 0x12,\n            0xd4, 0x39, 0xff, 0x15, 0x0a, 0xab, 0x76, 0x13, 0x49, 0x9d, 0xbd,\n            0x49, 0xad, 0xab, 0xc8, 0x67, 0x6e, 0xef, 0x02, 0x3b, 0x15, 0xb6,\n            0x5b, 0xfc, 0x5c, 0xa0, 0x69, 0x48, 0x10, 0x9f, 0x23, 0xf3, 0x50,\n            0xdb, 0x82, 0x12, 0x35, 0x35, 0xeb, 0x8a, 0x74, 0x33, 0xbd, 0xab,\n            0xcb, 0x90, 0x92, 0x71, 0xa6, 0xec, 0xbc, 0xb5, 0x8b, 0x93, 0x6a,\n            0x88, 0xcd, 0x4e, 0x8f, 0x2e, 0x6f, 0xf5, 0x80, 0x01, 0x75, 0xf1,\n            0x13, 0x25, 0x3d, 0x8f, 0xa9, 0xca, 0x88, 0x85, 0xc2, 0xf5, 0x52,\n            0xe6, 0x57, 0xdc, 0x60, 0x3f, 0x25, 0x2e, 0x1a, 0x8e, 0x30, 0x8f,\n            0x76, 0xf0, 0xbe, 0x79, 0xe2, 0xfb, 0x8f, 0x5d, 0x5f, 0xbb, 0xe2,\n            0xe3, 0x0e, 0xca, 0xdd, 0x22, 0x07, 0x23, 0xc8, 0xc0, 0xae, 0xa8,\n            0x07, 0x8c, 0xdf, 0xcb, 0x38, 0x68, 0x26, 0x3f, 0xf8, 0xf0, 0x94,\n            0x00, 0x54, 0xda, 0x48, 0x78, 0x18, 0x93, 0xa7, 0xe4, 0x9a, 0xd5,\n            0xaf, 0xf4, 0xaf, 0x30, 0x0c, 0xd8, 0x04, 0xa6, 0xb6, 0x27, 0x9a,\n            0xb3, 0xff, 0x3a, 0xfb, 0x64, 0x49, 0x1c, 0x85, 0x19, 0x4a, 0xab,\n            0x76, 0x0d, 0x58, 0xa6, 0x06, 0x65, 0x4f, 0x9f, 0x44, 0x00, 0xe8,\n            0xb3, 0x85, 0x91, 0x35, 0x6f, 0xbf, 0x64, 0x25, 0xac, 0xa2, 0x6d,\n            0xc8, 0x52, 0x44, 0x25, 0x9f, 0xf2, 0xb1, 0x9c, 0x41, 0xb9, 0xf9,\n            0x6f, 0x3c, 0xa9, 0xec, 0x1d, 0xde, 0x43, 0x4d, 0xa7, 0xd2, 0xd3,\n            0x92, 0xb9, 0x05, 0xdd, 0xf3, 0xd1, 0xf9, 0xaf, 0x93, 0xd1, 0xaf,\n            0x59, 0x50, 0xbd, 0x49, 0x3f, 0x5a, 0xa7, 0x31, 0xb4, 0x05, 0x6d,\n            0xf3, 0x1b, 0xd2, 0x67, 0xb6, 0xb9, 0x0a, 0x07, 0x98, 0x31, 0xaa,\n            0xf5, 0x79, 0xbe, 0x0a, 0x39, 0x01, 0x31, 0x37, 0xaa, 0xc6, 0xd4,\n            0x04, 0xf5, 0x18, 0xcf, 0xd4, 0x68, 0x40, 0x64, 0x7e, 0x78, 0xbf,\n            0xe7, 0x06, 0xca, 0x4c, 0xf5, 0xe9, 0xc5, 0x45, 0x3e, 0x9f, 0x7c,\n            0xfd, 0x2b, 0x8b, 0x4c, 0x8d, 0x16, 0x9a, 0x44, 0xe5, 0x5c, 0x88,\n            0xd4, 0xa9, 0xa7, 0xf9, 0x47, 0x42, 0x41, 0x10, 0x92, 0xab, 0xbd,\n            0xf8, 0xb8, 0x89, 0xe5, 0xc1, 0x99, 0xd0, 0x96, 0xe3, 0xf2, 0x47,\n            0x88,\n        ];\n\n        let dcid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];\n\n        let frames = [\n            0x06, 0x00, 0x40, 0xf1, 0x01, 0x00, 0x00, 0xed, 0x03, 0x03, 0xeb,\n            0xf8, 0xfa, 0x56, 0xf1, 0x29, 0x39, 0xb9, 0x58, 0x4a, 0x38, 0x96,\n            0x47, 0x2e, 0xc4, 0x0b, 0xb8, 0x63, 0xcf, 0xd3, 0xe8, 0x68, 0x04,\n            0xfe, 0x3a, 0x47, 0xf0, 0x6a, 0x2b, 0x69, 0x48, 0x4c, 0x00, 0x00,\n            0x04, 0x13, 0x01, 0x13, 0x02, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00,\n            0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d,\n            0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0xff, 0x01, 0x00, 0x01,\n            0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17,\n            0x00, 0x18, 0x00, 0x10, 0x00, 0x07, 0x00, 0x05, 0x04, 0x61, 0x6c,\n            0x70, 0x6e, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x93,\n            0x70, 0xb2, 0xc9, 0xca, 0xa4, 0x7f, 0xba, 0xba, 0xf4, 0x55, 0x9f,\n            0xed, 0xba, 0x75, 0x3d, 0xe1, 0x71, 0xfa, 0x71, 0xf5, 0x0f, 0x1c,\n            0xe1, 0x5d, 0x43, 0xe9, 0x94, 0xec, 0x74, 0xd7, 0x48, 0x00, 0x2b,\n            0x00, 0x03, 0x02, 0x03, 0x04, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x0e,\n            0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x02, 0x03, 0x08, 0x04, 0x08,\n            0x05, 0x08, 0x06, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c,\n            0x00, 0x02, 0x40, 0x01, 0xff, 0xa5, 0x00, 0x32, 0x04, 0x08, 0xff,\n            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x05, 0x04, 0x80, 0x00,\n            0xff, 0xff, 0x07, 0x04, 0x80, 0x00, 0xff, 0xff, 0x08, 0x01, 0x10,\n            0x01, 0x04, 0x80, 0x00, 0x75, 0x30, 0x09, 0x01, 0x10, 0x0f, 0x08,\n            0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x06, 0x04, 0x80,\n            0x00, 0xff, 0xff,\n        ];\n\n        assert_decrypt_initial_pkt(&mut pkt, &dcid, true, &frames, 2, 4);\n    }\n\n    #[test]\n    fn decrypt_server_initial_v1() {\n        let mut pkt = [\n            0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50,\n            0x2a, 0x42, 0x62, 0xb5, 0x00, 0x40, 0x75, 0xc0, 0xd9, 0x5a, 0x48,\n            0x2c, 0xd0, 0x99, 0x1c, 0xd2, 0x5b, 0x0a, 0xac, 0x40, 0x6a, 0x58,\n            0x16, 0xb6, 0x39, 0x41, 0x00, 0xf3, 0x7a, 0x1c, 0x69, 0x79, 0x75,\n            0x54, 0x78, 0x0b, 0xb3, 0x8c, 0xc5, 0xa9, 0x9f, 0x5e, 0xde, 0x4c,\n            0xf7, 0x3c, 0x3e, 0xc2, 0x49, 0x3a, 0x18, 0x39, 0xb3, 0xdb, 0xcb,\n            0xa3, 0xf6, 0xea, 0x46, 0xc5, 0xb7, 0x68, 0x4d, 0xf3, 0x54, 0x8e,\n            0x7d, 0xde, 0xb9, 0xc3, 0xbf, 0x9c, 0x73, 0xcc, 0x3f, 0x3b, 0xde,\n            0xd7, 0x4b, 0x56, 0x2b, 0xfb, 0x19, 0xfb, 0x84, 0x02, 0x2f, 0x8e,\n            0xf4, 0xcd, 0xd9, 0x37, 0x95, 0xd7, 0x7d, 0x06, 0xed, 0xbb, 0x7a,\n            0xaf, 0x2f, 0x58, 0x89, 0x18, 0x50, 0xab, 0xbd, 0xca, 0x3d, 0x20,\n            0x39, 0x8c, 0x27, 0x64, 0x56, 0xcb, 0xc4, 0x21, 0x58, 0x40, 0x7d,\n            0xd0, 0x74, 0xee,\n        ];\n\n        let dcid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];\n\n        let frames = [\n            0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00,\n            0x00, 0x56, 0x03, 0x03, 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1,\n            0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78, 0x25, 0xdd, 0xf7, 0x39, 0x88,\n            0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43, 0x0b, 0x9a,\n            0x04, 0x5a, 0x12, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33,\n            0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89,\n            0x69, 0x0b, 0x84, 0xd0, 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca,\n            0x68, 0x4d, 0x10, 0x81, 0x28, 0x7c, 0x83, 0x4d, 0x53, 0x11, 0xbc,\n            0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02, 0x03, 0x04,\n        ];\n\n        assert_decrypt_initial_pkt(&mut pkt, &dcid, false, &frames, 1, 2);\n    }\n\n    #[test]\n    fn decrypt_chacha20() {\n        let secret = [\n            0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42,\n            0x27, 0x48, 0xad, 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0,\n            0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3, 0x0f, 0x21, 0x63, 0x2b,\n        ];\n\n        let mut pkt = [\n            0x4c, 0xfe, 0x41, 0x89, 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6,\n            0x90, 0x80, 0x57, 0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb,\n        ];\n\n        let mut b = octets::OctetsMut::with_slice(&mut pkt);\n\n        let alg = crypto::Algorithm::ChaCha20_Poly1305;\n\n        let aead = crypto::Open::from_secret(alg, &secret).unwrap();\n\n        let mut hdr = Header::from_bytes(&mut b, 0).unwrap();\n        assert_eq!(hdr.ty, Type::Short);\n\n        let payload_len = b.cap();\n\n        decrypt_hdr(&mut b, &mut hdr, &aead).unwrap();\n        assert_eq!(hdr.pkt_num_len, 3);\n\n        let pn = decode_pkt_num(654_360_564, hdr.pkt_num, hdr.pkt_num_len);\n        assert_eq!(pn, 654_360_564);\n\n        let payload =\n            decrypt_pkt(&mut b, pn, hdr.pkt_num_len, payload_len, &aead).unwrap();\n\n        let payload = payload.as_ref();\n        assert_eq!(&payload, &[0x01]);\n    }\n\n    fn assert_encrypt_initial_pkt(\n        header: &mut [u8], dcid: &[u8], frames: &[u8], pn: u64, pn_len: usize,\n        is_server: bool, expected_pkt: &[u8],\n    ) {\n        let mut b = octets::OctetsMut::with_slice(header);\n\n        let hdr = Header::from_bytes(&mut b, 0).unwrap();\n        assert_eq!(hdr.ty, Type::Initial);\n\n        let mut out = vec![0; expected_pkt.len()];\n        let mut b = octets::OctetsMut::with_slice(&mut out);\n\n        b.put_bytes(header).unwrap();\n\n        let (_, mut aead) = crypto::derive_initial_key_material(\n            dcid,\n            hdr.version,\n            is_server,\n            false,\n        )\n        .unwrap();\n\n        let payload_len = frames.len();\n\n        let payload_offset = b.off();\n\n        b.put_bytes(frames).unwrap();\n\n        let written = encrypt_pkt(\n            &mut b,\n            pn,\n            pn_len,\n            payload_len,\n            payload_offset,\n            None,\n            &mut aead,\n        )\n        .unwrap();\n\n        assert_eq!(written, expected_pkt.len());\n        assert_eq!(&out[..written], expected_pkt);\n    }\n\n    #[test]\n    fn encrypt_client_initial_v1() {\n        let mut header = [\n            0xc3, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e,\n            0x51, 0x57, 0x08, 0x00, 0x00, 0x44, 0x9e, 0x00, 0x00, 0x00, 0x02,\n        ];\n\n        let dcid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];\n\n        let frames = [\n            0x06, 0x00, 0x40, 0xf1, 0x01, 0x00, 0x00, 0xed, 0x03, 0x03, 0xeb,\n            0xf8, 0xfa, 0x56, 0xf1, 0x29, 0x39, 0xb9, 0x58, 0x4a, 0x38, 0x96,\n            0x47, 0x2e, 0xc4, 0x0b, 0xb8, 0x63, 0xcf, 0xd3, 0xe8, 0x68, 0x04,\n            0xfe, 0x3a, 0x47, 0xf0, 0x6a, 0x2b, 0x69, 0x48, 0x4c, 0x00, 0x00,\n            0x04, 0x13, 0x01, 0x13, 0x02, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x00,\n            0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d,\n            0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0xff, 0x01, 0x00, 0x01,\n            0x00, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x06, 0x00, 0x1d, 0x00, 0x17,\n            0x00, 0x18, 0x00, 0x10, 0x00, 0x07, 0x00, 0x05, 0x04, 0x61, 0x6c,\n            0x70, 0x6e, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x93,\n            0x70, 0xb2, 0xc9, 0xca, 0xa4, 0x7f, 0xba, 0xba, 0xf4, 0x55, 0x9f,\n            0xed, 0xba, 0x75, 0x3d, 0xe1, 0x71, 0xfa, 0x71, 0xf5, 0x0f, 0x1c,\n            0xe1, 0x5d, 0x43, 0xe9, 0x94, 0xec, 0x74, 0xd7, 0x48, 0x00, 0x2b,\n            0x00, 0x03, 0x02, 0x03, 0x04, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x0e,\n            0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x02, 0x03, 0x08, 0x04, 0x08,\n            0x05, 0x08, 0x06, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x1c,\n            0x00, 0x02, 0x40, 0x01, 0xff, 0xa5, 0x00, 0x32, 0x04, 0x08, 0xff,\n            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x05, 0x04, 0x80, 0x00,\n            0xff, 0xff, 0x07, 0x04, 0x80, 0x00, 0xff, 0xff, 0x08, 0x01, 0x10,\n            0x01, 0x04, 0x80, 0x00, 0x75, 0x30, 0x09, 0x01, 0x10, 0x0f, 0x08,\n            0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08, 0x06, 0x04, 0x80,\n            0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n        ];\n\n        let pkt = [\n            0xc0, 0x00, 0x00, 0x00, 0x01, 0x08, 0x83, 0x94, 0xc8, 0xf0, 0x3e,\n            0x51, 0x57, 0x08, 0x00, 0x00, 0x44, 0x9e, 0x7b, 0x9a, 0xec, 0x34,\n            0xd1, 0xb1, 0xc9, 0x8d, 0xd7, 0x68, 0x9f, 0xb8, 0xec, 0x11, 0xd2,\n            0x42, 0xb1, 0x23, 0xdc, 0x9b, 0xd8, 0xba, 0xb9, 0x36, 0xb4, 0x7d,\n            0x92, 0xec, 0x35, 0x6c, 0x0b, 0xab, 0x7d, 0xf5, 0x97, 0x6d, 0x27,\n            0xcd, 0x44, 0x9f, 0x63, 0x30, 0x00, 0x99, 0xf3, 0x99, 0x1c, 0x26,\n            0x0e, 0xc4, 0xc6, 0x0d, 0x17, 0xb3, 0x1f, 0x84, 0x29, 0x15, 0x7b,\n            0xb3, 0x5a, 0x12, 0x82, 0xa6, 0x43, 0xa8, 0xd2, 0x26, 0x2c, 0xad,\n            0x67, 0x50, 0x0c, 0xad, 0xb8, 0xe7, 0x37, 0x8c, 0x8e, 0xb7, 0x53,\n            0x9e, 0xc4, 0xd4, 0x90, 0x5f, 0xed, 0x1b, 0xee, 0x1f, 0xc8, 0xaa,\n            0xfb, 0xa1, 0x7c, 0x75, 0x0e, 0x2c, 0x7a, 0xce, 0x01, 0xe6, 0x00,\n            0x5f, 0x80, 0xfc, 0xb7, 0xdf, 0x62, 0x12, 0x30, 0xc8, 0x37, 0x11,\n            0xb3, 0x93, 0x43, 0xfa, 0x02, 0x8c, 0xea, 0x7f, 0x7f, 0xb5, 0xff,\n            0x89, 0xea, 0xc2, 0x30, 0x82, 0x49, 0xa0, 0x22, 0x52, 0x15, 0x5e,\n            0x23, 0x47, 0xb6, 0x3d, 0x58, 0xc5, 0x45, 0x7a, 0xfd, 0x84, 0xd0,\n            0x5d, 0xff, 0xfd, 0xb2, 0x03, 0x92, 0x84, 0x4a, 0xe8, 0x12, 0x15,\n            0x46, 0x82, 0xe9, 0xcf, 0x01, 0x2f, 0x90, 0x21, 0xa6, 0xf0, 0xbe,\n            0x17, 0xdd, 0xd0, 0xc2, 0x08, 0x4d, 0xce, 0x25, 0xff, 0x9b, 0x06,\n            0xcd, 0xe5, 0x35, 0xd0, 0xf9, 0x20, 0xa2, 0xdb, 0x1b, 0xf3, 0x62,\n            0xc2, 0x3e, 0x59, 0x6d, 0xee, 0x38, 0xf5, 0xa6, 0xcf, 0x39, 0x48,\n            0x83, 0x8a, 0x3a, 0xec, 0x4e, 0x15, 0xda, 0xf8, 0x50, 0x0a, 0x6e,\n            0xf6, 0x9e, 0xc4, 0xe3, 0xfe, 0xb6, 0xb1, 0xd9, 0x8e, 0x61, 0x0a,\n            0xc8, 0xb7, 0xec, 0x3f, 0xaf, 0x6a, 0xd7, 0x60, 0xb7, 0xba, 0xd1,\n            0xdb, 0x4b, 0xa3, 0x48, 0x5e, 0x8a, 0x94, 0xdc, 0x25, 0x0a, 0xe3,\n            0xfd, 0xb4, 0x1e, 0xd1, 0x5f, 0xb6, 0xa8, 0xe5, 0xeb, 0xa0, 0xfc,\n            0x3d, 0xd6, 0x0b, 0xc8, 0xe3, 0x0c, 0x5c, 0x42, 0x87, 0xe5, 0x38,\n            0x05, 0xdb, 0x05, 0x9a, 0xe0, 0x64, 0x8d, 0xb2, 0xf6, 0x42, 0x64,\n            0xed, 0x5e, 0x39, 0xbe, 0x2e, 0x20, 0xd8, 0x2d, 0xf5, 0x66, 0xda,\n            0x8d, 0xd5, 0x99, 0x8c, 0xca, 0xbd, 0xae, 0x05, 0x30, 0x60, 0xae,\n            0x6c, 0x7b, 0x43, 0x78, 0xe8, 0x46, 0xd2, 0x9f, 0x37, 0xed, 0x7b,\n            0x4e, 0xa9, 0xec, 0x5d, 0x82, 0xe7, 0x96, 0x1b, 0x7f, 0x25, 0xa9,\n            0x32, 0x38, 0x51, 0xf6, 0x81, 0xd5, 0x82, 0x36, 0x3a, 0xa5, 0xf8,\n            0x99, 0x37, 0xf5, 0xa6, 0x72, 0x58, 0xbf, 0x63, 0xad, 0x6f, 0x1a,\n            0x0b, 0x1d, 0x96, 0xdb, 0xd4, 0xfa, 0xdd, 0xfc, 0xef, 0xc5, 0x26,\n            0x6b, 0xa6, 0x61, 0x17, 0x22, 0x39, 0x5c, 0x90, 0x65, 0x56, 0xbe,\n            0x52, 0xaf, 0xe3, 0xf5, 0x65, 0x63, 0x6a, 0xd1, 0xb1, 0x7d, 0x50,\n            0x8b, 0x73, 0xd8, 0x74, 0x3e, 0xeb, 0x52, 0x4b, 0xe2, 0x2b, 0x3d,\n            0xcb, 0xc2, 0xc7, 0x46, 0x8d, 0x54, 0x11, 0x9c, 0x74, 0x68, 0x44,\n            0x9a, 0x13, 0xd8, 0xe3, 0xb9, 0x58, 0x11, 0xa1, 0x98, 0xf3, 0x49,\n            0x1d, 0xe3, 0xe7, 0xfe, 0x94, 0x2b, 0x33, 0x04, 0x07, 0xab, 0xf8,\n            0x2a, 0x4e, 0xd7, 0xc1, 0xb3, 0x11, 0x66, 0x3a, 0xc6, 0x98, 0x90,\n            0xf4, 0x15, 0x70, 0x15, 0x85, 0x3d, 0x91, 0xe9, 0x23, 0x03, 0x7c,\n            0x22, 0x7a, 0x33, 0xcd, 0xd5, 0xec, 0x28, 0x1c, 0xa3, 0xf7, 0x9c,\n            0x44, 0x54, 0x6b, 0x9d, 0x90, 0xca, 0x00, 0xf0, 0x64, 0xc9, 0x9e,\n            0x3d, 0xd9, 0x79, 0x11, 0xd3, 0x9f, 0xe9, 0xc5, 0xd0, 0xb2, 0x3a,\n            0x22, 0x9a, 0x23, 0x4c, 0xb3, 0x61, 0x86, 0xc4, 0x81, 0x9e, 0x8b,\n            0x9c, 0x59, 0x27, 0x72, 0x66, 0x32, 0x29, 0x1d, 0x6a, 0x41, 0x82,\n            0x11, 0xcc, 0x29, 0x62, 0xe2, 0x0f, 0xe4, 0x7f, 0xeb, 0x3e, 0xdf,\n            0x33, 0x0f, 0x2c, 0x60, 0x3a, 0x9d, 0x48, 0xc0, 0xfc, 0xb5, 0x69,\n            0x9d, 0xbf, 0xe5, 0x89, 0x64, 0x25, 0xc5, 0xba, 0xc4, 0xae, 0xe8,\n            0x2e, 0x57, 0xa8, 0x5a, 0xaf, 0x4e, 0x25, 0x13, 0xe4, 0xf0, 0x57,\n            0x96, 0xb0, 0x7b, 0xa2, 0xee, 0x47, 0xd8, 0x05, 0x06, 0xf8, 0xd2,\n            0xc2, 0x5e, 0x50, 0xfd, 0x14, 0xde, 0x71, 0xe6, 0xc4, 0x18, 0x55,\n            0x93, 0x02, 0xf9, 0x39, 0xb0, 0xe1, 0xab, 0xd5, 0x76, 0xf2, 0x79,\n            0xc4, 0xb2, 0xe0, 0xfe, 0xb8, 0x5c, 0x1f, 0x28, 0xff, 0x18, 0xf5,\n            0x88, 0x91, 0xff, 0xef, 0x13, 0x2e, 0xef, 0x2f, 0xa0, 0x93, 0x46,\n            0xae, 0xe3, 0x3c, 0x28, 0xeb, 0x13, 0x0f, 0xf2, 0x8f, 0x5b, 0x76,\n            0x69, 0x53, 0x33, 0x41, 0x13, 0x21, 0x19, 0x96, 0xd2, 0x00, 0x11,\n            0xa1, 0x98, 0xe3, 0xfc, 0x43, 0x3f, 0x9f, 0x25, 0x41, 0x01, 0x0a,\n            0xe1, 0x7c, 0x1b, 0xf2, 0x02, 0x58, 0x0f, 0x60, 0x47, 0x47, 0x2f,\n            0xb3, 0x68, 0x57, 0xfe, 0x84, 0x3b, 0x19, 0xf5, 0x98, 0x40, 0x09,\n            0xdd, 0xc3, 0x24, 0x04, 0x4e, 0x84, 0x7a, 0x4f, 0x4a, 0x0a, 0xb3,\n            0x4f, 0x71, 0x95, 0x95, 0xde, 0x37, 0x25, 0x2d, 0x62, 0x35, 0x36,\n            0x5e, 0x9b, 0x84, 0x39, 0x2b, 0x06, 0x10, 0x85, 0x34, 0x9d, 0x73,\n            0x20, 0x3a, 0x4a, 0x13, 0xe9, 0x6f, 0x54, 0x32, 0xec, 0x0f, 0xd4,\n            0xa1, 0xee, 0x65, 0xac, 0xcd, 0xd5, 0xe3, 0x90, 0x4d, 0xf5, 0x4c,\n            0x1d, 0xa5, 0x10, 0xb0, 0xff, 0x20, 0xdc, 0xc0, 0xc7, 0x7f, 0xcb,\n            0x2c, 0x0e, 0x0e, 0xb6, 0x05, 0xcb, 0x05, 0x04, 0xdb, 0x87, 0x63,\n            0x2c, 0xf3, 0xd8, 0xb4, 0xda, 0xe6, 0xe7, 0x05, 0x76, 0x9d, 0x1d,\n            0xe3, 0x54, 0x27, 0x01, 0x23, 0xcb, 0x11, 0x45, 0x0e, 0xfc, 0x60,\n            0xac, 0x47, 0x68, 0x3d, 0x7b, 0x8d, 0x0f, 0x81, 0x13, 0x65, 0x56,\n            0x5f, 0xd9, 0x8c, 0x4c, 0x8e, 0xb9, 0x36, 0xbc, 0xab, 0x8d, 0x06,\n            0x9f, 0xc3, 0x3b, 0xd8, 0x01, 0xb0, 0x3a, 0xde, 0xa2, 0xe1, 0xfb,\n            0xc5, 0xaa, 0x46, 0x3d, 0x08, 0xca, 0x19, 0x89, 0x6d, 0x2b, 0xf5,\n            0x9a, 0x07, 0x1b, 0x85, 0x1e, 0x6c, 0x23, 0x90, 0x52, 0x17, 0x2f,\n            0x29, 0x6b, 0xfb, 0x5e, 0x72, 0x40, 0x47, 0x90, 0xa2, 0x18, 0x10,\n            0x14, 0xf3, 0xb9, 0x4a, 0x4e, 0x97, 0xd1, 0x17, 0xb4, 0x38, 0x13,\n            0x03, 0x68, 0xcc, 0x39, 0xdb, 0xb2, 0xd1, 0x98, 0x06, 0x5a, 0xe3,\n            0x98, 0x65, 0x47, 0x92, 0x6c, 0xd2, 0x16, 0x2f, 0x40, 0xa2, 0x9f,\n            0x0c, 0x3c, 0x87, 0x45, 0xc0, 0xf5, 0x0f, 0xba, 0x38, 0x52, 0xe5,\n            0x66, 0xd4, 0x45, 0x75, 0xc2, 0x9d, 0x39, 0xa0, 0x3f, 0x0c, 0xda,\n            0x72, 0x19, 0x84, 0xb6, 0xf4, 0x40, 0x59, 0x1f, 0x35, 0x5e, 0x12,\n            0xd4, 0x39, 0xff, 0x15, 0x0a, 0xab, 0x76, 0x13, 0x49, 0x9d, 0xbd,\n            0x49, 0xad, 0xab, 0xc8, 0x67, 0x6e, 0xef, 0x02, 0x3b, 0x15, 0xb6,\n            0x5b, 0xfc, 0x5c, 0xa0, 0x69, 0x48, 0x10, 0x9f, 0x23, 0xf3, 0x50,\n            0xdb, 0x82, 0x12, 0x35, 0x35, 0xeb, 0x8a, 0x74, 0x33, 0xbd, 0xab,\n            0xcb, 0x90, 0x92, 0x71, 0xa6, 0xec, 0xbc, 0xb5, 0x8b, 0x93, 0x6a,\n            0x88, 0xcd, 0x4e, 0x8f, 0x2e, 0x6f, 0xf5, 0x80, 0x01, 0x75, 0xf1,\n            0x13, 0x25, 0x3d, 0x8f, 0xa9, 0xca, 0x88, 0x85, 0xc2, 0xf5, 0x52,\n            0xe6, 0x57, 0xdc, 0x60, 0x3f, 0x25, 0x2e, 0x1a, 0x8e, 0x30, 0x8f,\n            0x76, 0xf0, 0xbe, 0x79, 0xe2, 0xfb, 0x8f, 0x5d, 0x5f, 0xbb, 0xe2,\n            0xe3, 0x0e, 0xca, 0xdd, 0x22, 0x07, 0x23, 0xc8, 0xc0, 0xae, 0xa8,\n            0x07, 0x8c, 0xdf, 0xcb, 0x38, 0x68, 0x26, 0x3f, 0xf8, 0xf0, 0x94,\n            0x00, 0x54, 0xda, 0x48, 0x78, 0x18, 0x93, 0xa7, 0xe4, 0x9a, 0xd5,\n            0xaf, 0xf4, 0xaf, 0x30, 0x0c, 0xd8, 0x04, 0xa6, 0xb6, 0x27, 0x9a,\n            0xb3, 0xff, 0x3a, 0xfb, 0x64, 0x49, 0x1c, 0x85, 0x19, 0x4a, 0xab,\n            0x76, 0x0d, 0x58, 0xa6, 0x06, 0x65, 0x4f, 0x9f, 0x44, 0x00, 0xe8,\n            0xb3, 0x85, 0x91, 0x35, 0x6f, 0xbf, 0x64, 0x25, 0xac, 0xa2, 0x6d,\n            0xc8, 0x52, 0x44, 0x25, 0x9f, 0xf2, 0xb1, 0x9c, 0x41, 0xb9, 0xf9,\n            0x6f, 0x3c, 0xa9, 0xec, 0x1d, 0xde, 0x43, 0x4d, 0xa7, 0xd2, 0xd3,\n            0x92, 0xb9, 0x05, 0xdd, 0xf3, 0xd1, 0xf9, 0xaf, 0x93, 0xd1, 0xaf,\n            0x59, 0x50, 0xbd, 0x49, 0x3f, 0x5a, 0xa7, 0x31, 0xb4, 0x05, 0x6d,\n            0xf3, 0x1b, 0xd2, 0x67, 0xb6, 0xb9, 0x0a, 0x07, 0x98, 0x31, 0xaa,\n            0xf5, 0x79, 0xbe, 0x0a, 0x39, 0x01, 0x31, 0x37, 0xaa, 0xc6, 0xd4,\n            0x04, 0xf5, 0x18, 0xcf, 0xd4, 0x68, 0x40, 0x64, 0x7e, 0x78, 0xbf,\n            0xe7, 0x06, 0xca, 0x4c, 0xf5, 0xe9, 0xc5, 0x45, 0x3e, 0x9f, 0x7c,\n            0xfd, 0x2b, 0x8b, 0x4c, 0x8d, 0x16, 0x9a, 0x44, 0xe5, 0x5c, 0x88,\n            0xd4, 0xa9, 0xa7, 0xf9, 0x47, 0x42, 0x41, 0x10, 0x92, 0xab, 0xbd,\n            0xf8, 0xb8, 0x89, 0xe5, 0xc1, 0x99, 0xd0, 0x96, 0xe3, 0xf2, 0x47,\n            0x88,\n        ];\n\n        assert_encrypt_initial_pkt(\n            &mut header,\n            &dcid,\n            &frames,\n            2,\n            4,\n            false,\n            &pkt,\n        );\n    }\n\n    #[test]\n    fn encrypt_server_initial_v1() {\n        let mut header = [\n            0xc1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50,\n            0x2a, 0x42, 0x62, 0xb5, 0x00, 0x40, 0x75, 0x00, 0x01,\n        ];\n\n        let dcid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];\n\n        let frames = [\n            0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00,\n            0x00, 0x56, 0x03, 0x03, 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1,\n            0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78, 0x25, 0xdd, 0xf7, 0x39, 0x88,\n            0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43, 0x0b, 0x9a,\n            0x04, 0x5a, 0x12, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33,\n            0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89,\n            0x69, 0x0b, 0x84, 0xd0, 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca,\n            0x68, 0x4d, 0x10, 0x81, 0x28, 0x7c, 0x83, 0x4d, 0x53, 0x11, 0xbc,\n            0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02, 0x03, 0x04,\n        ];\n\n        let pkt = [\n            0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50,\n            0x2a, 0x42, 0x62, 0xb5, 0x00, 0x40, 0x75, 0xc0, 0xd9, 0x5a, 0x48,\n            0x2c, 0xd0, 0x99, 0x1c, 0xd2, 0x5b, 0x0a, 0xac, 0x40, 0x6a, 0x58,\n            0x16, 0xb6, 0x39, 0x41, 0x00, 0xf3, 0x7a, 0x1c, 0x69, 0x79, 0x75,\n            0x54, 0x78, 0x0b, 0xb3, 0x8c, 0xc5, 0xa9, 0x9f, 0x5e, 0xde, 0x4c,\n            0xf7, 0x3c, 0x3e, 0xc2, 0x49, 0x3a, 0x18, 0x39, 0xb3, 0xdb, 0xcb,\n            0xa3, 0xf6, 0xea, 0x46, 0xc5, 0xb7, 0x68, 0x4d, 0xf3, 0x54, 0x8e,\n            0x7d, 0xde, 0xb9, 0xc3, 0xbf, 0x9c, 0x73, 0xcc, 0x3f, 0x3b, 0xde,\n            0xd7, 0x4b, 0x56, 0x2b, 0xfb, 0x19, 0xfb, 0x84, 0x02, 0x2f, 0x8e,\n            0xf4, 0xcd, 0xd9, 0x37, 0x95, 0xd7, 0x7d, 0x06, 0xed, 0xbb, 0x7a,\n            0xaf, 0x2f, 0x58, 0x89, 0x18, 0x50, 0xab, 0xbd, 0xca, 0x3d, 0x20,\n            0x39, 0x8c, 0x27, 0x64, 0x56, 0xcb, 0xc4, 0x21, 0x58, 0x40, 0x7d,\n            0xd0, 0x74, 0xee,\n        ];\n\n        assert_encrypt_initial_pkt(&mut header, &dcid, &frames, 1, 2, true, &pkt);\n    }\n\n    #[test]\n    fn encrypt_chacha20() {\n        let secret = [\n            0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42,\n            0x27, 0x48, 0xad, 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0,\n            0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3, 0x0f, 0x21, 0x63, 0x2b,\n        ];\n\n        let mut header = [0x42, 0x00, 0xbf, 0xf4];\n\n        let expected_pkt = [\n            0x4c, 0xfe, 0x41, 0x89, 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6,\n            0x90, 0x80, 0x57, 0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb,\n        ];\n\n        let mut b = octets::OctetsMut::with_slice(&mut header);\n\n        let hdr = Header::from_bytes(&mut b, 0).unwrap();\n        assert_eq!(hdr.ty, Type::Short);\n\n        let mut out = vec![0; expected_pkt.len()];\n        let mut b = octets::OctetsMut::with_slice(&mut out);\n\n        b.put_bytes(&header).unwrap();\n\n        let alg = crypto::Algorithm::ChaCha20_Poly1305;\n\n        let mut aead = crypto::Seal::from_secret(alg, &secret).unwrap();\n\n        let pn = 654_360_564;\n        let pn_len = 3;\n\n        let frames = [0x01];\n\n        let payload_len = frames.len();\n\n        let payload_offset = b.off();\n\n        b.put_bytes(&frames).unwrap();\n\n        let written = encrypt_pkt(\n            &mut b,\n            pn,\n            pn_len,\n            payload_len,\n            payload_offset,\n            None,\n            &mut aead,\n        )\n        .unwrap();\n\n        assert_eq!(written, expected_pkt.len());\n        assert_eq!(&out[..written], &expected_pkt[..]);\n    }\n\n    #[test]\n    fn decrypt_pkt_underflow() {\n        let mut buf = [0; 65535];\n        let mut b = octets::OctetsMut::with_slice(&mut buf);\n\n        let hdr = Header {\n            ty: Type::Initial,\n            version: crate::PROTOCOL_VERSION,\n            dcid: ConnectionId::default(),\n            scid: ConnectionId::default(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: None,\n            versions: None,\n            key_phase: false,\n        };\n\n        hdr.to_bytes(&mut b).unwrap();\n\n        b.put_bytes(&[0; 50]).unwrap();\n\n        let payload_len = b.get_varint().unwrap() as usize;\n\n        let (aead, _) =\n            crypto::derive_initial_key_material(b\"\", hdr.version, true, false)\n                .unwrap();\n\n        assert_eq!(\n            decrypt_pkt(&mut b, 0, 1, payload_len, &aead),\n            Err(Error::InvalidPacket)\n        );\n    }\n\n    #[test]\n    fn decrypt_pkt_too_small() {\n        let mut buf = [0; 65535];\n        let mut b = octets::OctetsMut::with_slice(&mut buf);\n\n        let hdr = Header {\n            ty: Type::Initial,\n            version: crate::PROTOCOL_VERSION,\n            dcid: ConnectionId::default(),\n            scid: ConnectionId::default(),\n            pkt_num: 0,\n            pkt_num_len: 0,\n            token: None,\n            versions: None,\n            key_phase: false,\n        };\n\n        hdr.to_bytes(&mut b).unwrap();\n\n        b.put_bytes(&[0; 1]).unwrap();\n\n        // No space for decryption.\n        let payload_len = 1;\n\n        let (aead, _) =\n            crypto::derive_initial_key_material(b\"\", hdr.version, true, false)\n                .unwrap();\n\n        assert_eq!(\n            decrypt_pkt(&mut b, 0, 1, payload_len, &aead),\n            Err(Error::CryptoFail)\n        );\n    }\n\n    #[test]\n    fn track_largest_packet_sent() {\n        let now = Instant::now();\n        let mut pkt_space = PktNumSpace::new();\n\n        assert!(pkt_space.largest_tx_pkt_num.is_none());\n\n        let sent_ctx = test_utils::helper_packet_sent(1, now, 10);\n        pkt_space.on_packet_sent(&sent_ctx);\n        assert_eq!(pkt_space.largest_tx_pkt_num.unwrap(), 1);\n\n        let sent_ctx = test_utils::helper_packet_sent(2, now, 10);\n        pkt_space.on_packet_sent(&sent_ctx);\n        assert_eq!(pkt_space.largest_tx_pkt_num.unwrap(), 2);\n    }\n\n    #[test]\n    fn skip_pn() {\n        let mut skip_manager = PktNumManager::new();\n        let cwnd = 1000;\n        let handshake_completed = true;\n        let mut next_pn = 0;\n\n        assert!(skip_manager.skip_pn.is_none());\n        assert!(skip_manager.skip_pn_counter.is_none());\n        assert!(!skip_manager.should_skip_pn(handshake_completed));\n\n        // Arm `skip_pn_counter`\n        skip_manager.on_packet_sent(\n            cwnd,\n            MAX_SEND_UDP_PAYLOAD_SIZE,\n            handshake_completed,\n        );\n        assert_eq!(next_pn, 0);\n        assert!(skip_manager.skip_pn.is_none());\n        assert!(skip_manager.skip_pn_counter.unwrap() >= MIN_SKIP_COUNTER_VALUE);\n        assert!(!skip_manager.should_skip_pn(handshake_completed));\n\n        // `should_skip_pn()` should be true once the counter expires\n        while skip_manager.skip_pn_counter.unwrap() > 0 {\n            // pretend to send the next packet\n            next_pn += 1;\n\n            skip_manager.on_packet_sent(\n                cwnd,\n                MAX_SEND_UDP_PAYLOAD_SIZE,\n                handshake_completed,\n            );\n        }\n        assert!(next_pn >= MIN_SKIP_COUNTER_VALUE);\n        assert!(skip_manager.skip_pn.is_none());\n        assert_eq!(skip_manager.skip_pn_counter.unwrap(), 0);\n        assert!(skip_manager.should_skip_pn(handshake_completed));\n\n        // skip the next pkt_num\n        skip_manager.set_skip_pn(Some(next_pn));\n        assert_eq!(skip_manager.skip_pn.unwrap(), next_pn);\n        assert!(skip_manager.skip_pn_counter.is_none());\n        assert!(!skip_manager.should_skip_pn(handshake_completed));\n    }\n\n    #[test]\n    fn arm_skip_counter_only_after_verifying_prev_skip_pn() {\n        let mut skip_manager = PktNumManager::new();\n        let cwnd = 1000;\n        let handshake_completed = true;\n\n        // Set skip pn\n        skip_manager.skip_pn_counter = Some(0);\n        skip_manager.set_skip_pn(Some(42));\n        assert!(skip_manager.skip_pn.is_some());\n        assert!(skip_manager.skip_pn_counter.is_none());\n\n        // Don't arm the skip_pn_counter since its still armed (0 means\n        // expired)\n        skip_manager.on_packet_sent(\n            cwnd,\n            MAX_SEND_UDP_PAYLOAD_SIZE,\n            handshake_completed,\n        );\n        assert!(skip_manager.skip_pn.is_some());\n        assert!(skip_manager.skip_pn_counter.is_none());\n\n        // Arm the skip_pn_counter once the skip_pn has been verified\n        skip_manager.skip_pn = None;\n        skip_manager.on_packet_sent(\n            cwnd,\n            MAX_SEND_UDP_PAYLOAD_SIZE,\n            handshake_completed,\n        );\n        assert!(skip_manager.skip_pn.is_none());\n        assert!(skip_manager.skip_pn_counter.is_some());\n    }\n\n    #[test]\n    fn arm_skip_counter_only_after_handshake_complete() {\n        let mut skip_manager = PktNumManager::new();\n        let cwnd = 1000;\n        skip_manager.skip_pn_counter = None;\n\n        // Don't arm the skip_pn_counter since handshake is not complete\n        let mut handshake_completed = false;\n        skip_manager.on_packet_sent(\n            cwnd,\n            MAX_SEND_UDP_PAYLOAD_SIZE,\n            handshake_completed,\n        );\n        assert!(skip_manager.skip_pn_counter.is_none());\n\n        // Arm counter after handshake complete\n        handshake_completed = true;\n        skip_manager.on_packet_sent(\n            cwnd,\n            MAX_SEND_UDP_PAYLOAD_SIZE,\n            handshake_completed,\n        );\n        assert!(skip_manager.skip_pn_counter.is_some());\n    }\n\n    #[test]\n    fn only_skip_after_handshake_complete() {\n        let mut skip_manager = PktNumManager::new();\n        skip_manager.skip_pn_counter = Some(0);\n\n        let mut handshake_completed = false;\n        // Don't skip since handshake is not complete\n        assert!(!skip_manager.should_skip_pn(handshake_completed));\n\n        handshake_completed = true;\n        // Skip pn after handshake complete\n        assert!(skip_manager.should_skip_pn(handshake_completed));\n    }\n}\n"
  },
  {
    "path": "quiche/src/path.rs",
    "content": "// Copyright (C) 2022, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\nuse std::collections::VecDeque;\n\nuse std::net::SocketAddr;\n\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse smallvec::SmallVec;\n\nuse slab::Slab;\n\nuse crate::Config;\nuse crate::Error;\nuse crate::Result;\nuse crate::StartupExit;\n\nuse crate::pmtud;\nuse crate::recovery;\nuse crate::recovery::Bandwidth;\nuse crate::recovery::HandshakeStatus;\nuse crate::recovery::OnLossDetectionTimeoutOutcome;\nuse crate::recovery::RecoveryOps;\n\n/// The different states of the path validation.\n#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]\npub enum PathState {\n    /// The path failed its validation.\n    Failed,\n\n    /// The path exists, but no path validation has been performed.\n    Unknown,\n\n    /// The path is under validation.\n    Validating,\n\n    /// The remote address has been validated, but not the path MTU.\n    ValidatingMTU,\n\n    /// The path has been validated.\n    Validated,\n}\n\nimpl PathState {\n    #[cfg(feature = \"ffi\")]\n    pub fn to_c(self) -> libc::ssize_t {\n        match self {\n            PathState::Failed => -1,\n            PathState::Unknown => 0,\n            PathState::Validating => 1,\n            PathState::ValidatingMTU => 2,\n            PathState::Validated => 3,\n        }\n    }\n}\n\n/// A path-specific event.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum PathEvent {\n    /// A new network path (local address, peer address) has been seen on a\n    /// received packet. Note that this event is only triggered for servers, as\n    /// the client is responsible from initiating new paths. The application may\n    /// then probe this new path, if desired.\n    New(SocketAddr, SocketAddr),\n\n    /// The related network path between local `SocketAddr` and peer\n    /// `SocketAddr` has been validated.\n    Validated(SocketAddr, SocketAddr),\n\n    /// The related network path between local `SocketAddr` and peer\n    /// `SocketAddr` failed to be validated. This network path will not be used\n    /// anymore, unless the application requests probing this path again.\n    FailedValidation(SocketAddr, SocketAddr),\n\n    /// The related network path between local `SocketAddr` and peer\n    /// `SocketAddr` has been closed and is now unusable on this connection.\n    Closed(SocketAddr, SocketAddr),\n\n    /// The stack observes that the Source Connection ID with the given sequence\n    /// number, initially used by the peer over the first pair of `SocketAddr`s,\n    /// is now reused over the second pair of `SocketAddr`s.\n    ReusedSourceConnectionId(\n        u64,\n        (SocketAddr, SocketAddr),\n        (SocketAddr, SocketAddr),\n    ),\n\n    /// The connection observed that the peer migrated over the network path\n    /// denoted by the pair of `SocketAddr`, i.e., non-probing packets have been\n    /// received on this network path. This is a server side only event.\n    ///\n    /// Note that this event is only raised if the path has been validated.\n    PeerMigrated(SocketAddr, SocketAddr),\n}\n\n/// A network path on which QUIC packets can be sent.\n#[derive(Debug)]\npub struct Path {\n    /// The local address.\n    local_addr: SocketAddr,\n\n    /// The remote address.\n    peer_addr: SocketAddr,\n\n    /// Source CID sequence number used over that path.\n    pub active_scid_seq: Option<u64>,\n\n    /// Destination CID sequence number used over that path.\n    pub active_dcid_seq: Option<u64>,\n\n    /// The current validation state of the path.\n    state: PathState,\n\n    /// Is this path used to send non-probing packets.\n    active: bool,\n\n    /// Loss recovery and congestion control state.\n    pub recovery: recovery::Recovery,\n\n    /// Path MTU discovery state. None if PMTUD is disabled on the path.\n    pub pmtud: Option<pmtud::Pmtud>,\n\n    /// Pending challenge data with the size of the packet containing them and\n    /// when they were sent.\n    in_flight_challenges: VecDeque<([u8; 8], usize, Instant)>,\n\n    /// The maximum challenge size that got acknowledged.\n    max_challenge_size: usize,\n\n    /// Number of consecutive (spaced by at least 1 RTT) probing packets lost.\n    probing_lost: usize,\n\n    /// Last instant when a probing packet got lost.\n    last_probe_lost_time: Option<Instant>,\n\n    /// Received challenge data.\n    received_challenges: VecDeque<[u8; 8]>,\n\n    /// Max length of received challenges queue.\n    received_challenges_max_len: usize,\n\n    /// Number of packets sent on this path.\n    pub sent_count: usize,\n\n    /// Number of packets received on this path.\n    pub recv_count: usize,\n\n    /// Total number of packets sent with data retransmitted from this path.\n    pub retrans_count: usize,\n\n    /// Total number of times PTO (probe timeout) fired.\n    ///\n    /// Loss usually happens in a burst so the number of packets lost will\n    /// depend on the volume of inflight packets at the time of loss (which\n    /// can be arbitrary). PTO count measures the number of loss events and\n    /// provides a normalized loss metric.\n    pub total_pto_count: usize,\n\n    /// Number of DATAGRAM frames sent on this path.\n    pub dgram_sent_count: usize,\n\n    /// Number of DATAGRAM frames received on this path.\n    pub dgram_recv_count: usize,\n\n    /// Total number of sent bytes over this path.\n    pub sent_bytes: u64,\n\n    /// Total number of bytes received over this path.\n    pub recv_bytes: u64,\n\n    /// Total number of bytes retransmitted from this path.\n    /// This counts only STREAM and CRYPTO data.\n    pub stream_retrans_bytes: u64,\n\n    /// Total number of bytes the server can send before the peer's address\n    /// is verified.\n    pub max_send_bytes: usize,\n\n    /// Whether the peer's address has been verified.\n    pub verified_peer_address: bool,\n\n    /// Whether the peer has verified our address.\n    pub peer_verified_local_address: bool,\n\n    /// Does it requires sending PATH_CHALLENGE?\n    challenge_requested: bool,\n\n    /// Whether the failure of this path was notified.\n    failure_notified: bool,\n\n    /// Whether the connection tries to migrate to this path, but it still needs\n    /// to be validated.\n    migrating: bool,\n\n    /// Whether or not we should force eliciting of an ACK (e.g. via PING frame)\n    pub needs_ack_eliciting: bool,\n}\n\nimpl Path {\n    /// Create a new Path instance with the provided addresses, the remaining of\n    /// the fields being set to their default value.\n    pub fn new(\n        local_addr: SocketAddr, peer_addr: SocketAddr,\n        recovery_config: &recovery::RecoveryConfig,\n        path_challenge_recv_max_queue_len: usize, is_initial: bool,\n        config: Option<&Config>,\n    ) -> Self {\n        let (state, active_scid_seq, active_dcid_seq) = if is_initial {\n            (PathState::Validated, Some(0), Some(0))\n        } else {\n            (PathState::Unknown, None, None)\n        };\n\n        let pmtud = config.and_then(|c| {\n            if c.pmtud {\n                let maximum_supported_mtu: usize = std::cmp::min(\n                    // if the max_udp_payload_size doesn't fit into a usize, then\n                    // max_send_udp_payload_size must be smaller so use that\n                    c.local_transport_params\n                        .max_udp_payload_size\n                        .try_into()\n                        .unwrap_or(c.max_send_udp_payload_size),\n                    c.max_send_udp_payload_size,\n                );\n                Some(pmtud::Pmtud::new(maximum_supported_mtu, c.pmtud_max_probes))\n            } else {\n                None\n            }\n        });\n\n        Self {\n            local_addr,\n            peer_addr,\n            active_scid_seq,\n            active_dcid_seq,\n            state,\n            active: false,\n            recovery: recovery::Recovery::new_with_config(recovery_config),\n            pmtud,\n            in_flight_challenges: VecDeque::new(),\n            max_challenge_size: 0,\n            probing_lost: 0,\n            last_probe_lost_time: None,\n            received_challenges: VecDeque::with_capacity(\n                path_challenge_recv_max_queue_len,\n            ),\n            received_challenges_max_len: path_challenge_recv_max_queue_len,\n            sent_count: 0,\n            recv_count: 0,\n            retrans_count: 0,\n            total_pto_count: 0,\n            dgram_sent_count: 0,\n            dgram_recv_count: 0,\n            sent_bytes: 0,\n            recv_bytes: 0,\n            stream_retrans_bytes: 0,\n            max_send_bytes: 0,\n            verified_peer_address: false,\n            peer_verified_local_address: false,\n            challenge_requested: false,\n            failure_notified: false,\n            migrating: false,\n            needs_ack_eliciting: false,\n        }\n    }\n\n    /// Returns the local address on which this path operates.\n    #[inline]\n    pub fn local_addr(&self) -> SocketAddr {\n        self.local_addr\n    }\n\n    /// Returns the peer address on which this path operates.\n    #[inline]\n    pub fn peer_addr(&self) -> SocketAddr {\n        self.peer_addr\n    }\n\n    /// Returns whether the path is working (i.e., not failed).\n    #[inline]\n    fn working(&self) -> bool {\n        self.state > PathState::Failed\n    }\n\n    /// Returns whether the path is active.\n    #[inline]\n    pub fn active(&self) -> bool {\n        self.active && self.working() && self.active_dcid_seq.is_some()\n    }\n\n    /// Returns whether the path can be used to send non-probing packets.\n    #[inline]\n    pub fn usable(&self) -> bool {\n        self.active() ||\n            (self.state == PathState::Validated &&\n                self.active_dcid_seq.is_some())\n    }\n\n    /// Returns whether the path is unused.\n    #[inline]\n    fn unused(&self) -> bool {\n        // FIXME: we should check that there is nothing in the sent queue.\n        !self.active() && self.active_dcid_seq.is_none()\n    }\n\n    /// Returns whether the path requires sending a probing packet.\n    #[inline]\n    pub fn probing_required(&self) -> bool {\n        !self.received_challenges.is_empty() || self.validation_requested()\n    }\n\n    /// Promotes the path to the provided state only if the new state is greater\n    /// than the current one.\n    fn promote_to(&mut self, state: PathState) {\n        if self.state < state {\n            self.state = state;\n        }\n    }\n\n    /// Returns whether the path is validated.\n    #[inline]\n    pub fn validated(&self) -> bool {\n        self.state == PathState::Validated\n    }\n\n    /// Returns whether this path failed its validation.\n    #[inline]\n    fn validation_failed(&self) -> bool {\n        self.state == PathState::Failed\n    }\n\n    // Returns whether this path is under path validation process.\n    #[inline]\n    pub fn under_validation(&self) -> bool {\n        matches!(self.state, PathState::Validating | PathState::ValidatingMTU)\n    }\n\n    /// Requests path validation.\n    #[inline]\n    pub fn request_validation(&mut self) {\n        self.challenge_requested = true;\n    }\n\n    /// Returns whether a validation is requested.\n    #[inline]\n    pub fn validation_requested(&self) -> bool {\n        self.challenge_requested\n    }\n\n    pub fn should_send_pmtu_probe(\n        &mut self, hs_confirmed: bool, hs_done: bool, out_len: usize,\n        is_closing: bool, frames_empty: bool,\n    ) -> bool {\n        let Some(pmtud) = self.pmtud.as_mut() else {\n            return false;\n        };\n\n        (hs_confirmed && hs_done) &&\n            self.recovery.cwnd_available() > pmtud.get_probe_size() &&\n            out_len >= pmtud.get_probe_size() &&\n            pmtud.should_probe() &&\n            !is_closing &&\n            frames_empty\n    }\n\n    pub fn on_challenge_sent(&mut self) {\n        self.promote_to(PathState::Validating);\n        self.challenge_requested = false;\n    }\n\n    /// Handles the sending of PATH_CHALLENGE.\n    pub fn add_challenge_sent(\n        &mut self, data: [u8; 8], pkt_size: usize, sent_time: Instant,\n    ) {\n        self.on_challenge_sent();\n        self.in_flight_challenges\n            .push_back((data, pkt_size, sent_time));\n    }\n\n    pub fn on_challenge_received(&mut self, data: [u8; 8]) {\n        // Discard challenges that would cause us to queue more than we want.\n        if self.received_challenges.len() == self.received_challenges_max_len {\n            return;\n        }\n\n        self.received_challenges.push_back(data);\n        self.peer_verified_local_address = true;\n    }\n\n    pub fn has_pending_challenge(&self, data: [u8; 8]) -> bool {\n        self.in_flight_challenges.iter().any(|(d, ..)| *d == data)\n    }\n\n    /// Returns whether the path is now validated.\n    pub fn on_response_received(&mut self, data: [u8; 8]) -> bool {\n        self.verified_peer_address = true;\n        self.probing_lost = 0;\n\n        let mut challenge_size = 0;\n        self.in_flight_challenges.retain(|(d, s, _)| {\n            if *d == data {\n                challenge_size = *s;\n                false\n            } else {\n                true\n            }\n        });\n\n        // The 4-tuple is reachable, but we didn't check Path MTU yet.\n        self.promote_to(PathState::ValidatingMTU);\n\n        self.max_challenge_size =\n            std::cmp::max(self.max_challenge_size, challenge_size);\n\n        if self.state == PathState::ValidatingMTU {\n            if self.max_challenge_size >= crate::MIN_CLIENT_INITIAL_LEN {\n                // Path MTU is sufficient for QUIC traffic.\n                self.promote_to(PathState::Validated);\n                return true;\n            }\n\n            // If the MTU was not validated, probe again.\n            self.request_validation();\n        }\n\n        false\n    }\n\n    fn on_failed_validation(&mut self) {\n        self.state = PathState::Failed;\n        self.active = false;\n    }\n\n    #[inline]\n    pub fn pop_received_challenge(&mut self) -> Option<[u8; 8]> {\n        self.received_challenges.pop_front()\n    }\n\n    pub fn on_loss_detection_timeout(\n        &mut self, handshake_status: HandshakeStatus, now: Instant,\n        is_server: bool, trace_id: &str,\n    ) -> OnLossDetectionTimeoutOutcome {\n        let outcome = self.recovery.on_loss_detection_timeout(\n            handshake_status,\n            now,\n            trace_id,\n        );\n\n        let mut lost_probe_time = None;\n        self.in_flight_challenges.retain(|(_, _, sent_time)| {\n            if *sent_time <= now {\n                if lost_probe_time.is_none() {\n                    lost_probe_time = Some(*sent_time);\n                }\n                false\n            } else {\n                true\n            }\n        });\n\n        // If we lost probing packets, check if the path failed\n        // validation.\n        if let Some(lost_probe_time) = lost_probe_time {\n            self.last_probe_lost_time = match self.last_probe_lost_time {\n                Some(last) => {\n                    // Count a loss if at least 1-RTT happened.\n                    if lost_probe_time - last >= self.recovery.rtt() {\n                        self.probing_lost += 1;\n                        Some(lost_probe_time)\n                    } else {\n                        Some(last)\n                    }\n                },\n                None => {\n                    self.probing_lost += 1;\n                    Some(lost_probe_time)\n                },\n            };\n            // As a server, if requesting a challenge is not\n            // possible due to the amplification attack, declare the\n            // validation as failed.\n            if self.probing_lost >= crate::MAX_PROBING_TIMEOUTS ||\n                (is_server && self.max_send_bytes < crate::MIN_PROBING_SIZE)\n            {\n                self.on_failed_validation();\n            } else {\n                self.request_validation();\n            }\n        }\n\n        // Track PTO timeout event\n        self.total_pto_count += 1;\n\n        outcome\n    }\n\n    /// Returns true if the path's recovery module hasn't processed any non-ACK\n    /// packets, and it is still OK to fully reinitialize the recovery module to\n    /// pickup changes to congestion control config.\n    pub fn can_reinit_recovery(&self) -> bool {\n        // The recovery module can be reinitialized until the connection attempts\n        // to send a packet with inflight data. The congestion\n        // controller doesn't track anything interesting until inflight\n        // data is sent. Handshake ACKs may be sent prior to arrival of\n        // the full ClientHello, but the send of ACK only packets\n        // shouldn't prevent the reinit of the recovery module.\n        self.recovery.bytes_in_flight() == 0 &&\n            self.recovery.bytes_in_flight_duration() == Duration::ZERO\n    }\n\n    pub fn reinit_recovery(\n        &mut self, recovery_config: &recovery::RecoveryConfig,\n    ) {\n        self.recovery = recovery::Recovery::new_with_config(recovery_config)\n    }\n\n    pub fn stats(&self) -> PathStats {\n        let pmtu = match self.pmtud.as_ref().map(|p| p.get_current_mtu()) {\n            Some(v) => v,\n\n            None => self.recovery.max_datagram_size(),\n        };\n\n        PathStats {\n            local_addr: self.local_addr,\n            peer_addr: self.peer_addr,\n            validation_state: self.state,\n            active: self.active,\n            recv: self.recv_count,\n            sent: self.sent_count,\n            lost: self.recovery.lost_count(),\n            retrans: self.retrans_count,\n            total_pto_count: self.total_pto_count,\n            dgram_recv: self.dgram_recv_count,\n            dgram_sent: self.dgram_sent_count,\n            rtt: self.recovery.rtt(),\n            min_rtt: self.recovery.min_rtt(),\n            max_rtt: self.recovery.max_rtt(),\n            rttvar: self.recovery.rttvar(),\n            cwnd: self.recovery.cwnd(),\n            sent_bytes: self.sent_bytes,\n            recv_bytes: self.recv_bytes,\n            lost_bytes: self.recovery.bytes_lost(),\n            stream_retrans_bytes: self.stream_retrans_bytes,\n            pmtu,\n            delivery_rate: self.recovery.delivery_rate().to_bytes_per_second(),\n            max_bandwidth: self\n                .recovery\n                .max_bandwidth()\n                .map(Bandwidth::to_bytes_per_second),\n            startup_exit: self.recovery.startup_exit(),\n        }\n    }\n\n    pub fn bytes_in_flight_duration(&self) -> Duration {\n        self.recovery.bytes_in_flight_duration()\n    }\n}\n\n/// An iterator over SocketAddr.\n#[derive(Default)]\npub struct SocketAddrIter {\n    pub(crate) sockaddrs: SmallVec<[SocketAddr; 8]>,\n    pub(crate) index: usize,\n}\n\nimpl Iterator for SocketAddrIter {\n    type Item = SocketAddr;\n\n    #[inline]\n    fn next(&mut self) -> Option<Self::Item> {\n        let v = self.sockaddrs.get(self.index)?;\n        self.index += 1;\n        Some(*v)\n    }\n}\n\nimpl ExactSizeIterator for SocketAddrIter {\n    #[inline]\n    fn len(&self) -> usize {\n        self.sockaddrs.len() - self.index\n    }\n}\n\n/// All path-related information.\npub struct PathMap {\n    /// The paths of the connection. Each of them has an internal identifier\n    /// that is used by `addrs_to_paths` and `ConnectionEntry`.\n    paths: Slab<Path>,\n\n    /// The maximum number of concurrent paths allowed.\n    max_concurrent_paths: usize,\n\n    /// The mapping from the (local `SocketAddr`, peer `SocketAddr`) to the\n    /// `Path` structure identifier.\n    addrs_to_paths: BTreeMap<(SocketAddr, SocketAddr), usize>,\n\n    /// Path-specific events to be notified to the application.\n    events: VecDeque<PathEvent>,\n\n    /// Whether this manager serves a connection as a server.\n    is_server: bool,\n}\n\nimpl PathMap {\n    /// Creates a new `PathMap` with the initial provided `path` and a\n    /// capacity limit.\n    pub fn new(\n        mut initial_path: Path, max_concurrent_paths: usize, is_server: bool,\n    ) -> Self {\n        let mut paths = Slab::with_capacity(1); // most connections only have one path\n        let mut addrs_to_paths = BTreeMap::new();\n\n        let local_addr = initial_path.local_addr;\n        let peer_addr = initial_path.peer_addr;\n\n        // As it is the first path, it is active by default.\n        initial_path.active = true;\n\n        let active_path_id = paths.insert(initial_path);\n        addrs_to_paths.insert((local_addr, peer_addr), active_path_id);\n\n        Self {\n            paths,\n            max_concurrent_paths,\n            addrs_to_paths,\n            events: VecDeque::new(),\n            is_server,\n        }\n    }\n\n    /// Gets an immutable reference to the path identified by `path_id`. If the\n    /// provided `path_id` does not identify any current `Path`, returns an\n    /// [`InvalidState`].\n    ///\n    /// [`InvalidState`]: enum.Error.html#variant.InvalidState\n    #[inline]\n    pub fn get(&self, path_id: usize) -> Result<&Path> {\n        self.paths.get(path_id).ok_or(Error::InvalidState)\n    }\n\n    /// Gets a mutable reference to the path identified by `path_id`. If the\n    /// provided `path_id` does not identify any current `Path`, returns an\n    /// [`InvalidState`].\n    ///\n    /// [`InvalidState`]: enum.Error.html#variant.InvalidState\n    #[inline]\n    pub fn get_mut(&mut self, path_id: usize) -> Result<&mut Path> {\n        self.paths.get_mut(path_id).ok_or(Error::InvalidState)\n    }\n\n    #[inline]\n    /// Gets an immutable reference to the active path with the value of the\n    /// lowest identifier. If there is no active path, returns `None`.\n    pub fn get_active_with_pid(&self) -> Option<(usize, &Path)> {\n        self.paths.iter().find(|(_, p)| p.active())\n    }\n\n    /// Gets an immutable reference to the active path with the lowest\n    /// identifier. If there is no active path, returns an [`InvalidState`].\n    ///\n    /// [`InvalidState`]: enum.Error.html#variant.InvalidState\n    #[inline]\n    pub fn get_active(&self) -> Result<&Path> {\n        self.get_active_with_pid()\n            .map(|(_, p)| p)\n            .ok_or(Error::InvalidState)\n    }\n\n    /// Gets the lowest active path identifier. If there is no active path,\n    /// returns an [`InvalidState`].\n    ///\n    /// [`InvalidState`]: enum.Error.html#variant.InvalidState\n    #[inline]\n    pub fn get_active_path_id(&self) -> Result<usize> {\n        self.get_active_with_pid()\n            .map(|(pid, _)| pid)\n            .ok_or(Error::InvalidState)\n    }\n\n    /// Gets an mutable reference to the active path with the lowest identifier.\n    /// If there is no active path, returns an [`InvalidState`].\n    ///\n    /// [`InvalidState`]: enum.Error.html#variant.InvalidState\n    #[inline]\n    pub fn get_active_mut(&mut self) -> Result<&mut Path> {\n        self.paths\n            .iter_mut()\n            .map(|(_, p)| p)\n            .find(|p| p.active())\n            .ok_or(Error::InvalidState)\n    }\n\n    /// Returns an iterator over all existing paths.\n    #[inline]\n    pub fn iter(&self) -> slab::Iter<'_, Path> {\n        self.paths.iter()\n    }\n\n    /// Returns a mutable iterator over all existing paths.\n    #[inline]\n    pub fn iter_mut(&mut self) -> slab::IterMut<'_, Path> {\n        self.paths.iter_mut()\n    }\n\n    /// Returns the number of existing paths.\n    #[inline]\n    pub fn len(&self) -> usize {\n        self.paths.len()\n    }\n\n    /// Returns the `Path` identifier related to the provided `addrs`.\n    #[inline]\n    pub fn path_id_from_addrs(\n        &self, addrs: &(SocketAddr, SocketAddr),\n    ) -> Option<usize> {\n        self.addrs_to_paths.get(addrs).copied()\n    }\n\n    /// Checks if creating a new path will not exceed the current `self.paths`\n    /// capacity. If yes, this method tries to remove one unused path. If it\n    /// fails to do so, returns [`Done`].\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    fn make_room_for_new_path(&mut self) -> Result<()> {\n        if self.paths.len() < self.max_concurrent_paths {\n            return Ok(());\n        }\n\n        let (pid_to_remove, _) = self\n            .paths\n            .iter()\n            .find(|(_, p)| p.unused())\n            .ok_or(Error::Done)?;\n\n        let path = self.paths.remove(pid_to_remove);\n        self.addrs_to_paths\n            .remove(&(path.local_addr, path.peer_addr));\n\n        self.notify_event(PathEvent::Closed(path.local_addr, path.peer_addr));\n\n        Ok(())\n    }\n\n    /// Records the provided `Path` and returns its assigned identifier.\n    ///\n    /// On success, this method takes care of creating a notification to the\n    /// serving application, if it serves a server-side connection.\n    ///\n    /// If there are already `max_concurrent_paths` currently recorded, this\n    /// method tries to remove an unused `Path` first. If it fails to do so,\n    /// it returns [`Done`].\n    ///\n    /// [`Done`]: enum.Error.html#variant.Done\n    pub fn insert_path(&mut self, path: Path, is_server: bool) -> Result<usize> {\n        self.make_room_for_new_path()?;\n\n        let local_addr = path.local_addr;\n        let peer_addr = path.peer_addr;\n\n        let pid = self.paths.insert(path);\n        self.addrs_to_paths.insert((local_addr, peer_addr), pid);\n\n        // Notifies the application if we are in server mode.\n        if is_server {\n            self.notify_event(PathEvent::New(local_addr, peer_addr));\n        }\n\n        Ok(pid)\n    }\n\n    /// Notifies a path event to the application served by the connection.\n    pub fn notify_event(&mut self, ev: PathEvent) {\n        self.events.push_back(ev);\n    }\n\n    /// Gets the first path event to be notified to the application.\n    pub fn pop_event(&mut self) -> Option<PathEvent> {\n        self.events.pop_front()\n    }\n\n    /// Notifies all failed validations to the application.\n    pub fn notify_failed_validations(&mut self) {\n        let validation_failed = self\n            .paths\n            .iter_mut()\n            .filter(|(_, p)| p.validation_failed() && !p.failure_notified);\n\n        for (_, p) in validation_failed {\n            self.events.push_back(PathEvent::FailedValidation(\n                p.local_addr,\n                p.peer_addr,\n            ));\n\n            p.failure_notified = true;\n        }\n    }\n\n    /// Finds a path candidate to be active and returns its identifier.\n    pub fn find_candidate_path(&self) -> Option<usize> {\n        // TODO: also consider unvalidated paths if there are no more validated.\n        self.paths\n            .iter()\n            .find(|(_, p)| p.usable())\n            .map(|(pid, _)| pid)\n    }\n\n    /// Handles incoming PATH_RESPONSE data.\n    pub fn on_response_received(&mut self, data: [u8; 8]) -> Result<()> {\n        let active_pid = self.get_active_path_id()?;\n\n        let challenge_pending =\n            self.iter_mut().find(|(_, p)| p.has_pending_challenge(data));\n\n        if let Some((pid, p)) = challenge_pending {\n            if p.on_response_received(data) {\n                let local_addr = p.local_addr;\n                let peer_addr = p.peer_addr;\n                let was_migrating = p.migrating;\n\n                p.migrating = false;\n\n                // Notifies the application.\n                self.notify_event(PathEvent::Validated(local_addr, peer_addr));\n\n                // If this path was the candidate for migration, notifies the\n                // application.\n                if pid == active_pid && was_migrating {\n                    self.notify_event(PathEvent::PeerMigrated(\n                        local_addr, peer_addr,\n                    ));\n                }\n            }\n        }\n        Ok(())\n    }\n\n    /// Sets the path with identifier 'path_id' to be active.\n    ///\n    /// There can be exactly one active path on which non-probing packets can be\n    /// sent. If another path is marked as active, it will be superseded by the\n    /// one having `path_id` as identifier.\n    ///\n    /// A server should always ensure that the active path is validated. If it\n    /// is already the case, it notifies the application that the connection\n    /// migrated. Otherwise, it triggers a path validation and defers the\n    /// notification once it is actually validated.\n    pub fn set_active_path(&mut self, path_id: usize) -> Result<()> {\n        let is_server = self.is_server;\n\n        if let Ok(old_active_path) = self.get_active_mut() {\n            old_active_path.active = false;\n        }\n\n        let new_active_path = self.get_mut(path_id)?;\n        new_active_path.active = true;\n\n        if is_server {\n            if new_active_path.validated() {\n                let local_addr = new_active_path.local_addr();\n                let peer_addr = new_active_path.peer_addr();\n\n                self.notify_event(PathEvent::PeerMigrated(local_addr, peer_addr));\n            } else {\n                new_active_path.migrating = true;\n\n                // Requests path validation if needed.\n                if !new_active_path.under_validation() {\n                    new_active_path.request_validation();\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Configures path MTU discovery on all existing paths.\n    pub fn set_discover_pmtu_on_existing_paths(\n        &mut self, discover: bool, max_send_udp_payload_size: usize,\n        pmtud_max_probes: u8,\n    ) {\n        for (_, path) in self.paths.iter_mut() {\n            path.pmtud = if discover {\n                Some(pmtud::Pmtud::new(\n                    max_send_udp_payload_size,\n                    pmtud_max_probes,\n                ))\n            } else {\n                None\n            };\n        }\n    }\n}\n\n/// Statistics about the path of a connection.\n///\n/// A connection’s path statistics can be collected using the [`path_stats()`]\n/// method.\n///\n/// [`path_stats()`]: struct.Connection.html#method.path_stats\n#[derive(Clone)]\npub struct PathStats {\n    /// The local address of the path.\n    pub local_addr: SocketAddr,\n\n    /// The peer address of the path.\n    pub peer_addr: SocketAddr,\n\n    /// The path validation state.\n    pub validation_state: PathState,\n\n    /// Whether the path is marked as active.\n    pub active: bool,\n\n    /// The number of QUIC packets received.\n    pub recv: usize,\n\n    /// The number of QUIC packets sent.\n    pub sent: usize,\n\n    /// The number of QUIC packets that were lost.\n    pub lost: usize,\n\n    /// The number of sent QUIC packets with retransmitted data.\n    pub retrans: usize,\n\n    /// The number of times PTO (probe timeout) fired.\n    ///\n    /// Loss usually happens in a burst so the number of packets lost will\n    /// depend on the volume of inflight packets at the time of loss (which\n    /// can be arbitrary). PTO count measures the number of loss events and\n    /// provides a normalized loss metric.\n    pub total_pto_count: usize,\n\n    /// The number of DATAGRAM frames received.\n    pub dgram_recv: usize,\n\n    /// The number of DATAGRAM frames sent.\n    pub dgram_sent: usize,\n\n    /// The estimated round-trip time of the connection.\n    pub rtt: Duration,\n\n    /// The minimum round-trip time observed.\n    pub min_rtt: Option<Duration>,\n\n    /// The maximum round-trip time observed.\n    pub max_rtt: Option<Duration>,\n\n    /// The estimated round-trip time variation in samples using a mean\n    /// variation.\n    pub rttvar: Duration,\n\n    /// The size of the connection's congestion window in bytes.\n    pub cwnd: usize,\n\n    /// The number of sent bytes.\n    pub sent_bytes: u64,\n\n    /// The number of received bytes.\n    pub recv_bytes: u64,\n\n    /// The number of bytes lost.\n    pub lost_bytes: u64,\n\n    /// The number of stream bytes retransmitted.\n    pub stream_retrans_bytes: u64,\n\n    /// The current PMTU for the connection.\n    pub pmtu: usize,\n\n    /// The most recent data delivery rate estimate in bytes/s.\n    ///\n    /// Note that this value could be inaccurate if the application does not\n    /// respect pacing hints (see [`SendInfo.at`] and [Pacing] for more\n    /// details).\n    ///\n    /// [`SendInfo.at`]: struct.SendInfo.html#structfield.at\n    /// [Pacing]: index.html#pacing\n    pub delivery_rate: u64,\n\n    /// The maximum bandwidth estimate for the connection in bytes/s.\n    ///\n    /// Note: not all congestion control algorithms provide this metric;\n    /// it is currently only implemented for bbr2_gcongestion.\n    pub max_bandwidth: Option<u64>,\n\n    /// Statistics from when a CCA first exited the startup phase.\n    pub startup_exit: Option<StartupExit>,\n}\n\nimpl std::fmt::Debug for PathStats {\n    #[inline]\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(\n            f,\n            \"local_addr={:?} peer_addr={:?} \",\n            self.local_addr, self.peer_addr,\n        )?;\n        write!(\n            f,\n            \"validation_state={:?} active={} \",\n            self.validation_state, self.active,\n        )?;\n        write!(\n            f,\n            \"recv={} sent={} lost={} retrans={} rtt={:?} min_rtt={:?} rttvar={:?} cwnd={}\",\n            self.recv, self.sent, self.lost, self.retrans, self.rtt, self.min_rtt, self.rttvar, self.cwnd,\n        )?;\n\n        write!(\n            f,\n            \" sent_bytes={} recv_bytes={} lost_bytes={}\",\n            self.sent_bytes, self.recv_bytes, self.lost_bytes,\n        )?;\n\n        write!(\n            f,\n            \" stream_retrans_bytes={} pmtu={} delivery_rate={}\",\n            self.stream_retrans_bytes, self.pmtu, self.delivery_rate,\n        )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::rand;\n    use crate::MIN_CLIENT_INITIAL_LEN;\n\n    use crate::recovery::RecoveryConfig;\n    use crate::Config;\n\n    use super::*;\n\n    #[test]\n    fn path_validation_limited_mtu() {\n        let client_addr = \"127.0.0.1:1234\".parse().unwrap();\n        let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n        let server_addr = \"127.0.0.1:4321\".parse().unwrap();\n\n        let config = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        let recovery_config = RecoveryConfig::from_config(&config);\n\n        let path = Path::new(\n            client_addr,\n            server_addr,\n            &recovery_config,\n            config.path_challenge_recv_max_queue_len,\n            true,\n            None,\n        );\n        let mut path_mgr = PathMap::new(path, 2, false);\n\n        let probed_path = Path::new(\n            client_addr_2,\n            server_addr,\n            &recovery_config,\n            config.path_challenge_recv_max_queue_len,\n            false,\n            None,\n        );\n        path_mgr.insert_path(probed_path, false).unwrap();\n\n        let pid = path_mgr\n            .path_id_from_addrs(&(client_addr_2, server_addr))\n            .unwrap();\n        path_mgr.get_mut(pid).unwrap().request_validation();\n        assert!(path_mgr.get_mut(pid).unwrap().validation_requested());\n        assert!(path_mgr.get_mut(pid).unwrap().probing_required());\n\n        // Fake sending of PathChallenge in a packet of MIN_CLIENT_INITIAL_LEN - 1\n        // bytes.\n        let data = rand::rand_u64().to_be_bytes();\n        path_mgr.get_mut(pid).unwrap().add_challenge_sent(\n            data,\n            MIN_CLIENT_INITIAL_LEN - 1,\n            Instant::now(),\n        );\n\n        assert!(!path_mgr.get_mut(pid).unwrap().validation_requested());\n        assert!(!path_mgr.get_mut(pid).unwrap().probing_required());\n        assert!(path_mgr.get_mut(pid).unwrap().under_validation());\n        assert!(!path_mgr.get_mut(pid).unwrap().validated());\n        assert_eq!(path_mgr.get_mut(pid).unwrap().state, PathState::Validating);\n        assert_eq!(path_mgr.pop_event(), None);\n\n        // Receives the response. The path is reachable, but the MTU is not\n        // validated yet.\n        path_mgr.on_response_received(data).unwrap();\n\n        assert!(path_mgr.get_mut(pid).unwrap().validation_requested());\n        assert!(path_mgr.get_mut(pid).unwrap().probing_required());\n        assert!(path_mgr.get_mut(pid).unwrap().under_validation());\n        assert!(!path_mgr.get_mut(pid).unwrap().validated());\n        assert_eq!(\n            path_mgr.get_mut(pid).unwrap().state,\n            PathState::ValidatingMTU\n        );\n        assert_eq!(path_mgr.pop_event(), None);\n\n        // Fake sending of PathChallenge in a packet of MIN_CLIENT_INITIAL_LEN\n        // bytes.\n        let data = rand::rand_u64().to_be_bytes();\n        path_mgr.get_mut(pid).unwrap().add_challenge_sent(\n            data,\n            MIN_CLIENT_INITIAL_LEN,\n            Instant::now(),\n        );\n\n        path_mgr.on_response_received(data).unwrap();\n\n        assert!(!path_mgr.get_mut(pid).unwrap().validation_requested());\n        assert!(!path_mgr.get_mut(pid).unwrap().probing_required());\n        assert!(!path_mgr.get_mut(pid).unwrap().under_validation());\n        assert!(path_mgr.get_mut(pid).unwrap().validated());\n        assert_eq!(path_mgr.get_mut(pid).unwrap().state, PathState::Validated);\n        assert_eq!(\n            path_mgr.pop_event(),\n            Some(PathEvent::Validated(client_addr_2, server_addr))\n        );\n    }\n\n    #[test]\n    fn multiple_probes() {\n        let client_addr = \"127.0.0.1:1234\".parse().unwrap();\n        let server_addr = \"127.0.0.1:4321\".parse().unwrap();\n\n        let config = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        let recovery_config = RecoveryConfig::from_config(&config);\n\n        let path = Path::new(\n            client_addr,\n            server_addr,\n            &recovery_config,\n            config.path_challenge_recv_max_queue_len,\n            true,\n            None,\n        );\n        let mut client_path_mgr = PathMap::new(path, 2, false);\n        let mut server_path = Path::new(\n            server_addr,\n            client_addr,\n            &recovery_config,\n            config.path_challenge_recv_max_queue_len,\n            false,\n            None,\n        );\n\n        let client_pid = client_path_mgr\n            .path_id_from_addrs(&(client_addr, server_addr))\n            .unwrap();\n\n        // First probe.\n        let data = rand::rand_u64().to_be_bytes();\n\n        client_path_mgr\n            .get_mut(client_pid)\n            .unwrap()\n            .add_challenge_sent(data, MIN_CLIENT_INITIAL_LEN, Instant::now());\n\n        // Second probe.\n        let data_2 = rand::rand_u64().to_be_bytes();\n\n        client_path_mgr\n            .get_mut(client_pid)\n            .unwrap()\n            .add_challenge_sent(data_2, MIN_CLIENT_INITIAL_LEN, Instant::now());\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            2\n        );\n\n        // If we receive multiple challenges, we can store them.\n        server_path.on_challenge_received(data);\n        assert_eq!(server_path.received_challenges.len(), 1);\n        server_path.on_challenge_received(data_2);\n        assert_eq!(server_path.received_challenges.len(), 2);\n\n        // Response for first probe.\n        client_path_mgr.on_response_received(data).unwrap();\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            1\n        );\n\n        // Response for second probe.\n        client_path_mgr.on_response_received(data_2).unwrap();\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            0\n        );\n    }\n\n    #[test]\n    fn too_many_probes() {\n        let client_addr = \"127.0.0.1:1234\".parse().unwrap();\n        let server_addr = \"127.0.0.1:4321\".parse().unwrap();\n\n        // Default to DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN\n        let config = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        let recovery_config = RecoveryConfig::from_config(&config);\n\n        let path = Path::new(\n            client_addr,\n            server_addr,\n            &recovery_config,\n            config.path_challenge_recv_max_queue_len,\n            true,\n            None,\n        );\n        let mut client_path_mgr = PathMap::new(path, 2, false);\n        let mut server_path = Path::new(\n            server_addr,\n            client_addr,\n            &recovery_config,\n            config.path_challenge_recv_max_queue_len,\n            false,\n            None,\n        );\n\n        let client_pid = client_path_mgr\n            .path_id_from_addrs(&(client_addr, server_addr))\n            .unwrap();\n\n        // First probe.\n        let data = rand::rand_u64().to_be_bytes();\n\n        client_path_mgr\n            .get_mut(client_pid)\n            .unwrap()\n            .add_challenge_sent(data, MIN_CLIENT_INITIAL_LEN, Instant::now());\n\n        // Second probe.\n        let data_2 = rand::rand_u64().to_be_bytes();\n\n        client_path_mgr\n            .get_mut(client_pid)\n            .unwrap()\n            .add_challenge_sent(data_2, MIN_CLIENT_INITIAL_LEN, Instant::now());\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            2\n        );\n\n        // Third probe.\n        let data_3 = rand::rand_u64().to_be_bytes();\n\n        client_path_mgr\n            .get_mut(client_pid)\n            .unwrap()\n            .add_challenge_sent(data_3, MIN_CLIENT_INITIAL_LEN, Instant::now());\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            3\n        );\n\n        // Fourth probe.\n        let data_4 = rand::rand_u64().to_be_bytes();\n\n        client_path_mgr\n            .get_mut(client_pid)\n            .unwrap()\n            .add_challenge_sent(data_4, MIN_CLIENT_INITIAL_LEN, Instant::now());\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            4\n        );\n\n        // If we receive multiple challenges, we can store them up to our queue\n        // size.\n        server_path.on_challenge_received(data);\n        assert_eq!(server_path.received_challenges.len(), 1);\n        server_path.on_challenge_received(data_2);\n        assert_eq!(server_path.received_challenges.len(), 2);\n        server_path.on_challenge_received(data_3);\n        assert_eq!(server_path.received_challenges.len(), 3);\n        server_path.on_challenge_received(data_4);\n        assert_eq!(server_path.received_challenges.len(), 3);\n\n        // Response for first probe.\n        client_path_mgr.on_response_received(data).unwrap();\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            3\n        );\n\n        // Response for second probe.\n        client_path_mgr.on_response_received(data_2).unwrap();\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            2\n        );\n\n        // Response for third probe.\n        client_path_mgr.on_response_received(data_3).unwrap();\n        assert_eq!(\n            client_path_mgr\n                .get(client_pid)\n                .unwrap()\n                .in_flight_challenges\n                .len(),\n            1\n        );\n\n        // There will never be a response for fourth probe...\n    }\n}\n"
  },
  {
    "path": "quiche/src/pmtud.rs",
    "content": "//! Path MTU Discovery ([RFC 8899] DPLPMTUD).\n//!\n//! Discovers the path MTU using loss-based inference: probe packets are sent\n//! and their acknowledgment (or lack thereof) determines path capacity.\n//!\n//! # Algorithm\n//!\n//! Optimistic binary search between [`MIN_PLPMTU`] (1200) and max supported\n//! MTU:\n//! 1. Probe at max MTU\n//! 2. On max_probes consecutive failures, record as smallest failed size\n//! 3. Binary search between largest success and smallest failure\n//! 4. Complete when difference ≤ 1 byte\n//!\n//! A successful probe at any point resets the failure counter and updates\n//! the largest known working size.\n//!\n//! [RFC 8899]: https://datatracker.ietf.org/doc/html/rfc8899\n\n/// Maximum number of probe attempts before treating a size as failed.\n/// https://datatracker.ietf.org/doc/html/rfc8899#section-5.1.2\npub(crate) const MAX_PROBES_DEFAULT: u8 = 3;\n\n/// Min Packetization Layer Path MTU (PLPMTU).\n/// https://datatracker.ietf.org/doc/html/rfc8899#section-5.1.2\n/// For QUIC, this is 1200 bytes per https://datatracker.ietf.org/doc/html/rfc9000#section-14.1\nconst MIN_PLPMTU: usize = crate::MIN_CLIENT_INITIAL_LEN;\n\n#[derive(Default)]\npub struct Pmtud {\n    /// The PMTU after the completion of PMTUD.\n    /// Will be [`None`] if the PMTU is less than the minimum supported MTU.\n    pmtu: Option<usize>,\n\n    /// The current PMTUD probe size. Set to maximum_supported_mtu at\n    /// initialization.\n    probe_size: usize,\n\n    /// The maximum supported MTU.\n    maximum_supported_mtu: usize,\n\n    /// The size of the smallest failed probe.\n    smallest_failed_probe_size: Option<usize>,\n\n    /// The size of the largest successful probe.\n    largest_successful_probe_size: Option<usize>,\n\n    /// Indicates if a PMTUD probe is in flight. Used to limit probes to 1/RTT.\n    in_flight: bool,\n\n    /// The number of times the current probe size has failed.\n    probe_failure_count: u8,\n\n    /// The maximum number of failed probe attempts before treating a size as\n    /// failed.\n    max_probes: u8,\n}\n\nimpl Pmtud {\n    /// Creates new PMTUD instance.\n    ///\n    /// If `max_probes` is 0, uses the default value of [`MAX_PROBES_DEFAULT`].\n    pub fn new(maximum_supported_mtu: usize, max_probes: u8) -> Self {\n        let max_probes = if max_probes == 0 {\n            warn!(\n                \"max_probes is 0, using default value {}\",\n                MAX_PROBES_DEFAULT\n            );\n            MAX_PROBES_DEFAULT\n        } else {\n            max_probes\n        };\n\n        Self {\n            maximum_supported_mtu,\n            probe_size: maximum_supported_mtu,\n            max_probes,\n            ..Default::default()\n        }\n    }\n\n    /// Indicates whether probing should continue on the connection.\n    ///\n    /// Checks there are no probes in flight, that a PMTU has not been\n    /// found, and that the minimum supported MTU has not been reached.\n    pub fn should_probe(&self) -> bool {\n        !self.in_flight &&\n            self.pmtu.is_none() &&\n            self.smallest_failed_probe_size != Some(MIN_PLPMTU)\n    }\n\n    /// Sets the PMTUD probe size.\n    fn set_probe_size(&mut self, probe_size: usize) {\n        self.probe_size = std::cmp::min(probe_size, self.maximum_supported_mtu);\n    }\n\n    /// Returns the PMTUD probe size.\n    pub fn get_probe_size(&self) -> usize {\n        self.probe_size\n    }\n\n    /// Returns the largest successful PMTUD probe size if one exists, otherwise\n    /// returns the minimum supported MTU.\n    pub fn get_current_mtu(&self) -> usize {\n        self.largest_successful_probe_size.unwrap_or(MIN_PLPMTU)\n    }\n\n    /// Returns the PMTU.\n    pub fn get_pmtu(&self) -> Option<usize> {\n        self.pmtu\n    }\n\n    /// Selects PMTU probe size based on the binary search algorithm.\n    ///\n    /// Based on the Optimistic Binary algorithm defined in:\n    /// Ref: <https://www.hb.fh-muenster.de/opus4/frontdoor/deliver/index/docId/14965/file/dplpmtudQuicPaper.pdf>\n    fn update_probe_size(&mut self) {\n        match (\n            self.smallest_failed_probe_size,\n            self.largest_successful_probe_size,\n        ) {\n            // Binary search between successful and failed probes\n            (Some(failed_probe_size), Some(successful_probe_size)) => {\n                // Something has changed along the path that invalidates\n                // previous PMTUD probes. Restart PMTUD\n                if failed_probe_size <= successful_probe_size {\n                    warn!(\n                        \"Inconsistent PMTUD probing results. Restarting PMTUD. \\\n                        failed_probe_size: {failed_probe_size}, \\\n                        successful_probe_size: {successful_probe_size}\",\n                    );\n\n                    return self.restart_pmtud();\n                }\n\n                // Found the PMTU\n                if failed_probe_size - successful_probe_size <= 1 {\n                    debug!(\"Found PMTU: {successful_probe_size}\");\n                    self.set_pmtu(successful_probe_size);\n                } else {\n                    self.probe_size =\n                        (successful_probe_size + failed_probe_size) / 2\n                }\n            },\n\n            // With only failed probes, binary search between the smallest failed\n            // probe and the minimum supported MTU\n            (Some(failed_probe_size), None) =>\n                self.probe_size = (MIN_PLPMTU + failed_probe_size) / 2,\n\n            // As the algorithm is optimistic in that the initial probe size\n            // is the maximum supported MTU, then having only a successful probe\n            // means the maximum supported MTU is <= PMTU\n            (None, Some(successful_probe_size)) => {\n                self.set_pmtu(successful_probe_size);\n            },\n\n            // Use the initial probe size if no record of success/failures\n            (None, None) => self.probe_size = self.maximum_supported_mtu,\n        }\n    }\n\n    /// Sets whether a probe is currently in flight for this connection.\n    pub fn set_in_flight(&mut self, in_flight: bool) {\n        self.in_flight = in_flight;\n    }\n\n    /// Records a successful probe and returns the largest successful probe size\n    pub fn successful_probe(&mut self, probe_size: usize) -> Option<usize> {\n        self.probe_failure_count = 0;\n\n        self.largest_successful_probe_size = std::cmp::max(\n            // make sure we don't exceed the maximum supported MTU\n            Some(probe_size.min(self.maximum_supported_mtu)),\n            self.largest_successful_probe_size,\n        );\n\n        self.update_probe_size();\n        self.in_flight = false;\n\n        self.largest_successful_probe_size\n    }\n\n    /// Records a failed probe\n    pub fn failed_probe(&mut self, probe_size: usize) {\n        // Treat errant probes as if they failed at the minimum supported MTU\n        let probe_size = std::cmp::max(probe_size, MIN_PLPMTU);\n        self.probe_failure_count += 1;\n\n        if self.probe_failure_count < self.max_probes {\n            debug!(\n                \"Probe size {} failed ({}/{}), will retry\",\n                probe_size, self.probe_failure_count, self.max_probes\n            );\n            self.in_flight = false;\n            return;\n        }\n\n        debug!(\n            \"Probe size {} failed {} times, treating as MTU limitation\",\n            probe_size, self.probe_failure_count\n        );\n\n        // Check if we have one instance of a failed probe so that a min\n        // comparison can be made otherwise if this is the first failed\n        // probe just record it\n        self.smallest_failed_probe_size = Some(\n            self.smallest_failed_probe_size\n                .map_or(probe_size, |s| s.min(probe_size)),\n        );\n\n        self.probe_failure_count = 0;\n        self.update_probe_size();\n        self.in_flight = false;\n    }\n\n    // Resets PMTUD internals such that PMTUD will be recalculated\n    // on the next opportunity\n    fn restart_pmtud(&mut self) {\n        self.set_probe_size(self.maximum_supported_mtu);\n        self.smallest_failed_probe_size = None;\n        self.largest_successful_probe_size = None;\n        self.pmtu = None;\n        self.probe_failure_count = 0;\n    }\n\n    // Checks that a probe of PMTU size can be ack'd by enabling\n    // a probe on the next opportunity. If this probe is dropped\n    // PMTUD will restart from a fresh state\n    pub fn revalidate_pmtu(&mut self) {\n        if let Some(pmtu) = self.pmtu {\n            self.set_probe_size(pmtu);\n            self.pmtu = None;\n            self.probe_failure_count = 0;\n            self.largest_successful_probe_size = None;\n        }\n    }\n\n    fn set_pmtu(&mut self, successful_probe_size: usize) {\n        self.pmtu = Some(successful_probe_size);\n        self.probe_size = successful_probe_size;\n        self.probe_failure_count = 0;\n    }\n}\n\nimpl std::fmt::Debug for Pmtud {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"pmtu={:?} \", self.pmtu)?;\n        write!(f, \"probe_size={:?} \", self.probe_size)?;\n        write!(f, \"should_probe={:?} \", self.should_probe())?;\n        write!(\n            f,\n            \"failures={}/{} \",\n            self.probe_failure_count, self.max_probes\n        )?;\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn pmtud_initial_state() {\n        let pmtud = Pmtud::new(1350, 1);\n        assert_eq!(pmtud.get_current_mtu(), 1200);\n        assert_eq!(pmtud.get_probe_size(), 1350);\n        assert!(pmtud.should_probe());\n    }\n\n    #[test]\n    fn pmtud_max_probes_zero_uses_default() {\n        let pmtud = Pmtud::new(1500, 0);\n        assert_eq!(pmtud.max_probes, MAX_PROBES_DEFAULT);\n    }\n\n    #[test]\n    fn pmtud_max_probes_set_to_provided_value() {\n        let pmtud = Pmtud::new(1500, 5);\n        assert_eq!(pmtud.max_probes, 5);\n        assert_ne!(pmtud.max_probes, MAX_PROBES_DEFAULT);\n    }\n\n    #[test]\n    fn pmtud_binary_search_algorithm() {\n        let mut pmtud = Pmtud::new(1500, 1);\n\n        // Set initial probe size to 1500\n        assert_eq!(pmtud.get_probe_size(), 1500);\n\n        // Simulate probe loss - should update to midpoint\n        pmtud.failed_probe(1500);\n        // Expected: 1200 + ((1500 - 1200) / 2) = 1200 + 150 = 1350\n        assert_eq!(pmtud.get_probe_size(), 1350);\n\n        // Another probe loss\n        pmtud.failed_probe(1350);\n        // Expected: 1200 + ((1350 - 1200) / 2) = 1200 + 75 = 1275\n        assert_eq!(pmtud.get_probe_size(), 1275);\n\n        pmtud.failed_probe(1275);\n        // Expected: 1200 + ((1275 - 1200) / 2) = 1200 + 37 = 1237\n        assert_eq!(pmtud.get_probe_size(), 1237);\n\n        pmtud.failed_probe(1237);\n        // Expected: 1200 + ((1237 - 1200) / 2) = 1200 + 18 = 1218\n        assert_eq!(pmtud.get_probe_size(), 1218);\n\n        pmtud.failed_probe(1218);\n        // Expected: 1200 + ((1218 - 1200) / 2) = 1200 + 9 = 1209\n        assert_eq!(pmtud.get_probe_size(), 1209);\n\n        pmtud.failed_probe(1209);\n        // Expected: 1200 + ((1209 - 1200) / 2) = 1200 + 4 = 1204\n        assert_eq!(pmtud.get_probe_size(), 1204);\n\n        pmtud.failed_probe(1204);\n        // Expected: 1200 + ((1204 - 1200) / 2) = 1200 + 2 = 1202\n        assert_eq!(pmtud.get_probe_size(), 1202);\n\n        pmtud.failed_probe(1202);\n        // Expected: 1200 + ((1202 - 1200) / 2) = 1200 + 1 = 1201\n        assert_eq!(pmtud.get_probe_size(), 1201);\n\n        pmtud.failed_probe(1201);\n        // Expected: 1200 + ((1201 - 1200) / 2) = 1200 + 0 = 1200\n        assert_eq!(pmtud.get_probe_size(), 1200);\n    }\n\n    #[test]\n    fn pmtud_successful_probe() {\n        let mut pmtud = Pmtud::new(1400, 1);\n\n        // Simulate successful probe\n        pmtud.successful_probe(1400);\n\n        assert_eq!(pmtud.get_current_mtu(), 1400);\n    }\n\n    /// Test case for resetting the PMTUD state.\n    ///\n    /// This test initializes the PMTUD instance, performs a successful probe,\n    /// recalculates the PMTU, and then uses the `pmtud_test_runner` function\n    /// to verify the PMTU discovery process.\n    #[test]\n    fn test_pmtud_reset() {\n        let mut pmtud = Pmtud::new(1350, 1);\n        pmtud.successful_probe(1350);\n        assert_eq!(pmtud.pmtu, Some(1350));\n        assert!(!pmtud.should_probe());\n\n        // Restart PMTUD and expect the state to reset\n        pmtud.restart_pmtud();\n\n        // Run the PMTUD test runner with the reset state\n        pmtud_test_runner(&mut pmtud, 1237);\n    }\n\n    /// Test case for receiving a probe outside the defined supported MTU range.\n    #[test]\n    fn test_pmtud_errant_probe() {\n        let mut pmtud = Pmtud::new(1350, 1);\n        pmtud.successful_probe(1500);\n        // Even though we've received a probe larger than supported\n        // maximum MTU, the PMTU should still respect the configured maximum\n        assert_eq!(pmtud.pmtu, Some(1350));\n        assert!(!pmtud.should_probe());\n\n        pmtud.restart_pmtud();\n\n        // A failed probe of a value less than the minimum supported MTU\n        // should stop probing\n        pmtud.failed_probe(1100);\n        assert_eq!(pmtud.pmtu, None);\n        assert_eq!(pmtud.get_probe_size(), 1200);\n        assert!(!pmtud.should_probe());\n    }\n\n    /// Test case for PMTU equal to the minimum supported MTU.\n    ///\n    /// This test verifies that the PMTU discovery process correctly identifies\n    /// when the PMTU is equal to the minimum supported MTU.\n    #[test]\n    fn test_pmtu_equal_to_min_supported_mtu() {\n        let mut pmtud = Pmtud::new(1350, 1);\n        pmtud_test_runner(&mut pmtud, 1200);\n    }\n\n    /// Test case for PMTU greater than the minimum supported MTU.\n    ///\n    /// This test verifies that the PMTU discovery process correctly identifies\n    /// when the PMTU is greater than the minimum supported MTU.\n    #[test]\n    fn test_pmtu_greater_than_min_supported_mtu() {\n        let mut pmtud = Pmtud::new(1350, 1);\n        pmtud_test_runner(&mut pmtud, 1500);\n    }\n\n    /// Test case for PMTU less than the minimum supported MTU.\n    ///\n    /// This test verifies that the PMTU discovery process correctly handles\n    /// the case when the PMTU is less than the minimum supported MTU.\n    #[test]\n    fn test_pmtu_less_than_min_supported_mtu() {\n        let mut pmtud = Pmtud::new(1350, 1);\n        pmtud_test_runner(&mut pmtud, 1100);\n    }\n\n    /// Test case for PMTU revalidation.\n    ///\n    /// This test verifies that the PMTU recalculation logic correctly resets\n    /// the PMTUD state and identifies the correct PMTU after a failed\n    /// validation probe.\n    #[test]\n    fn test_pmtu_revalidation() {\n        let mut pmtud = Pmtud::new(1350, 1);\n        pmtud.set_probe_size(1350);\n        pmtud.successful_probe(1350);\n\n        // Simulate a case where an established PMTU probe is dropped repeatedly\n        pmtud.revalidate_pmtu();\n        fail_probe_max_times(&mut pmtud, 1350);\n\n        // Run the PMTUD test runner with the reset state\n        pmtud_test_runner(&mut pmtud, 1250);\n    }\n\n    #[test]\n    fn pmtud_revalidation_tolerates_random_packet_loss() {\n        let mut pmtud = Pmtud::new(1500, MAX_PROBES_DEFAULT);\n\n        pmtud.successful_probe(1500);\n        assert_eq!(pmtud.get_pmtu(), Some(1500));\n\n        pmtud.revalidate_pmtu();\n        assert_eq!(pmtud.get_pmtu(), None);\n        assert!(pmtud.largest_successful_probe_size.is_none());\n\n        pmtud.failed_probe(1500);\n        assert_eq!(pmtud.probe_failure_count, 1);\n        assert!(pmtud.pmtu.is_none());\n\n        pmtud.failed_probe(1500);\n        assert_eq!(pmtud.probe_failure_count, 2);\n\n        pmtud.successful_probe(1500);\n        assert_eq!(pmtud.get_pmtu(), Some(1500));\n        assert_eq!(pmtud.probe_failure_count, 0);\n    }\n\n    /// Test that when revalidating PMTU, if the revalidation probe fails,\n    /// PMTUD should binary search down, not restart.\n    #[test]\n    fn pmtud_revalidation_failure_binary_searches_not_restarts() {\n        let mut pmtud = Pmtud::new(1500, 1);\n\n        pmtud.successful_probe(1500);\n        assert_eq!(pmtud.get_pmtu(), Some(1500));\n\n        // Revalidation clears largest_successful_probe_size\n        pmtud.revalidate_pmtu();\n        assert!(pmtud.largest_successful_probe_size.is_none());\n\n        // Revalidation probe fails - should binary search down, not restart\n        pmtud.failed_probe(1500);\n\n        assert_eq!(pmtud.smallest_failed_probe_size, Some(1500));\n        assert!(pmtud.largest_successful_probe_size.is_none());\n        assert_eq!(pmtud.get_probe_size(), 1350); // (1200 + 1500) / 2\n    }\n\n    #[test]\n    fn pmtud_tolerates_initial_packet_loss() {\n        let mut pmtud = Pmtud::new(1500, MAX_PROBES_DEFAULT);\n\n        pmtud.failed_probe(1500);\n        assert_eq!(pmtud.probe_failure_count, 1);\n        assert!(pmtud.smallest_failed_probe_size.is_none());\n\n        pmtud.failed_probe(1500);\n        assert_eq!(pmtud.probe_failure_count, 2);\n        assert!(pmtud.smallest_failed_probe_size.is_none());\n\n        pmtud.successful_probe(1500);\n        assert_eq!(pmtud.get_pmtu(), Some(1500));\n        assert_eq!(pmtud.probe_failure_count, 0);\n    }\n\n    #[test]\n    fn pmtud_confirms_failure_after_max_probes() {\n        let mut pmtud = Pmtud::new(1500, 1);\n\n        pmtud.failed_probe(1500);\n\n        assert_eq!(pmtud.smallest_failed_probe_size, Some(1500));\n        assert!(pmtud.pmtu.is_none());\n        assert!(pmtud.get_probe_size() < 1500);\n        assert!(pmtud.get_probe_size() >= MIN_PLPMTU);\n    }\n\n    #[test]\n    fn pmtud_binary_search_no_slowdown() {\n        let mut pmtud = Pmtud::new(1500, 2);\n\n        fail_probe_max_times(&mut pmtud, 1500);\n        assert!(pmtud.pmtu.is_none());\n\n        let search_size_1 = pmtud.get_probe_size();\n        assert!(search_size_1 < 1500);\n\n        pmtud.successful_probe(search_size_1);\n        assert_eq!(pmtud.probe_failure_count, 0);\n\n        let search_size_2 = pmtud.get_probe_size();\n        pmtud.failed_probe(search_size_2);\n\n        assert!(pmtud.pmtu.is_none());\n        assert_eq!(pmtud.probe_failure_count, 1);\n    }\n\n    /// Test convergence to correct MTU with intermittent packet loss.\n    ///\n    /// Simulates a scenario where the first probe at each size fails but the\n    /// second succeeds (random loss, not MTU limitation). Verifies that:\n    /// 1. probe_failure_count resets to 0 on success\n    /// 2. probe_failure_count resets to 0 when probe size changes\n    /// 3. Algorithm converges to the correct MTU of 1337\n    #[test]\n    fn pmtud_convergence_with_intermittent_loss() {\n        let mut pmtud = Pmtud::new(1500, 3);\n        let target_mtu = 1337;\n\n        while pmtud.get_pmtu().is_none() {\n            let probe_size = pmtud.get_probe_size();\n\n            if probe_size <= target_mtu {\n                // First probe fails (random loss)\n                pmtud.failed_probe(probe_size);\n                assert_eq!(pmtud.probe_failure_count, 1);\n\n                // Second probe succeeds\n                pmtud.successful_probe(probe_size);\n                assert_eq!(pmtud.probe_failure_count, 0); // Reset on success\n            } else {\n                // Size exceeds MTU - all probes fail\n                let old_probe_size = probe_size;\n                fail_probe_max_times(&mut pmtud, probe_size);\n\n                // After max failures, probe_failure_count resets and size changes\n                assert_eq!(pmtud.probe_failure_count, 0);\n                if pmtud.get_pmtu().is_none() {\n                    assert!(pmtud.get_probe_size() < old_probe_size);\n                }\n            }\n        }\n\n        assert_eq!(pmtud.get_pmtu(), Some(target_mtu));\n    }\n\n    #[test]\n    fn pmtud_failure_at_min_plpmtu() {\n        let mut pmtud = Pmtud::new(1500, MAX_PROBES_DEFAULT);\n\n        pmtud.failed_probe(100);\n        pmtud.failed_probe(100);\n        pmtud.failed_probe(100);\n\n        assert_eq!(pmtud.smallest_failed_probe_size, Some(MIN_PLPMTU));\n    }\n\n    #[test]\n    fn pmtud_in_flight_cleared_on_all_outcomes() {\n        let mut pmtud = Pmtud::new(1500, 1);\n\n        pmtud.set_in_flight(true);\n        assert!(pmtud.in_flight);\n\n        pmtud.failed_probe(1500);\n        assert!(!pmtud.in_flight);\n\n        pmtud.set_in_flight(true);\n\n        pmtud.successful_probe(1500);\n        assert!(!pmtud.in_flight);\n    }\n\n    #[test]\n    fn pmtud_update_probe_size_initial_state() {\n        let mut pmtud = Pmtud::new(1500, 1);\n\n        // Manually set probe_size to something else to verify update_probe_size\n        // resets it\n        pmtud.probe_size = 1200;\n\n        // With no successful or failed probes, should reset to\n        // maximum_supported_mtu\n        pmtud.update_probe_size();\n\n        assert_eq!(pmtud.probe_size, 1500);\n    }\n\n    // Test utilities\n\n    fn fail_probe_max_times(pmtud: &mut Pmtud, size: usize) {\n        for _ in 0..pmtud.max_probes {\n            pmtud.failed_probe(size);\n        }\n    }\n\n    /// Runs a test for the PMTUD algorithm, given a target PMTU `target_mtu`.\n    ///\n    /// The test iteratively sends probes until the PMTU is found or the minimum\n    /// supported MTU is reached. Verifies that the PMTU is equal to the target\n    /// PMTU.\n    fn pmtud_test_runner(pmtud: &mut Pmtud, test_pmtu: usize) {\n        // Loop until the PMTU is found or the minimum supported MTU is reached\n        while pmtud.get_probe_size() >= MIN_PLPMTU {\n            // Send a probe with the current probe size\n            let probe_size = pmtud.get_probe_size();\n\n            if probe_size <= test_pmtu {\n                pmtud.successful_probe(probe_size);\n            } else {\n                fail_probe_max_times(pmtud, probe_size);\n            }\n\n            // Update the probe size based on the result\n            pmtud.update_probe_size();\n\n            // If the probe size hasn't changed and is equal to the minimum\n            // supported MTU, break the loop\n            if pmtud.get_probe_size() == probe_size && probe_size == MIN_PLPMTU {\n                break;\n            }\n\n            // If the PMTU is found, break the loop\n            if pmtud.get_pmtu().is_some() {\n                break;\n            }\n        }\n\n        // Verify that the PMTU is correct\n        if test_pmtu < MIN_PLPMTU {\n            assert_eq!(pmtud.get_pmtu(), None);\n        } else if test_pmtu > pmtud.maximum_supported_mtu {\n            assert_eq!(pmtud.get_pmtu(), Some(pmtud.maximum_supported_mtu));\n        } else {\n            assert_eq!(pmtud.get_pmtu(), Some(test_pmtu));\n        }\n    }\n}\n"
  },
  {
    "path": "quiche/src/rand.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\npub fn rand_bytes(buf: &mut [u8]) {\n    unsafe {\n        RAND_bytes(buf.as_mut_ptr(), buf.len());\n    }\n}\n\npub fn rand_u8() -> u8 {\n    let mut buf = [0; 1];\n\n    rand_bytes(&mut buf);\n\n    buf[0]\n}\n\npub fn rand_u64() -> u64 {\n    let mut buf = [0; 8];\n\n    rand_bytes(&mut buf);\n\n    u64::from_ne_bytes(buf)\n}\n\npub fn rand_u64_uniform(max: u64) -> u64 {\n    let chunk_size = u64::MAX / max;\n    let end_of_last_chunk = chunk_size * max;\n\n    let mut r = rand_u64();\n\n    while r >= end_of_last_chunk {\n        r = rand_u64();\n    }\n\n    r / chunk_size\n}\n\nextern \"C\" {\n    fn RAND_bytes(buf: *mut u8, len: libc::size_t) -> libc::c_int;\n}\n"
  },
  {
    "path": "quiche/src/range_buf.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::cmp;\nuse std::marker::PhantomData;\nuse std::ops::Deref;\n\nuse crate::buffers::BufFactory;\nuse crate::buffers::DefaultBufFactory;\n\n/// Buffer holding data at a specific offset.\n///\n/// The data is stored in a `Vec<u8>` in such a way that it can be shared\n/// between multiple `RangeBuf` objects.\n///\n/// Each `RangeBuf` will have its own view of that buffer, where the `start`\n/// value indicates the initial offset within the `Vec`, and `len` indicates the\n/// number of bytes, starting from `start` that are included.\n///\n/// In addition, `pos` indicates the current offset within the `Vec`, starting\n/// from the very beginning of the `Vec`.\n///\n/// Finally, `off` is the starting offset for the specific `RangeBuf` within the\n/// stream the buffer belongs to.\n#[derive(Clone, Debug, Default)]\npub struct RangeBuf<F = DefaultBufFactory>\nwhere\n    F: BufFactory,\n{\n    /// The internal buffer holding the data.\n    ///\n    /// To avoid needless allocations when a RangeBuf is split, this field\n    /// should be reference-counted so it can be shared between multiple\n    /// RangeBuf objects, and sliced using the `start` and `len` values.\n    pub(crate) data: F::Buf,\n\n    /// The initial offset within the internal buffer.\n    pub(crate) start: usize,\n\n    /// The current offset within the internal buffer.\n    pub(crate) pos: usize,\n\n    /// The number of bytes in the buffer, from the initial offset.\n    pub(crate) len: usize,\n\n    /// The offset of the buffer within a stream.\n    pub(crate) off: u64,\n\n    /// Whether this contains the final byte in the stream.\n    pub(crate) fin: bool,\n\n    _bf: PhantomData<F>,\n}\n\nimpl<F: BufFactory> RangeBuf<F>\nwhere\n    F::Buf: Clone,\n{\n    /// Creates a new `RangeBuf` from the given slice.\n    pub fn from(buf: &[u8], off: u64, fin: bool) -> RangeBuf<F> {\n        Self::from_raw(F::buf_from_slice(buf), off, fin)\n    }\n\n    pub fn from_raw(data: F::Buf, off: u64, fin: bool) -> RangeBuf<F> {\n        RangeBuf {\n            len: data.as_ref().len(),\n            data,\n            start: 0,\n            pos: 0,\n            off,\n            fin,\n            _bf: Default::default(),\n        }\n    }\n\n    /// Returns whether `self` holds the final offset in the stream.\n    pub fn fin(&self) -> bool {\n        self.fin\n    }\n\n    /// Returns the starting offset of `self`.\n    pub fn off(&self) -> u64 {\n        (self.off - self.start as u64) + self.pos as u64\n    }\n\n    /// Returns the final offset of `self`.\n    pub fn max_off(&self) -> u64 {\n        self.off() + self.len() as u64\n    }\n\n    /// Returns the length of `self`.\n    pub fn len(&self) -> usize {\n        self.len - (self.pos - self.start)\n    }\n\n    /// Returns true if `self` has a length of zero bytes.\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    /// Consumes the starting `count` bytes of `self`.\n    pub fn consume(&mut self, count: usize) {\n        self.pos += count;\n    }\n\n    /// Splits the buffer into two at the given index.\n    pub fn split_off(&mut self, at: usize) -> RangeBuf<F>\n    where\n        F::Buf: Clone + AsRef<[u8]>,\n    {\n        assert!(\n            at <= self.len,\n            \"`at` split index (is {}) should be <= len (is {})\",\n            at,\n            self.len\n        );\n\n        let buf = RangeBuf {\n            data: self.data.clone(),\n            start: self.start + at,\n            pos: cmp::max(self.pos, self.start + at),\n            len: self.len - at,\n            off: self.off + at as u64,\n            _bf: Default::default(),\n            fin: self.fin,\n        };\n\n        self.pos = cmp::min(self.pos, self.start + at);\n        self.len = at;\n        self.fin = false;\n\n        buf\n    }\n}\n\nimpl<F: BufFactory> Deref for RangeBuf<F> {\n    type Target = [u8];\n\n    fn deref(&self) -> &[u8] {\n        &self.data.as_ref()[self.pos..self.start + self.len]\n    }\n}\n\nimpl<F: BufFactory> Ord for RangeBuf<F> {\n    fn cmp(&self, other: &RangeBuf<F>) -> cmp::Ordering {\n        // Invert ordering to implement min-heap.\n        self.off.cmp(&other.off).reverse()\n    }\n}\n\nimpl<F: BufFactory> PartialOrd for RangeBuf<F> {\n    fn partial_cmp(&self, other: &RangeBuf<F>) -> Option<cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl<F: BufFactory> Eq for RangeBuf<F> {}\n\nimpl<F: BufFactory> PartialEq for RangeBuf<F> {\n    fn eq(&self, other: &RangeBuf<F>) -> bool {\n        self.off == other.off\n    }\n}\n"
  },
  {
    "path": "quiche/src/ranges.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::cmp;\nuse std::iter::FromIterator;\nuse std::ops::Range;\n\nuse std::collections::BTreeMap;\nuse std::collections::Bound;\n\nuse either::Either;\nuse smallvec::SmallVec;\n\nconst MAX_INLINE_CAPACITY: usize = 4;\nconst MIN_TO_INLINE: usize = 2;\n\n/// A sorted collection of non overlapping [`u64`] ranges\n#[derive(Clone, PartialEq, Eq, PartialOrd)]\npub enum RangeSet {\n    Inline(InlineRangeSet),\n    BTree(BTreeRangeSet),\n}\n\n/// A [`RangeSet`] variant backed by a [`SmallVec`] that is capable of storing\n/// [`MAX_INLINE_CAPACITY`] of ranges without allocation\n#[derive(Clone, PartialEq, Eq, PartialOrd)]\npub struct InlineRangeSet {\n    inner: SmallVec<[(u64, u64); MAX_INLINE_CAPACITY]>,\n    capacity: usize,\n}\n\n/// A [`RangeSet`] variant backed by a [`BTreeMap`] that is capable of storing\n/// an arbitrary number of ranges\n#[derive(Clone, PartialEq, Eq, PartialOrd)]\npub struct BTreeRangeSet {\n    inner: BTreeMap<u64, u64>,\n    capacity: usize,\n}\n\nimpl RangeSet {\n    /// Create a new [`RangeSet`].\n    ///\n    /// When the length of a [`RangeSet`] overflows `capacity` it will remove\n    /// the smallest range.\n    pub fn new(capacity: usize) -> Self {\n        RangeSet::Inline(InlineRangeSet {\n            inner: Default::default(),\n            capacity,\n        })\n    }\n\n    /// The number of nonoverlapping ranges stored in this [`RangeSet`].\n    pub fn len(&self) -> usize {\n        match self {\n            RangeSet::Inline(set) => set.inner.len(),\n            RangeSet::BTree(set) => set.inner.len(),\n        }\n    }\n\n    /// Converts the inner representation from a BTree to Inline and vice versa\n    /// when the proper conditions are met. Keeps the stored data intact.\n    #[inline(always)]\n    fn fixup(&mut self) {\n        match self {\n            RangeSet::Inline(set) if set.inner.len() == MAX_INLINE_CAPACITY => {\n                let old_inner = std::mem::take(&mut set.inner);\n                *self = RangeSet::BTree(BTreeRangeSet {\n                    inner: old_inner.into_inner().expect(\"At capacity\").into(),\n                    capacity: set.capacity,\n                });\n            },\n\n            RangeSet::BTree(set) if set.inner.len() <= MIN_TO_INLINE => {\n                let old_inner = std::mem::take(&mut set.inner);\n                *self = RangeSet::Inline(InlineRangeSet {\n                    inner: SmallVec::from_iter(old_inner),\n                    capacity: set.capacity,\n                })\n            },\n\n            _ => {},\n        }\n    }\n\n    /// Insert a new [`Range`] into the collection.\n    ///\n    /// If the [`Range`] overlaps with any existing range, it may be merged with\n    /// one or more other [`Range`]s. If following the insertion the number of\n    /// stored ranges overflows capacity, the smalles range will be removed.\n    #[inline]\n    pub fn insert(&mut self, item: Range<u64>) {\n        match self {\n            RangeSet::Inline(set) => set.insert(item),\n            RangeSet::BTree(set) => set.insert(item),\n        }\n\n        self.fixup();\n    }\n\n    /// Iterate over the stored ranges in incremental order.\n    pub fn iter(\n        &self,\n    ) -> impl DoubleEndedIterator<Item = Range<u64>> + ExactSizeIterator + '_\n    {\n        match self {\n            RangeSet::BTree(set) =>\n                Either::Left(set.inner.iter().map(|(k, v)| *k..*v)),\n\n            RangeSet::Inline(set) =>\n                Either::Right(set.inner.iter().map(|(s, e)| *s..*e)),\n        }\n    }\n\n    /// Iterate over every single [`u64`] value covered by the ranges in this\n    /// [`RangeSet`] in incremental order.\n    #[cfg(test)]\n    pub fn flatten(&self) -> impl DoubleEndedIterator<Item = u64> + '_ {\n        match self {\n            RangeSet::BTree(set) =>\n                Either::Left(set.inner.iter().flat_map(|(k, v)| *k..*v)),\n\n            RangeSet::Inline(set) =>\n                Either::Right(set.inner.iter().flat_map(|(s, e)| *s..*e)),\n        }\n    }\n\n    /// The smallest value covered by ranges in this collection.\n    #[cfg(test)]\n    pub fn first(&self) -> Option<u64> {\n        match self {\n            RangeSet::Inline(set) => set.inner.first().map(|(s, _)| *s),\n\n            RangeSet::BTree(set) => set.inner.first_key_value().map(|(k, _)| *k),\n        }\n    }\n\n    /// The largest value covered by ranges in this collection.\n    pub fn last(&self) -> Option<u64> {\n        match self {\n            RangeSet::Inline(set) => set.inner.last().map(|(_, e)| *e - 1),\n\n            RangeSet::BTree(set) =>\n                set.inner.last_key_value().map(|(_, v)| *v - 1),\n        }\n    }\n\n    #[inline]\n    pub fn remove_until(&mut self, largest: u64) {\n        match self {\n            RangeSet::Inline(set) => set.remove_until(largest),\n            RangeSet::BTree(set) => set.remove_until(largest),\n        }\n\n        self.fixup();\n    }\n\n    pub fn push_item(&mut self, item: u64) {\n        self.insert(item..item + 1)\n    }\n}\n\nimpl InlineRangeSet {\n    fn insert(&mut self, item: Range<u64>) {\n        let start = item.start;\n        let mut end = item.end;\n        let mut pos = 0;\n\n        loop {\n            match self.inner.get_mut(pos) {\n                Some((s, e)) => {\n                    if start > *e {\n                        // Skip while start is greater than end\n                        pos += 1;\n                        continue;\n                    }\n\n                    if end < *s {\n                        // Inserted range is entirely before this range. Insert\n                        // and return.\n                        if self.inner.len() == self.capacity {\n                            self.inner.remove(0);\n                            pos -= 1;\n                        }\n\n                        self.inner.insert(pos, (start, end));\n                        return;\n                    }\n\n                    // At this point we know (start <= *e)\n                    if start < *s {\n                        // We know we are completely past the previous range, so\n                        // we can simply adjust the lower bound\n                        *s = start;\n                    }\n\n                    if end > *e {\n                        // We adjusted the upper bound of an existing range, we\n                        // must now check it does not overlap with the next range\n                        *e = end;\n                        break;\n                    } else {\n                        return;\n                    }\n                },\n\n                None => {\n                    if self.inner.len() == self.capacity {\n                        self.inner.remove(0);\n                    }\n\n                    self.inner.push((start, end));\n                    return;\n                },\n            }\n        }\n\n        // Merge any newly overlapping ranges\n        while let Some((s, e)) = self.inner.get(pos + 1).copied() {\n            if end < s {\n                // We are done, since the next range is completely disjoint\n                break;\n            }\n\n            let new_e = e.max(end);\n            self.inner[pos].1 = new_e;\n            end = new_e;\n            self.inner.remove(pos + 1);\n        }\n    }\n\n    fn remove_until(&mut self, largest: u64) {\n        while let Some((s, e)) = self.inner.first_mut() {\n            if largest >= *e {\n                self.inner.remove(0);\n                continue;\n            }\n\n            *s = (largest + 1).max(*s);\n            if *s == *e {\n                self.inner.remove(0);\n            }\n\n            break;\n        }\n    }\n}\n\nimpl BTreeRangeSet {\n    // TODO: use RangeInclusive\n    fn insert(&mut self, item: Range<u64>) {\n        let mut start = item.start;\n        let mut end = item.end;\n\n        // Check if preceding existing range overlaps with the new one.\n        if let Some(r) = self.prev_to(start) {\n            // New range overlaps with existing range in the set, merge them.\n            if range_overlaps(&r, &item) {\n                self.inner.remove(&r.start);\n\n                start = cmp::min(start, r.start);\n                end = cmp::max(end, r.end);\n            }\n        }\n\n        // Check if following existing ranges overlap with the new one.\n        while let Some(r) = self.next_to(start) {\n            // Existing range is fully contained in the new range, remove it.\n            if item.contains(&r.start) && item.contains(&r.end) {\n                self.inner.remove(&r.start);\n                continue;\n            }\n\n            // New range doesn't overlap anymore, we are done.\n            if !range_overlaps(&r, &item) {\n                break;\n            }\n\n            // New range overlaps with existing range in the set, merge them.\n            self.inner.remove(&r.start);\n\n            start = cmp::min(start, r.start);\n            end = cmp::max(end, r.end);\n        }\n\n        if self.inner.len() >= self.capacity {\n            self.inner.pop_first();\n        }\n\n        self.inner.insert(start, end);\n    }\n\n    fn remove_until(&mut self, largest: u64) {\n        let ranges: Vec<Range<u64>> = self\n            .inner\n            .range((Bound::Unbounded, Bound::Included(&largest)))\n            .map(|(&s, &e)| s..e)\n            .collect();\n\n        for r in ranges {\n            self.inner.remove(&r.start);\n\n            if r.end > largest + 1 {\n                let start = largest + 1;\n                self.insert(start..r.end);\n            }\n        }\n    }\n\n    fn prev_to(&self, item: u64) -> Option<Range<u64>> {\n        self.inner\n            .range((Bound::Unbounded, Bound::Included(item)))\n            .map(|(&s, &e)| s..e)\n            .next_back()\n    }\n\n    fn next_to(&self, item: u64) -> Option<Range<u64>> {\n        self.inner\n            .range((Bound::Included(item), Bound::Unbounded))\n            .map(|(&s, &e)| s..e)\n            .next()\n    }\n}\n\nimpl Default for RangeSet {\n    fn default() -> Self {\n        RangeSet::Inline(InlineRangeSet {\n            inner: Default::default(),\n            capacity: usize::MAX,\n        })\n    }\n}\n\n// This implements comparison between `BTreeRangeSet` and standard `Range`. The\n// idea is that a `RangeSet` with no gaps (i.e. that only contains a single\n// range) is basically equvalent to a normal `Range` so they should be\n// comparable.\nimpl PartialEq<Range<u64>> for RangeSet {\n    fn eq(&self, other: &Range<u64>) -> bool {\n        // If there is more than one range it means that the range set is not\n        // contiguous, so can't be equal to a single range.\n        if self.len() != 1 {\n            return false;\n        }\n\n        // Get the first and only range in the set.\n        let range = self.iter().next().unwrap();\n        range == *other\n    }\n}\n\nimpl std::fmt::Debug for RangeSet {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        let ranges: Vec<Range<u64>> = self\n            .iter()\n            .map(|mut r| {\n                r.end -= 1;\n                r\n            })\n            .collect();\n\n        write!(f, \"{ranges:?}\")\n    }\n}\n\nfn range_overlaps(r: &Range<u64>, other: &Range<u64>) -> bool {\n    other.start >= r.start && other.start <= r.end ||\n        other.end >= r.start && other.end <= r.end\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn insert_non_overlapping() {\n        let mut r = RangeSet::default();\n        assert_eq!(r.len(), 0);\n        let empty: &[u64] = &[];\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &empty);\n\n        r.insert(4..7);\n        assert_eq!(r.len(), 1);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6]);\n\n        r.insert(9..12);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n    }\n\n    #[test]\n    fn insert_contained() {\n        let mut r = RangeSet::default();\n\n        r.insert(4..7);\n        r.insert(9..12);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n\n        r.insert(4..7);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n\n        r.insert(4..6);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n\n        r.insert(5..6);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n\n        r.insert(10..11);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n\n        r.insert(9..11);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n    }\n\n    #[test]\n    fn insert_overlapping() {\n        let mut r = RangeSet::default();\n\n        r.insert(3..6);\n        r.insert(9..12);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[3, 4, 5, 9, 10, 11]);\n\n        r.insert(5..7);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[3, 4, 5, 6, 9, 10, 11]);\n\n        r.insert(10..15);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            3, 4, 5, 6, 9, 10, 11, 12, 13, 14\n        ]);\n\n        r.insert(2..5);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14\n        ]);\n\n        r.insert(8..10);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14\n        ]);\n\n        r.insert(6..10);\n        assert_eq!(r.len(), 1);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14\n        ]);\n    }\n\n    #[test]\n    fn insert_overlapping_multi() {\n        let mut r = RangeSet::default();\n\n        r.insert(3..6);\n        r.insert(16..20);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            3, 4, 5, 16, 17, 18, 19\n        ]);\n\n        r.insert(10..11);\n        assert_eq!(r.len(), 3);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            3, 4, 5, 10, 16, 17, 18, 19\n        ]);\n\n        assert!(matches!(r, RangeSet::Inline(_)));\n\n        r.insert(13..14);\n        assert_eq!(r.len(), 4);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            3, 4, 5, 10, 13, 16, 17, 18, 19\n        ]);\n\n        // Make sure it converted to a btree at capacity\n        assert!(matches!(r, RangeSet::BTree(_)));\n\n        r.insert(4..17);\n        assert_eq!(r.len(), 1);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19\n        ]);\n\n        // Make sure it converted back to inline\n        assert!(matches!(r, RangeSet::Inline(_)));\n    }\n\n    #[test]\n    fn prev_to() {\n        let mut r = BTreeRangeSet {\n            inner: Default::default(),\n            capacity: usize::MAX,\n        };\n\n        r.insert(4..7);\n        r.insert(9..12);\n\n        assert_eq!(r.prev_to(2), None);\n        assert_eq!(r.prev_to(4), Some(4..7));\n        assert_eq!(r.prev_to(15), Some(9..12));\n        assert_eq!(r.prev_to(5), Some(4..7));\n        assert_eq!(r.prev_to(8), Some(4..7));\n    }\n\n    #[test]\n    fn next_to() {\n        let mut r = BTreeRangeSet {\n            inner: Default::default(),\n            capacity: usize::MAX,\n        };\n\n        r.insert(4..7);\n        r.insert(9..12);\n\n        assert_eq!(r.next_to(2), Some(4..7));\n        assert_eq!(r.next_to(12), None);\n        assert_eq!(r.next_to(15), None);\n        assert_eq!(r.next_to(5), Some(9..12));\n        assert_eq!(r.next_to(8), Some(9..12));\n    }\n\n    #[test]\n    fn push_item() {\n        let mut r = RangeSet::default();\n\n        r.insert(4..7);\n        r.insert(9..12);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n\n        r.push_item(15);\n        assert_eq!(r.len(), 3);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            4, 5, 6, 9, 10, 11, 15\n        ]);\n\n        r.push_item(15);\n        assert_eq!(r.len(), 3);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            4, 5, 6, 9, 10, 11, 15\n        ]);\n\n        r.push_item(1);\n        assert_eq!(r.len(), 4);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            1, 4, 5, 6, 9, 10, 11, 15\n        ]);\n\n        r.push_item(12);\n        r.push_item(13);\n        r.push_item(14);\n\n        assert_eq!(r.len(), 3);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            1, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15\n        ]);\n\n        r.push_item(2);\n        r.push_item(3);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15\n        ]);\n\n        r.push_item(8);\n        r.push_item(7);\n        assert_eq!(r.len(), 1);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15\n        ]);\n    }\n\n    #[test]\n    fn flatten_rev() {\n        let mut r = RangeSet::default();\n        assert_eq!(r.len(), 0);\n\n        let empty: &[u64] = &[];\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &empty);\n\n        r.insert(4..7);\n        assert_eq!(r.len(), 1);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6]);\n        assert_eq!(&r.flatten().rev().collect::<Vec<u64>>(), &[6, 5, 4]);\n\n        r.insert(9..12);\n        assert_eq!(r.len(), 2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[4, 5, 6, 9, 10, 11]);\n        assert_eq!(&r.flatten().rev().collect::<Vec<u64>>(), &[\n            11, 10, 9, 6, 5, 4\n        ]);\n    }\n\n    #[test]\n    fn flatten_one() {\n        let mut r = RangeSet::default();\n        assert_eq!(r.len(), 0);\n\n        let empty: &[u64] = &[];\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &empty);\n\n        r.insert(0..1);\n        assert_eq!(r.len(), 1);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[0]);\n        assert_eq!(&r.flatten().rev().collect::<Vec<u64>>(), &[0]);\n    }\n\n    #[test]\n    fn remove_largest() {\n        let mut r = RangeSet::default();\n\n        r.insert(3..6);\n        r.insert(9..11);\n        r.insert(13..14);\n        r.insert(16..20);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            3, 4, 5, 9, 10, 13, 16, 17, 18, 19\n        ]);\n\n        r.remove_until(2);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            3, 4, 5, 9, 10, 13, 16, 17, 18, 19\n        ]);\n\n        r.remove_until(4);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            5, 9, 10, 13, 16, 17, 18, 19\n        ]);\n\n        r.remove_until(6);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[\n            9, 10, 13, 16, 17, 18, 19\n        ]);\n\n        r.remove_until(10);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[13, 16, 17, 18, 19]);\n\n        r.remove_until(17);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[18, 19]);\n\n        r.remove_until(18);\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &[19]);\n\n        r.remove_until(20);\n\n        let empty: &[u64] = &[];\n        assert_eq!(&r.flatten().collect::<Vec<u64>>(), &empty);\n    }\n\n    #[test]\n    fn eq_range() {\n        let mut r = RangeSet::default();\n        assert_ne!(r, 0..0);\n\n        let expected = 3..20;\n\n        r.insert(3..6);\n        assert_ne!(r, expected);\n\n        r.insert(16..20);\n        assert_ne!(r, expected);\n\n        r.insert(10..11);\n        assert_ne!(r, expected);\n\n        r.insert(13..14);\n        assert_ne!(r, expected);\n\n        r.insert(4..17);\n\n        assert_eq!(r, expected);\n    }\n\n    #[test]\n    fn first_last() {\n        let mut r = RangeSet::default();\n        assert_eq!(r.first(), None);\n        assert_eq!(r.last(), None);\n\n        r.insert(10..11);\n        assert_eq!(r.first(), Some(10));\n        assert_eq!(r.last(), Some(10));\n\n        r.insert(13..14);\n        assert_eq!(r.first(), Some(10));\n        assert_eq!(r.last(), Some(13));\n\n        r.insert(3..6);\n        assert_eq!(r.first(), Some(3));\n        assert_eq!(r.last(), Some(13));\n\n        r.insert(16..20);\n        assert_eq!(r.first(), Some(3));\n        assert_eq!(r.last(), Some(19));\n\n        r.insert(4..17);\n        assert_eq!(r.first(), Some(3));\n        assert_eq!(r.last(), Some(19));\n    }\n\n    #[test]\n    fn capacity() {\n        let mut r = RangeSet::new(3);\n        assert_eq!(r.first(), None);\n        assert_eq!(r.last(), None);\n\n        r.insert(10..11);\n        assert_eq!(r.first(), Some(10));\n        assert_eq!(r.last(), Some(10));\n\n        r.insert(13..14);\n        assert_eq!(r.first(), Some(10));\n        assert_eq!(r.last(), Some(13));\n\n        r.insert(3..6);\n        assert_eq!(r.first(), Some(3));\n        assert_eq!(r.last(), Some(13));\n\n        r.insert(16..20);\n        assert_eq!(r.first(), Some(10));\n        assert_eq!(r.last(), Some(19));\n\n        r.insert(4..17);\n        assert_eq!(r.first(), Some(4));\n        assert_eq!(r.last(), Some(19));\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/AGENTS.md",
    "content": "# recovery/ -- Loss Detection & Congestion Control\n\n## OVERVIEW\n\nQUIC loss detection and congestion control per RFC 9002. Two parallel CC\nimplementations coexist: `congestion/` (legacy Reno/CUBIC) and `gcongestion/`\n(next-gen BBR2 ported from google/quiche). `Recovery` enum dispatches between\n`LegacyRecovery` and `GRecovery` via `enum_dispatch` over the `RecoveryOps`\ntrait (40+ methods).\n\n## STRUCTURE\n\n```\nmod.rs              Recovery enum, RecoveryOps trait, CongestionControlAlgorithm,\n                    Sent, ReleaseTime/ReleaseDecision, RecoveryConfig, constants\nbandwidth.rs        Bandwidth newtype\nbytes_in_flight.rs  Bytes-in-flight tracking\nrtt.rs              RTT estimation\n\ncongestion/         Legacy CC (Reno, CUBIC)\n  mod.rs            CongestionControlOps vtable struct, Congestion state\n  recovery.rs       LegacyRecovery impl of RecoveryOps, Acked struct\n  reno.rs           Static RENO: CongestionControlOps\n  cubic.rs          Static CUBIC: CongestionControlOps\n  delivery_rate.rs  Delivery rate sampling\n  hystart.rs        HyStart slow-start exit\n  prr.rs            Proportional Rate Reduction\n  test_sender.rs    Test-only CC sender\n\ngcongestion/        Next-gen CC (BBR2)\n  mod.rs            CongestionControl trait, Acked struct, BbrParams (#[doc(hidden)])\n  recovery.rs       GRecovery impl of RecoveryOps\n  pacer.rs          Token-bucket pacer\n  bbr2.rs           BBR2 top-level + Bbr2CongestionControl\n  bbr2/             BBR2 state machine substates\n    mode.rs         Mode enum (Startup, Drain, ProbeBw, ProbeRtt)\n    startup.rs      Startup mode\n    drain.rs        Drain mode\n    probe_bw.rs     ProbeBW mode (bandwidth probing cycles)\n    probe_rtt.rs    ProbeRTT mode (min RTT measurement)\n    network_model.rs  Bandwidth/RTT model, BbrParams application\n  bbr.rs            BBR (v1, not actively used)\n```\n\n## WHERE TO LOOK\n\n| Task | File | Notes |\n|------|------|-------|\n| Add/change CC algorithm | `congestion/mod.rs` or `gcongestion/mod.rs` | Different dispatch patterns |\n| Modify loss detection | `congestion/recovery.rs` or `gcongestion/recovery.rs` | Parallel impls |\n| Shared trait surface | `mod.rs:183-320` | `RecoveryOps` -- both impls must satisfy |\n| Algorithm selection | `mod.rs:365` | `CongestionControlAlgorithm` enum, `#[repr(C)]` for FFI |\n| BBR2 tuning | `gcongestion/bbr2/network_model.rs` | `BbrParams` applied here |\n| Pacing | `gcongestion/pacer.rs`, `mod.rs:691` | `ReleaseTime`, `ReleaseDecision` |\n| Per-packet metadata | `mod.rs:394` | `Sent` struct |\n| qlog integration | grep `#[cfg(feature = \"qlog\")]` | Gated throughout both impls |\n\n## ANTI-PATTERNS\n\n- **Two `Acked` structs** at `congestion/recovery.rs:1079` and `gcongestion/mod.rs:49`.\n  NOT unified. Do NOT create a third.\n- **FIXME stubs**: Some `RecoveryOps` methods only apply to one impl. Both sides\n  have `// FIXME only used by {congestion,gcongestion}` stubs that return\n  defaults. Do not proliferate; prefer narrowing the shared trait.\n- **`congestion/` uses C-like vtable** (`CongestionControlOps` with static fn\n  pointers: `RENO`, `CUBIC`). `gcongestion/` uses trait objects\n  (`CongestionControl` trait). Do not mix dispatch styles.\n- **`BbrParams` is `#[doc(hidden)]`** and experimental. Do not stabilize or\n  expose without explicit intent.\n- **Many `#[cfg(test)]` accessors** on `RecoveryOps` (lines 200-297). Test-only;\n  do not call from production code.\n\n## NOTES\n\n- Constants cite RFC 9002: `INITIAL_TIME_THRESHOLD = 9.0/8.0`, `GRANULARITY = 1ms`.\n- `CongestionControlAlgorithm` values: Reno=0, CUBIC=1, Bbr2Gcongestion=4. Gap is intentional (removed variants).\n- `Recovery::new_with_config` tries `GRecovery::new` first; falls back to `LegacyRecovery` if algo is Reno/CUBIC.\n- `bbr2/` is a deeply nested state machine -- changes require understanding all six substates.\n- `enable_relaxed_loss_threshold` experiment adjusts time thresholds dynamically on spurious loss.\n- `gcongestion/bbr.rs` is BBRv1 -- mostly vestigial alongside BBR2.\n"
  },
  {
    "path": "quiche/src/recovery/bandwidth.rs",
    "content": "// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Duration;\n\nconst NUM_MILLIS_PER_SECOND: u64 = 1000;\nconst NUM_MICROS_PER_MILLI: u64 = 1000;\nconst NUM_MICROS_PER_SECOND: u64 = NUM_MICROS_PER_MILLI * NUM_MILLIS_PER_SECOND;\nconst NUM_NANOS_PER_SECOND: u64 = 1000 * NUM_MICROS_PER_SECOND;\n\n#[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy)]\npub struct Bandwidth {\n    bits_per_second: u64,\n}\n\nimpl std::ops::Mul<f64> for Bandwidth {\n    type Output = Bandwidth;\n\n    fn mul(self, rhs: f64) -> Self::Output {\n        Bandwidth {\n            bits_per_second: (self.bits_per_second as f64 * rhs).round() as u64,\n        }\n    }\n}\n\nimpl std::ops::Mul<f32> for Bandwidth {\n    type Output = Bandwidth;\n\n    fn mul(self, rhs: f32) -> Self::Output {\n        self * rhs as f64\n    }\n}\n\nimpl std::ops::Sub<Bandwidth> for Bandwidth {\n    type Output = Option<Bandwidth>;\n\n    fn sub(self, rhs: Bandwidth) -> Self::Output {\n        self.bits_per_second\n            .checked_sub(rhs.bits_per_second)\n            .map(|bps| Bandwidth {\n                bits_per_second: bps,\n            })\n    }\n}\n\nimpl std::ops::Add<Bandwidth> for Bandwidth {\n    type Output = Bandwidth;\n\n    fn add(self, rhs: Bandwidth) -> Self::Output {\n        Bandwidth {\n            bits_per_second: self.bits_per_second.add(rhs.bits_per_second),\n        }\n    }\n}\n\nimpl std::ops::Mul<Duration> for Bandwidth {\n    type Output = u64;\n\n    fn mul(self, rhs: Duration) -> Self::Output {\n        self.to_bytes_per_period(rhs)\n    }\n}\n\nimpl Bandwidth {\n    pub const fn from_bytes_and_time_delta(\n        bytes: usize, time_delta: Duration,\n    ) -> Self {\n        if bytes == 0 {\n            return Bandwidth { bits_per_second: 0 };\n        }\n\n        let mut nanos = time_delta.as_nanos() as u64;\n        if nanos == 0 {\n            nanos = 1;\n        }\n\n        let num_nano_bits = 8 * bytes as u64 * NUM_NANOS_PER_SECOND;\n        if num_nano_bits < nanos {\n            return Bandwidth { bits_per_second: 1 };\n        }\n\n        Bandwidth {\n            bits_per_second: num_nano_bits / nanos,\n        }\n    }\n\n    #[allow(dead_code)]\n    pub const fn from_bytes_per_second(bytes_per_second: u64) -> Self {\n        Bandwidth {\n            bits_per_second: bytes_per_second * 8,\n        }\n    }\n\n    #[allow(dead_code)]\n    pub const fn to_bits_per_second(self) -> u64 {\n        self.bits_per_second\n    }\n\n    pub const fn to_bytes_per_second(self) -> u64 {\n        self.bits_per_second / 8\n    }\n\n    pub const fn from_kbits_per_second(k_bits_per_second: u64) -> Self {\n        Bandwidth {\n            bits_per_second: k_bits_per_second * 1_000,\n        }\n    }\n\n    #[allow(dead_code)]\n    pub const fn from_mbits_per_second(m_bits_per_second: u64) -> Self {\n        Bandwidth::from_kbits_per_second(m_bits_per_second * 1_000)\n    }\n\n    pub const fn infinite() -> Self {\n        Bandwidth {\n            bits_per_second: u64::MAX,\n        }\n    }\n\n    pub const fn zero() -> Self {\n        Bandwidth { bits_per_second: 0 }\n    }\n\n    pub fn transfer_time(&self, bytes: usize) -> Duration {\n        if self.bits_per_second == 0 {\n            Duration::ZERO\n        } else {\n            Duration::from_nanos(\n                (bytes as u64 * 8 * NUM_NANOS_PER_SECOND) / self.bits_per_second,\n            )\n        }\n    }\n\n    pub fn to_bytes_per_period(self, time_period: Duration) -> u64 {\n        self.bits_per_second * time_period.as_nanos() as u64 /\n            8 /\n            NUM_NANOS_PER_SECOND\n    }\n}\n\nimpl std::fmt::Debug for Bandwidth {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self.bits_per_second {\n            x if x < 1_000_000 => write!(f, \"{:.2} Kbps\", x as f64 / 1_000.),\n            x if x < 1_000_000_000 => {\n                write!(f, \"{:.2} Mbps\", x as f64 / 1_000_000.)\n            },\n            x => write!(f, \"{:.2} Gbps\", x as f64 / 1_000_000_000.),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn constructors() {\n        // Internal representation is bits per second.\n        assert_eq!(Bandwidth::from_bytes_per_second(100).bits_per_second, 800);\n        let bw = Bandwidth::from_bytes_per_second(100);\n        assert_eq!(bw.to_bits_per_second(), 800);\n        assert_eq!(bw.to_bytes_per_second(), 100);\n\n        // kbits == 1000 bits\n        assert_eq!(\n            Bandwidth::from_kbits_per_second(100).bits_per_second,\n            100_000\n        );\n\n        // mbits == 1000,000 bits\n        assert_eq!(\n            Bandwidth::from_mbits_per_second(100).bits_per_second,\n            100_000_000\n        );\n\n        assert_eq!(Bandwidth::infinite().bits_per_second, u64::MAX);\n        assert_eq!(Bandwidth::zero().bits_per_second, 0);\n    }\n\n    #[test]\n    fn arithmetic_ops() {\n        let bw_1k = Bandwidth::from_kbits_per_second(1);\n        let bw_5k = Bandwidth::from_kbits_per_second(5);\n        let bw_6k = Bandwidth::from_kbits_per_second(6);\n\n        // Addition\n        assert_eq!(bw_1k + bw_5k, bw_6k);\n\n        // Subtraction\n        assert_eq!(bw_6k - bw_5k, Some(bw_1k));\n        assert_eq!(bw_6k - bw_6k, Some(Bandwidth::zero()));\n\n        // Negative bw is not defined.\n        assert_eq!(bw_1k - bw_5k, None);\n\n        // Multiplication by scalars\n        assert_eq!(bw_1k * 6.0f64, bw_6k);\n        assert_eq!(bw_1k * 6.0f32, bw_6k);\n        assert_eq!(bw_5k * 0.0, Bandwidth::zero());\n        assert_eq!(bw_5k * 1.0, bw_5k);\n\n        // Multiplication saturates on overflow and underflow.\n        assert_eq!(Bandwidth::infinite() * -1.0, Bandwidth::zero());\n        assert_eq!((Bandwidth::infinite() * 2.0f64).bits_per_second, u64::MAX);\n\n        // Multiplication rounds up.\n        assert_eq!(\n            (Bandwidth::infinite() * 0.5f64).bits_per_second,\n            u64::MAX / 2 + 1\n        );\n    }\n\n    #[test]\n    fn from_bytes_and_time_delta() {\n        assert_eq!(\n            Bandwidth::from_bytes_and_time_delta(10, Duration::from_millis(1000))\n                .bits_per_second,\n            80\n        );\n        assert_eq!(\n            Bandwidth::from_bytes_and_time_delta(10, Duration::from_millis(100))\n                .bits_per_second,\n            800\n        );\n        assert_eq!(\n            Bandwidth::from_bytes_and_time_delta(\n                100,\n                Duration::from_millis(1000)\n            )\n            .bits_per_second,\n            800\n        );\n    }\n\n    #[test]\n    fn transfer_time() {\n        let one_kbit_sec = Bandwidth::from_kbits_per_second(1);\n        assert_eq!(one_kbit_sec.transfer_time(0), Duration::ZERO);\n        assert_eq!(one_kbit_sec.transfer_time(100), Duration::from_millis(800));\n    }\n\n    #[test]\n    fn to_bytes_per_period() {\n        let one_kbit_sec = Bandwidth::from_kbits_per_second(1);\n        assert_eq!(\n            one_kbit_sec.to_bytes_per_period(Duration::from_millis(10_000)),\n            1250\n        );\n        assert_eq!(\n            one_kbit_sec.to_bytes_per_period(Duration::from_millis(1000)),\n            125\n        );\n        assert_eq!(\n            one_kbit_sec.to_bytes_per_period(Duration::from_millis(100)),\n            12\n        );\n        assert_eq!(\n            one_kbit_sec.to_bytes_per_period(Duration::from_millis(10)),\n            1\n        );\n        assert_eq!(\n            one_kbit_sec.to_bytes_per_period(Duration::from_millis(1)),\n            0\n        );\n\n        // Mul<Duration> implementation.\n        assert_eq!(one_kbit_sec * Duration::from_millis(10_000), 1250);\n    }\n\n    #[test]\n    fn debug() {\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth { bits_per_second: 1 }),\n            \"0.00 Kbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 12\n            }),\n            \"0.01 Kbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 123\n            }),\n            \"0.12 Kbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 1234\n            }),\n            \"1.23 Kbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 12345\n            }),\n            \"12.35 Kbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 123456\n            }),\n            \"123.46 Kbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 1234567\n            }),\n            \"1.23 Mbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 12345678\n            }),\n            \"12.35 Mbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 123456789\n            }),\n            \"123.46 Mbps\"\n        );\n        assert_eq!(\n            format!(\"{:?}\", Bandwidth {\n                bits_per_second: 1234567890\n            }),\n            \"1.23 Gbps\"\n        );\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/bytes_in_flight.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Duration;\nuse std::time::Instant;\n\n/// Estimate the total duration a connection has bytes-in-flight.\n///\n/// There can be multiple transitions from bytes-in-flight >0 to 0 and 0 to >0\n/// during a connection's lifetime. Total bytes-in-flight duration is the sum of\n/// all intervals that transition from idle to not-idle and back to idle. Close\n/// intervals are the ones that transitioned back to idle. The open one is the\n/// most recent interval for which we only have a start time, but no end time.\n#[derive(Default)]\npub struct BytesInFlight {\n    // Current bytes in flight.\n    bytes_in_flight: usize,\n\n    // Instant at which bytes_in_flight transitioned from 0 to >0.\n    // Set if bytes_in_flight is currently >0 which indicates that\n    // the bytes in flight interval is currently \"open\".\n    bytes_in_flight_interval_start: Option<Instant>,\n\n    // Duration of the current open interval.\n    open_interval_duration: Duration,\n\n    // Sum of closed interval durations seen so far.\n    closed_interval_duration: Duration,\n}\n\nimpl BytesInFlight {\n    /// Add to bytes in flight.  Record the start time when\n    /// bytes_in_flight was 0 at the beginning of the function.\n    pub(crate) fn add(&mut self, delta: usize, now: Instant) {\n        if delta == 0 {\n            return;\n        }\n\n        self.bytes_in_flight += delta;\n\n        if self.bytes_in_flight_interval_start.is_some() {\n            self.update_in_flight_duration(now);\n        } else {\n            self.bytes_in_flight_interval_start = Some(now);\n        }\n    }\n\n    /// Substract from bytes in flight.  If bytes_in_flight drops to 0,\n    /// end the current bytes_in_flight >0 interval.\n    pub(crate) fn saturating_subtract(&mut self, delta: usize, now: Instant) {\n        self.bytes_in_flight = self.bytes_in_flight.saturating_sub(delta);\n        self.update_in_flight_duration(now);\n    }\n\n    /// Current bytes in flight.\n    pub(crate) fn get(&self) -> usize {\n        self.bytes_in_flight\n    }\n\n    /// Returns true if there are 0 bytes in flight.\n    pub(crate) fn is_zero(&self) -> bool {\n        self.bytes_in_flight == 0\n    }\n\n    /// Total time during which bytes_in_flight was > 0.\n    pub(crate) fn get_duration(&self) -> Duration {\n        self.closed_interval_duration + self.open_interval_duration\n    }\n\n    fn update_in_flight_duration(&mut self, now: Instant) {\n        if let Some(start) = self.bytes_in_flight_interval_start {\n            if self.bytes_in_flight == 0 {\n                self.open_interval_duration = Duration::ZERO;\n                self.closed_interval_duration += now - start;\n                self.bytes_in_flight_interval_start = None;\n            } else {\n                self.open_interval_duration = now - start;\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn bytes_in_flight_basic() {\n        let start = Instant::now();\n\n        let mut bytes_in_flight: BytesInFlight = Default::default();\n        assert_eq!(bytes_in_flight.get(), 0);\n        assert_eq!(bytes_in_flight.get_duration(), Duration::ZERO);\n\n        // bytes_in_flight_interval_start is initialized when bytes > 0.\n        bytes_in_flight.add(1, start);\n        assert_eq!(bytes_in_flight.get(), 1);\n        assert_eq!(bytes_in_flight.get_duration(), Duration::ZERO);\n\n        // Advance time forward and verify that the inflight time for an open\n        // interval.\n        let mut now = start + Duration::from_secs(2);\n        bytes_in_flight.add(2, now);\n        bytes_in_flight.add(3, now);\n        assert_eq!(bytes_in_flight.get(), 6);\n        // Interval start does not change on adds\n        assert_eq!(bytes_in_flight.get_duration(), Duration::from_secs(2));\n\n        now += Duration::from_secs(5);\n        bytes_in_flight.saturating_subtract(3, now);\n        // Bytes > 0, interval remains open.\n        assert_eq!(bytes_in_flight.get(), 3);\n        assert_eq!(bytes_in_flight.get_duration(), Duration::from_secs(7));\n\n        bytes_in_flight.saturating_subtract(3, now);\n        // Bytes == 0, interval is closed.\n        assert_eq!(bytes_in_flight.get(), 0);\n        assert_eq!(bytes_in_flight.get_duration(), Duration::from_secs(7));\n\n        // Open a second interval.\n        now += Duration::from_secs(30);\n        bytes_in_flight.add(10, now);\n        assert_eq!(bytes_in_flight.get(), 10);\n        assert_eq!(bytes_in_flight.get_duration(), Duration::from_secs(7));\n\n        // Close the second interval.\n        now += Duration::from_secs(5);\n        bytes_in_flight.saturating_subtract(10, now);\n        assert_eq!(bytes_in_flight.get(), 0);\n        // Expect the time to be the 7sec + 5sec since those were the open time of\n        // the two bytes in flight intervals.\n        assert_eq!(bytes_in_flight.get_duration(), Duration::from_secs(12));\n    }\n\n    #[test]\n    fn bytes_in_flight_saturating_sub() {\n        let start = Instant::now();\n\n        let mut bytes_in_flight: BytesInFlight = Default::default();\n        bytes_in_flight.add(10, start);\n        assert_eq!(bytes_in_flight.get(), 10);\n\n        bytes_in_flight.saturating_subtract(7, start + Duration::from_secs(3));\n        assert_eq!(bytes_in_flight.get(), 3);\n        assert_eq!(bytes_in_flight.get_duration(), Duration::from_secs(3));\n        // Interval is still open.\n        bytes_in_flight.saturating_subtract(1, start + Duration::from_secs(20));\n        assert_eq!(bytes_in_flight.get(), 2);\n        assert_eq!(bytes_in_flight.get_duration(), Duration::from_secs(20));\n\n        bytes_in_flight.saturating_subtract(7, start + Duration::from_secs(25));\n        assert_eq!(bytes_in_flight.get(), 0);\n        // Interval is closed.\n        assert_eq!(bytes_in_flight.get_duration(), Duration::from_secs(25));\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/congestion/cubic.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! CUBIC Congestion Control\n//!\n//! This implementation is based on the following draft:\n//! <https://tools.ietf.org/html/draft-ietf-tcpm-rfc8312bis-02>\n//!\n//! Note that Slow Start can use HyStart++ when enabled.\n\nuse std::cmp;\n\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse super::rtt::RttStats;\nuse super::Acked;\nuse super::Sent;\n\nuse super::reno;\nuse super::Congestion;\nuse super::CongestionControlOps;\nuse crate::recovery::MINIMUM_WINDOW_PACKETS;\n\npub(crate) static CUBIC: CongestionControlOps = CongestionControlOps {\n    on_init,\n    on_packet_sent,\n    on_packets_acked,\n    congestion_event,\n    checkpoint,\n    rollback,\n    #[cfg(feature = \"qlog\")]\n    state_str,\n    debug_fmt,\n};\n\n/// CUBIC Constants.\n///\n/// These are recommended value in RFC8312.\nconst BETA_CUBIC: f64 = 0.7;\n\nconst C: f64 = 0.4;\n\n/// Threshold for rolling back state, as percentage of lost packets relative to\n/// cwnd.\nconst ROLLBACK_THRESHOLD_PERCENT: usize = 20;\n\n/// Minimum threshold for rolling back state, as number of packets.\nconst MIN_ROLLBACK_THRESHOLD: usize = 2;\n\n/// Default value of alpha_aimd in the beginning of congestion avoidance.\nconst ALPHA_AIMD: f64 = 3.0 * (1.0 - BETA_CUBIC) / (1.0 + BETA_CUBIC);\n\n/// CUBIC State Variables.\n///\n/// We need to keep those variables across the connection.\n/// k, w_max, w_est are described in the RFC.\n#[derive(Debug, Default)]\npub struct State {\n    k: f64,\n\n    w_max: f64,\n\n    w_est: f64,\n\n    alpha_aimd: f64,\n\n    // Used in CUBIC fix (see on_packet_sent())\n    last_sent_time: Option<Instant>,\n\n    // Tracks when the last ACK was processed, approximating when\n    // bytes_in_flight transitioned toward zero. Used to measure the\n    // actual idle duration in on_packet_sent() instead of the time\n    // since the last send (which inflates the delta by a full RTT).\n    last_ack_time: Option<Instant>,\n\n    // Store cwnd increment during congestion avoidance.\n    cwnd_inc: usize,\n\n    // CUBIC state checkpoint preceding the last congestion event.\n    prior: PriorState,\n}\n\n/// Stores the CUBIC state from before the last congestion event.\n///\n/// <https://tools.ietf.org/id/draft-ietf-tcpm-rfc8312bis-00.html#section-4.9>\n#[derive(Debug, Default)]\nstruct PriorState {\n    congestion_window: usize,\n\n    ssthresh: usize,\n\n    w_max: f64,\n\n    k: f64,\n\n    epoch_start: Option<Instant>,\n\n    lost_count: usize,\n}\n\n/// CUBIC Functions.\n///\n/// Note that these calculations are based on a count of cwnd as bytes,\n/// not packets.\n/// Unit of t (duration) and RTT are based on seconds (f64).\nimpl State {\n    // K = cubic_root ((w_max - cwnd) / C) (Eq. 2)\n    fn cubic_k(&self, cwnd: usize, max_datagram_size: usize) -> f64 {\n        let w_max = self.w_max / max_datagram_size as f64;\n        let cwnd = cwnd as f64 / max_datagram_size as f64;\n\n        libm::cbrt((w_max - cwnd) / C)\n    }\n\n    // W_cubic(t) = C * (t - K)^3 + w_max (Eq. 1)\n    fn w_cubic(&self, t: Duration, max_datagram_size: usize) -> f64 {\n        let w_max = self.w_max / max_datagram_size as f64;\n\n        (C * (t.as_secs_f64() - self.k).powi(3) + w_max) *\n            max_datagram_size as f64\n    }\n\n    // W_est = W_est + alpha_aimd * (segments_acked / cwnd)  (Eq. 4)\n    fn w_est_inc(\n        &self, acked: usize, cwnd: usize, max_datagram_size: usize,\n    ) -> f64 {\n        self.alpha_aimd * (acked as f64 / cwnd as f64) * max_datagram_size as f64\n    }\n}\n\nfn on_init(_r: &mut Congestion) {}\n\nfn on_packet_sent(\n    r: &mut Congestion, sent_bytes: usize, bytes_in_flight: usize, now: Instant,\n) {\n    reno::on_packet_sent(r, sent_bytes, bytes_in_flight, now);\n\n    // Don't adjust epoch or track send time for non-data packets\n    // (e.g. ACKs). These have in_flight=true but size=0.\n    // Skip all the following logic for these packets.\n    if sent_bytes == 0 && r.enable_cubic_idle_restart_fix {\n        return;\n    }\n\n    // See https://github.com/torvalds/linux/commit/30927520dbae297182990bb21d08762bcc35ce1d\n    // First transmit when no packets in flight.\n    // Shift epoch start to keep cwnd growth on the cubic curve.\n    let cubic = &mut r.cubic_state;\n\n    if bytes_in_flight == 0 {\n        if let Some(recovery_start_time) = r.congestion_recovery_start_time {\n            // Measure idle from the most recent activity: either the\n            // last ACK (approximating when bif hit 0) or the last data\n            // send, whichever is later. Using last_sent_time alone\n            // would inflate the delta by a full RTT when cwnd is small\n            // and bif transiently hits 0 between ACK and send.\n            let idle_start = if r.enable_cubic_idle_restart_fix {\n                cmp::max(cubic.last_ack_time, cubic.last_sent_time)\n            } else {\n                cubic.last_sent_time\n            };\n\n            if let Some(idle_start) = idle_start {\n                if idle_start < now {\n                    let delta = now - idle_start;\n                    r.congestion_recovery_start_time =\n                        Some(recovery_start_time + delta);\n                }\n            }\n        }\n    }\n\n    cubic.last_sent_time = Some(now);\n}\n\nfn on_packets_acked(\n    r: &mut Congestion, bytes_in_flight: usize, packets: &mut Vec<Acked>,\n    now: Instant, rtt_stats: &RttStats,\n) {\n    r.cubic_state.last_ack_time = Some(now);\n\n    for pkt in packets.drain(..) {\n        on_packet_acked(r, bytes_in_flight, &pkt, now, rtt_stats);\n    }\n}\n\nfn on_packet_acked(\n    r: &mut Congestion, bytes_in_flight: usize, packet: &Acked, now: Instant,\n    rtt_stats: &RttStats,\n) {\n    let in_congestion_recovery = r.in_congestion_recovery(packet.time_sent);\n\n    if in_congestion_recovery {\n        r.prr.on_packet_acked(\n            packet.size,\n            bytes_in_flight,\n            r.ssthresh.get(),\n            r.max_datagram_size,\n        );\n\n        return;\n    }\n\n    if r.app_limited {\n        return;\n    }\n\n    // Detecting spurious congestion events.\n    // <https://tools.ietf.org/id/draft-ietf-tcpm-rfc8312bis-00.html#section-4.9>\n    //\n    // When the recovery episode ends with recovering\n    // a few packets (less than cwnd / mss * ROLLBACK_THRESHOLD_PERCENT(%)), it's\n    // considered as spurious and restore to the previous state.\n    if r.congestion_recovery_start_time.is_some() {\n        let new_lost = r.lost_count - r.cubic_state.prior.lost_count;\n\n        let rollback_threshold = (r.congestion_window / r.max_datagram_size) *\n            ROLLBACK_THRESHOLD_PERCENT /\n            100;\n\n        let rollback_threshold = rollback_threshold.max(MIN_ROLLBACK_THRESHOLD);\n\n        if new_lost < rollback_threshold {\n            let did_rollback = rollback(r);\n            if did_rollback {\n                return;\n            }\n        }\n    }\n\n    if r.congestion_window < r.ssthresh.get() {\n        // In Slow start, bytes_acked_sl is used for counting\n        // acknowledged bytes.\n        r.bytes_acked_sl += packet.size;\n\n        if r.bytes_acked_sl >= r.max_datagram_size {\n            if r.hystart.in_css() {\n                r.congestion_window +=\n                    r.hystart.css_cwnd_inc(r.max_datagram_size);\n            } else {\n                r.congestion_window += r.max_datagram_size;\n            }\n\n            r.bytes_acked_sl -= r.max_datagram_size;\n        }\n\n        if r.hystart.on_packet_acked(packet, rtt_stats.latest_rtt, now) {\n            // Exit to congestion avoidance if CSS ends.\n            r.ssthresh.update(r.congestion_window, true);\n        }\n    } else {\n        // Congestion avoidance.\n        let ca_start_time;\n\n        // In CSS, use css_start_time instead of congestion_recovery_start_time.\n        if r.hystart.in_css() {\n            ca_start_time = r.hystart.css_start_time().unwrap();\n\n            // Reset w_max and k when CSS started.\n            if r.cubic_state.w_max == 0.0 {\n                r.cubic_state.w_max = r.congestion_window as f64;\n                r.cubic_state.k = 0.0;\n\n                r.cubic_state.w_est = r.congestion_window as f64;\n                r.cubic_state.alpha_aimd = ALPHA_AIMD;\n            }\n        } else {\n            match r.congestion_recovery_start_time {\n                Some(t) => ca_start_time = t,\n                None => {\n                    // When we come here without congestion_event() triggered,\n                    // initialize congestion_recovery_start_time, w_max and k.\n                    ca_start_time = now;\n                    r.congestion_recovery_start_time = Some(now);\n\n                    r.cubic_state.w_max = r.congestion_window as f64;\n                    r.cubic_state.k = 0.0;\n\n                    r.cubic_state.w_est = r.congestion_window as f64;\n                    r.cubic_state.alpha_aimd = ALPHA_AIMD;\n                },\n            }\n        }\n\n        let t = now.saturating_duration_since(ca_start_time);\n\n        // target = w_cubic(t + rtt)\n        let target = r\n            .cubic_state\n            .w_cubic(t + *rtt_stats.min_rtt, r.max_datagram_size);\n\n        // Clipping target to [cwnd, 1.5 x cwnd]\n        let target = f64::max(target, r.congestion_window as f64);\n        let target = f64::min(target, r.congestion_window as f64 * 1.5);\n\n        // Update w_est.\n        let w_est_inc = r.cubic_state.w_est_inc(\n            packet.size,\n            r.congestion_window,\n            r.max_datagram_size,\n        );\n        r.cubic_state.w_est += w_est_inc;\n\n        if r.cubic_state.w_est >= r.cubic_state.w_max {\n            r.cubic_state.alpha_aimd = 1.0;\n        }\n\n        let mut cubic_cwnd = r.congestion_window;\n\n        if r.cubic_state.w_cubic(t, r.max_datagram_size) < r.cubic_state.w_est {\n            // AIMD friendly region (W_cubic(t) < W_est)\n            cubic_cwnd = cmp::max(cubic_cwnd, r.cubic_state.w_est as usize);\n        } else {\n            // Concave region or convex region use same increment.\n            let cubic_inc =\n                r.max_datagram_size * (target as usize - cubic_cwnd) / cubic_cwnd;\n\n            cubic_cwnd += cubic_inc;\n        }\n\n        // Update the increment and increase cwnd by MSS.\n        r.cubic_state.cwnd_inc += cubic_cwnd - r.congestion_window;\n\n        if r.cubic_state.cwnd_inc >= r.max_datagram_size {\n            r.congestion_window += r.max_datagram_size;\n            r.cubic_state.cwnd_inc -= r.max_datagram_size;\n        }\n    }\n}\n\nfn congestion_event(\n    r: &mut Congestion, bytes_in_flight: usize, _lost_bytes: usize,\n    largest_lost_pkt: &Sent, now: Instant,\n) {\n    let time_sent = largest_lost_pkt.time_sent;\n    let in_congestion_recovery = r.in_congestion_recovery(time_sent);\n\n    // Start a new congestion event if packet was sent after the\n    // start of the previous congestion recovery period.\n    if !in_congestion_recovery {\n        r.congestion_recovery_start_time = Some(now);\n\n        // Fast convergence\n        if (r.congestion_window as f64) < r.cubic_state.w_max {\n            r.cubic_state.w_max =\n                r.congestion_window as f64 * (1.0 + BETA_CUBIC) / 2.0;\n        } else {\n            r.cubic_state.w_max = r.congestion_window as f64;\n        }\n\n        let ssthresh = (r.congestion_window as f64 * BETA_CUBIC) as usize;\n        let ssthresh =\n            cmp::max(ssthresh, r.max_datagram_size * MINIMUM_WINDOW_PACKETS);\n        r.ssthresh.update(ssthresh, r.hystart.in_css());\n        r.congestion_window = ssthresh;\n\n        r.cubic_state.k = if r.cubic_state.w_max < r.congestion_window as f64 {\n            0.0\n        } else {\n            r.cubic_state\n                .cubic_k(r.congestion_window, r.max_datagram_size)\n        };\n\n        r.cubic_state.cwnd_inc =\n            (r.cubic_state.cwnd_inc as f64 * BETA_CUBIC) as usize;\n\n        r.cubic_state.w_est = r.congestion_window as f64;\n        r.cubic_state.alpha_aimd = ALPHA_AIMD;\n\n        if r.hystart.in_css() {\n            r.hystart.congestion_event();\n        }\n\n        r.prr.congestion_event(bytes_in_flight);\n    }\n}\n\nfn checkpoint(r: &mut Congestion) {\n    r.cubic_state.prior.congestion_window = r.congestion_window;\n    r.cubic_state.prior.ssthresh = r.ssthresh.get();\n    r.cubic_state.prior.w_max = r.cubic_state.w_max;\n    r.cubic_state.prior.k = r.cubic_state.k;\n    r.cubic_state.prior.epoch_start = r.congestion_recovery_start_time;\n    r.cubic_state.prior.lost_count = r.lost_count;\n}\n\nfn rollback(r: &mut Congestion) -> bool {\n    // Don't go back to slow start.\n    if r.cubic_state.prior.congestion_window < r.cubic_state.prior.ssthresh {\n        return false;\n    }\n\n    if r.congestion_window >= r.cubic_state.prior.congestion_window {\n        return false;\n    }\n\n    r.congestion_window = r.cubic_state.prior.congestion_window;\n    r.ssthresh.update(r.cubic_state.prior.ssthresh, false);\n    r.cubic_state.w_max = r.cubic_state.prior.w_max;\n    r.cubic_state.k = r.cubic_state.prior.k;\n    r.congestion_recovery_start_time = r.cubic_state.prior.epoch_start;\n\n    true\n}\n\n#[cfg(feature = \"qlog\")]\nfn state_str(r: &Congestion, now: Instant) -> &'static str {\n    reno::state_str(r, now)\n}\n\nfn debug_fmt(r: &Congestion, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n    write!(\n        f,\n        \"cubic={{ k={} w_max={} }} \",\n        r.cubic_state.k, r.cubic_state.w_max\n    )\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use crate::CongestionControlAlgorithm;\n\n    use crate::recovery::congestion::hystart;\n    use crate::recovery::congestion::recovery::LegacyRecovery;\n    use crate::recovery::congestion::test_sender::TestSender;\n    use crate::recovery::RecoveryOps;\n\n    fn test_sender() -> TestSender {\n        TestSender::new(CongestionControlAlgorithm::CUBIC, false)\n    }\n\n    fn hystart_test_sender() -> TestSender {\n        TestSender::new(CongestionControlAlgorithm::CUBIC, true)\n    }\n\n    #[test]\n    fn cubic_init() {\n        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        cfg.set_cc_algorithm(CongestionControlAlgorithm::CUBIC);\n\n        let r = LegacyRecovery::new(&cfg);\n\n        assert!(r.cwnd() > 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n    }\n\n    #[test]\n    fn cubic_slow_start() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        // Send initcwnd full MSS packets to become no longer app limited\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n\n        let cwnd_prev = sender.congestion_window;\n\n        sender.ack_n_packets(1, size);\n\n        // Check if cwnd increased by packet size (slow start)\n        assert_eq!(sender.congestion_window, cwnd_prev + size);\n    }\n\n    #[test]\n    fn cubic_slow_start_multi_acks() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        // Send initcwnd full MSS packets to become no longer app limited\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n\n        let cwnd_prev = sender.congestion_window;\n\n        sender.ack_n_packets(3, size);\n\n        // Acked 3 packets.\n        assert_eq!(sender.congestion_window, cwnd_prev + size * 3);\n    }\n\n    #[test]\n    fn cubic_congestion_event() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        sender.send_packet(size);\n\n        let cwnd_prev = sender.congestion_window;\n\n        sender.lose_n_packets(1, size, None);\n\n        // In CUBIC, after congestion event, cwnd will be reduced by (1 -\n        // CUBIC_BETA)\n        assert_eq!(\n            cwnd_prev as f64 * BETA_CUBIC,\n            sender.congestion_window as f64\n        );\n    }\n\n    #[test]\n    fn cubic_congestion_avoidance() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        let prev_cwnd = sender.congestion_window;\n\n        // Send initcwnd full MSS packets to become no longer app limited\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n\n        let rtt = Duration::from_millis(100);\n\n        sender.advance_time(rtt);\n        sender.lose_n_packets(1, size, None);\n\n        // After congestion event, cwnd will be reduced.\n        let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;\n        assert_eq!(sender.congestion_window, cur_cwnd);\n\n        // Shift current time by 1 RTT.\n        sender.update_rtt(rtt);\n        // Exit from the recovery.\n        sender.advance_time(rtt);\n\n        sender.ack_n_packets(sender.initial_congestion_window_packets - 2, size);\n        // Only packets sent after exit recovery can increase cwnd.\n        assert_eq!(sender.congestion_window, cur_cwnd);\n\n        for _ in 0..8 {\n            sender.send_packet(size);\n        }\n        sender.advance_time(rtt);\n\n        sender.ack_n_packets(1, size);\n        // Only packets sent after exit recovery can increase cwnd.\n        assert_eq!(sender.congestion_window, cur_cwnd);\n\n        // During Congestion Avoidance, it will take\n        // 6 ACKs to increase cwnd by 1 MSS.\n        for _ in 0..6 {\n            sender.ack_n_packets(1, size);\n            sender.advance_time(rtt);\n        }\n\n        assert_eq!(sender.congestion_window, cur_cwnd + size);\n    }\n\n    #[test]\n    fn cubic_hystart_css_to_ss() {\n        let mut sender = hystart_test_sender();\n        let size = sender.max_datagram_size;\n\n        // 1st round.\n        let n_rtt_sample = hystart::N_RTT_SAMPLE;\n\n        let rtt_1st = Duration::from_millis(50);\n\n        let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;\n        sender.hystart.start_round(next_rnd);\n        // Send 1st round packets.\n        for _ in 0..n_rtt_sample {\n            sender.send_packet(size);\n        }\n        sender.update_app_limited(false);\n\n        // Receiving Acks.\n        sender.advance_time(rtt_1st);\n        sender.update_rtt(rtt_1st);\n        sender.ack_n_packets(n_rtt_sample, size);\n\n        // Not in CSS yet.\n        assert!(sender.hystart.css_start_time().is_none());\n\n        // 2nd round.\n        let mut rtt_2nd = Duration::from_millis(100);\n\n        sender.advance_time(rtt_2nd);\n\n        let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;\n        sender.hystart.start_round(next_rnd);\n        // Send 2nd round packets.\n        for _ in 0..n_rtt_sample {\n            sender.send_packet(size);\n        }\n        sender.update_app_limited(false);\n\n        // Receiving Acks.\n        // Last ack will cause to exit to CSS.\n        let mut cwnd_prev = sender.congestion_window();\n\n        for _ in 0..n_rtt_sample {\n            cwnd_prev = sender.congestion_window();\n            sender.update_rtt(rtt_2nd);\n            sender.ack_n_packets(1, size);\n            // Keep increasing RTT so that hystart exits to CSS.\n            rtt_2nd += rtt_2nd.saturating_add(Duration::from_millis(4));\n        }\n\n        // Now we are in CSS.\n        assert!(sender.hystart.css_start_time().is_some());\n        assert_eq!(sender.congestion_window(), cwnd_prev + size);\n\n        // 3rd round, which RTT is less than previous round to\n        // trigger back to Slow Start.\n        let rtt_3rd = Duration::from_millis(80);\n        sender.advance_time(rtt_3rd);\n        cwnd_prev = sender.congestion_window();\n\n        let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;\n        sender.hystart.start_round(next_rnd);\n        // Send 3nd round packets.\n        for _ in 0..n_rtt_sample {\n            sender.send_packet(size);\n        }\n        sender.update_app_limited(false);\n\n        // Receiving Acks.\n        // Last ack will cause to exit to SS.\n        sender.update_rtt(rtt_3rd);\n        sender.ack_n_packets(n_rtt_sample, size);\n\n        // Now we are back in Slow Start.\n        assert!(sender.hystart.css_start_time().is_none());\n        assert_eq!(\n            sender.congestion_window(),\n            cwnd_prev +\n                size / hystart::CSS_GROWTH_DIVISOR * hystart::N_RTT_SAMPLE\n        );\n    }\n\n    #[test]\n    fn cubic_hystart_css_to_ca() {\n        let mut sender = hystart_test_sender();\n        let size = sender.max_datagram_size;\n\n        // 1st round.\n        let n_rtt_sample = hystart::N_RTT_SAMPLE;\n\n        let rtt_1st = Duration::from_millis(50);\n\n        let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;\n        sender.hystart.start_round(next_rnd);\n        // Send 1st round packets.\n        for _ in 0..n_rtt_sample {\n            sender.send_packet(size);\n        }\n        sender.update_app_limited(false);\n\n        // Receiving Acks.\n        sender.advance_time(rtt_1st);\n        sender.update_rtt(rtt_1st);\n        sender.ack_n_packets(n_rtt_sample, size);\n\n        // Not in CSS yet.\n        assert!(sender.hystart.css_start_time().is_none());\n\n        // 2nd round.\n        let mut rtt_2nd = Duration::from_millis(100);\n        sender.advance_time(rtt_2nd);\n        // Send 2nd round packets.\n        let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;\n        sender.hystart.start_round(next_rnd);\n        for _ in 0..n_rtt_sample {\n            sender.send_packet(size);\n        }\n        sender.update_app_limited(false);\n\n        // Receiving Acks.\n        // Last ack will cause to exit to CSS.\n        let mut cwnd_prev = sender.congestion_window();\n\n        for _ in 0..n_rtt_sample {\n            cwnd_prev = sender.congestion_window();\n            sender.update_rtt(rtt_2nd);\n            sender.ack_n_packets(1, size);\n            // Keep increasing RTT so that hystart exits to CSS.\n            rtt_2nd += rtt_2nd.saturating_add(Duration::from_millis(4));\n        }\n\n        // Now we are in CSS.\n        assert!(sender.hystart.css_start_time().is_some());\n        assert_eq!(sender.congestion_window(), cwnd_prev + size);\n\n        // Run 5 (CSS_ROUNDS) in CSS, to exit to congestion avoidance.\n        let rtt_css = Duration::from_millis(100);\n        sender.advance_time(rtt_css);\n\n        for _ in 0..hystart::CSS_ROUNDS {\n            // Send a round of packets.\n            let next_rnd = sender.next_pkt + n_rtt_sample as u64 - 1;\n            sender.hystart.start_round(next_rnd);\n            for _ in 0..n_rtt_sample {\n                sender.send_packet(size);\n            }\n            sender.update_app_limited(false);\n\n            // Receiving Acks.\n            sender.update_rtt(rtt_css);\n            sender.ack_n_packets(n_rtt_sample, size);\n        }\n        // Now we are in congestion avoidance.\n        assert_eq!(sender.congestion_window(), sender.ssthresh.get());\n    }\n\n    #[test]\n    fn cubic_spurious_congestion_event() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        let prev_cwnd = sender.congestion_window();\n\n        // Send initcwnd full MSS packets to become no longer app limited\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n        sender.lose_n_packets(1, size, None);\n\n        // After congestion event, cwnd will be reduced.\n        let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;\n        assert_eq!(sender.congestion_window(), cur_cwnd);\n\n        // Ack more than cwnd bytes with rtt=100ms\n        let rtt = Duration::from_millis(100);\n        sender.update_rtt(rtt);\n\n        let acked = Acked {\n            pkt_num: 0,\n            // To exit from recovery\n            time_sent: sender.time + rtt,\n            size,\n            delivered: 0,\n            delivered_time: sender.time,\n            first_sent_time: sender.time,\n            is_app_limited: false,\n            rtt: Duration::ZERO,\n        };\n\n        // Trigger detecting spurious congestion event\n        sender.inject_ack(acked, sender.time + rtt + Duration::from_millis(5));\n\n        // This is from slow start, no rollback.\n        assert_eq!(sender.congestion_window(), cur_cwnd);\n\n        sender.advance_time(rtt);\n\n        let prev_cwnd = sender.congestion_window();\n\n        sender.lose_n_packets(1, size, Some(sender.time));\n\n        // After congestion event, cwnd will be reduced.\n        let cur_cwnd = (cur_cwnd as f64 * BETA_CUBIC) as usize;\n        assert_eq!(sender.congestion_window(), cur_cwnd);\n\n        sender.advance_time(rtt + Duration::from_millis(5));\n\n        let acked = Acked {\n            pkt_num: 0,\n            // To exit from recovery\n            time_sent: sender.time + rtt,\n            size,\n            delivered: 0,\n            delivered_time: sender.time,\n            first_sent_time: sender.time,\n            is_app_limited: false,\n            rtt: Duration::ZERO,\n        };\n\n        // Trigger detecting spurious congestion event.\n        sender.inject_ack(acked, sender.time + rtt + Duration::from_millis(5));\n\n        // cwnd is rolled back to the previous one.\n        assert_eq!(sender.congestion_window(), prev_cwnd);\n    }\n\n    #[test]\n    fn cubic_fast_convergence() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        let prev_cwnd = sender.congestion_window;\n\n        // Send initcwnd full MSS packets to become no longer app limited\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n\n        // Trigger congestion event to update ssthresh\n        sender.lose_n_packets(1, size, None);\n\n        // After 1st congestion event, cwnd will be reduced.\n        let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;\n        assert_eq!(sender.congestion_window, cur_cwnd);\n\n        // Shift current time by 1 RTT.\n        let rtt = Duration::from_millis(100);\n        sender.update_rtt(rtt);\n        // Exit from the recovery.\n        sender.advance_time(rtt);\n\n        // Drain most packets in flight except for 1 to avoid adjustments to\n        // cubic.last_sent_time when idle, see on_packet_sent.\n        sender.ack_n_packets(sender.initial_congestion_window_packets - 2, size);\n\n        // During Congestion Avoidance, it will take 6 ACKs to\n        // increase cwnd by 1 MSS.  But ACKed packets must have been\n        // sent after the loss event.\n        for _ in 0..7 {\n            sender.send_packet(size);\n        }\n        // ACK the packet that was sent before the loss event.\n        sender.ack_n_packets(1, size);\n        sender.advance_time(rtt);\n        for _ in 0..6 {\n            sender.ack_n_packets(1, size);\n        }\n\n        assert_eq!(sender.congestion_window, cur_cwnd + size);\n\n        let prev_cwnd = sender.congestion_window;\n\n        // Fast convergence: now there is 2nd congestion event and\n        // cwnd is not fully recovered to w_max, w_max will be\n        // further reduced.\n        sender.lose_n_packets(1, size, None);\n\n        // After 2nd congestion event, cwnd will be reduced.\n        let cur_cwnd = (prev_cwnd as f64 * BETA_CUBIC) as usize;\n        assert_eq!(sender.congestion_window, cur_cwnd);\n\n        // w_max will be further reduced, not prev_cwnd\n        assert_eq!(\n            sender.cubic_state.w_max,\n            prev_cwnd as f64 * (1.0 + BETA_CUBIC) / 2.0\n        );\n    }\n\n    #[test]\n    fn cubic_perpetual_recovery_trap() {\n        // Reproduces the bug where cwnd stays pinned at minimum after a\n        // loss event because the idle-time epoch shift pushes\n        // congestion_recovery_start_time into the future on every\n        // send -> ACK -> send cycle when bif transiently hits 0.\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n        let rtt = Duration::from_millis(100);\n\n        // Fill the pipe.\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n\n        sender.update_rtt(rtt);\n        sender.advance_time(rtt);\n\n        // Trigger a loss to enter recovery and reduce cwnd.\n        let initial_cwnd = sender.congestion_window;\n        sender.lose_n_packets(1, size, None);\n        let post_loss_cwnd = sender.congestion_window;\n        assert_eq!(post_loss_cwnd, (initial_cwnd as f64 * BETA_CUBIC) as usize);\n        let initial_recovery_start_time =\n            sender.congestion_recovery_start_time.unwrap();\n        assert_eq!(initial_recovery_start_time, sender.time);\n\n        // ACK remaining in-flight packets to exit recovery.\n        sender.ack_n_packets(sender.initial_congestion_window_packets - 1, size);\n        assert_eq!(sender.bytes_in_flight, 0);\n\n        // Now simulate the problematic pattern: send a small burst at\n        // minimum cwnd, ACK it (bif drops to 0), advance one RTT, repeat.\n        // With the bug, recovery_start_time would advance on every\n        // cycle, trapping cwnd.  With the fix, cwnd must grow.\n        let packets_per_burst = cmp::max(1, sender.congestion_window / size);\n\n        for _ in 0..20 {\n            for _ in 0..packets_per_burst {\n                sender.send_packet(size);\n            }\n\n            sender.advance_time(rtt);\n            sender.ack_n_packets(packets_per_burst, size);\n            assert_eq!(sender.bytes_in_flight, 0);\n        }\n\n        // cwnd must have grown beyond the post-loss value.\n        assert!(\n            sender.congestion_window > post_loss_cwnd,\n            \"cwnd stuck at {} (post-loss {}): perpetual recovery trap\",\n            sender.congestion_window,\n            post_loss_cwnd,\n        );\n        // Recovery start hasn't changed.\n        assert_eq!(\n            sender.congestion_recovery_start_time.unwrap() -\n                initial_recovery_start_time,\n            Duration::ZERO,\n            \"epoch should not have shifted without idle gap\",\n        );\n    }\n\n    #[test]\n    fn cubic_genuine_idle_epoch_shift() {\n        // After a loss + recovery, a long idle period (seconds) should\n        // shift the epoch so that cwnd growth resumes from where it\n        // left off rather than jumping.\n        let mut sender = test_sender();\n        let test_start = sender.time;\n        let size = sender.max_datagram_size;\n        let rtt = Duration::from_millis(100);\n\n        // Fill the pipe and trigger loss.\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n        sender.update_rtt(rtt);\n        sender.advance_time(rtt);\n        sender.lose_n_packets(1, size, None);\n\n        let initial_recovery_start =\n            sender.congestion_recovery_start_time.unwrap();\n        assert_eq!(\n            initial_recovery_start,\n            test_start + rtt,\n            \"Recovery start is set\",\n        );\n\n        let post_loss_cwnd = sender.congestion_window;\n\n        // ACK remaining to exit recovery.\n        sender.ack_n_packets(sender.initial_congestion_window_packets - 1, size);\n\n        // App-limited like behavior\n        // Long idle: 5 seconds of silence.\n        let idle_duration = Duration::from_secs(5);\n        sender.advance_time(idle_duration);\n\n        // Resume sending and ACKing.\n        let packets_per_burst = cmp::max(1, sender.congestion_window / size);\n\n        for _ in 0..packets_per_burst {\n            sender.send_packet(size);\n        }\n        sender.advance_time(rtt);\n        sender.ack_n_packets(packets_per_burst, size);\n\n        // After one RTT of sending post-idle, cwnd should not have\n        // shrunk.\n        assert_eq!(sender.congestion_window, post_loss_cwnd);\n\n        // Verify recovery_start_time was shifted forward (i.e., the\n        // idle shift logic ran).\n        let recovery_start = sender.congestion_recovery_start_time.unwrap();\n        assert!(\n            recovery_start >\n                sender.cubic_state.last_sent_time.unwrap() - idle_duration,\n            \"epoch should have been shifted forward by idle period\",\n        );\n        assert_eq!(\n            recovery_start - sender.cubic_state.last_sent_time.unwrap(),\n            Duration::ZERO,\n            \"epoch should have been shifted forward by idle period\",\n        );\n        assert_eq!(\n            recovery_start - initial_recovery_start,\n            idle_duration,\n            \"Recovery start should have moved forward by the idle duration\",\n        );\n    }\n\n    #[test]\n    fn cubic_zero_bytes_sent_no_epoch_shift() {\n        // A zero-byte packet (e.g. PADDING-only) must NOT shift\n        // congestion_recovery_start_time or update last_sent_time.\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n        let rtt = Duration::from_millis(100);\n\n        // Fill pipe, trigger loss to set congestion_recovery_start_time.\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n        sender.update_rtt(rtt);\n        sender.advance_time(rtt);\n        sender.lose_n_packets(1, size, None);\n\n        // ACK everything → bif = 0.\n        sender.ack_n_packets(sender.initial_congestion_window_packets - 1, size);\n        assert_eq!(sender.bytes_in_flight, 0);\n\n        // Record state before zero-byte send.\n        let recovery_start_before = sender.congestion_recovery_start_time;\n        let last_sent_before = sender.cubic_state.last_sent_time;\n\n        // Advance time so any shift would be visible.\n        sender.advance_time(Duration::from_millis(500));\n\n        // Send a zero-byte packet (simulates PADDING-only).\n        sender.send_packet(0);\n\n        // Neither recovery_start_time nor last_sent_time should change.\n        assert_eq!(\n            sender.congestion_recovery_start_time, recovery_start_before,\n            \"recovery_start_time must not shift on zero-byte send\",\n        );\n        assert_eq!(\n            sender.cubic_state.last_sent_time, last_sent_before,\n            \"last_sent_time must not update on zero-byte send\",\n        );\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/congestion/delivery_rate.rs",
    "content": "// Copyright (C) 2020-2022, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Delivery rate estimation.\n//!\n//! This implements the algorithm for estimating delivery rate as described in\n//! <https://tools.ietf.org/html/draft-cheng-iccrg-delivery-rate-estimation-01>\n\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse crate::recovery::bandwidth::Bandwidth;\n\nuse super::Acked;\nuse super::Sent;\n\n#[derive(Debug)]\npub struct Rate {\n    delivered: usize,\n\n    delivered_time: Instant,\n\n    first_sent_time: Instant,\n\n    // Packet number of the last sent packet with app limited.\n    end_of_app_limited: u64,\n\n    // Packet number of the last sent packet.\n    last_sent_packet: u64,\n\n    // Packet number of the largest acked packet.\n    largest_acked: u64,\n\n    // Sample of rate estimation.\n    rate_sample: RateSample,\n}\n\nimpl Default for Rate {\n    fn default() -> Self {\n        let now = Instant::now();\n\n        Rate {\n            delivered: 0,\n\n            delivered_time: now,\n\n            first_sent_time: now,\n\n            end_of_app_limited: 0,\n\n            last_sent_packet: 0,\n\n            largest_acked: 0,\n\n            rate_sample: RateSample::new(),\n        }\n    }\n}\n\nimpl Rate {\n    pub fn on_packet_sent(\n        &mut self, pkt: &mut Sent, bytes_in_flight: usize, bytes_lost: u64,\n    ) {\n        // No packets in flight.\n        if bytes_in_flight == 0 {\n            self.first_sent_time = pkt.time_sent;\n            self.delivered_time = pkt.time_sent;\n        }\n\n        pkt.first_sent_time = self.first_sent_time;\n        pkt.delivered_time = self.delivered_time;\n        pkt.delivered = self.delivered;\n        pkt.is_app_limited = self.app_limited();\n        pkt.tx_in_flight = bytes_in_flight;\n        pkt.lost = bytes_lost;\n\n        self.last_sent_packet = pkt.pkt_num;\n    }\n\n    // Update the delivery rate sample when a packet is acked.\n    pub fn update_rate_sample(&mut self, pkt: &Acked, now: Instant) {\n        self.delivered += pkt.size;\n        self.delivered_time = now;\n\n        // Update info using the newest packet. If rate_sample is not yet\n        // initialized, initialize with the first packet.\n        if self.rate_sample.prior_time.is_none() ||\n            pkt.delivered >= self.rate_sample.prior_delivered\n        {\n            self.rate_sample.prior_delivered = pkt.delivered;\n            self.rate_sample.prior_time = Some(pkt.delivered_time);\n            self.rate_sample.is_app_limited = pkt.is_app_limited;\n            self.rate_sample.send_elapsed =\n                pkt.time_sent.saturating_duration_since(pkt.first_sent_time);\n            self.rate_sample.rtt = pkt.rtt;\n            self.rate_sample.ack_elapsed = self\n                .delivered_time\n                .saturating_duration_since(pkt.delivered_time);\n\n            self.first_sent_time = pkt.time_sent;\n        }\n\n        self.largest_acked = self.largest_acked.max(pkt.pkt_num);\n    }\n\n    pub fn generate_rate_sample(&mut self, min_rtt: Duration) {\n        // End app-limited phase if bubble is ACKed and gone.\n        if self.app_limited() && self.largest_acked > self.end_of_app_limited {\n            self.update_app_limited(false);\n        }\n\n        if self.rate_sample.prior_time.is_some() {\n            let interval = self\n                .rate_sample\n                .send_elapsed\n                .max(self.rate_sample.ack_elapsed);\n\n            self.rate_sample.delivered =\n                self.delivered - self.rate_sample.prior_delivered;\n            self.rate_sample.interval = interval;\n\n            if interval < min_rtt {\n                self.rate_sample.interval = Duration::ZERO;\n\n                // No reliable sample.\n                return;\n            }\n\n            if !interval.is_zero() {\n                let rate_sample_bandwidth = {\n                    let rate_sample_bytes_per_second = (self.rate_sample.delivered\n                        as f64 /\n                        interval.as_secs_f64())\n                        as u64;\n\n                    Bandwidth::from_bytes_per_second(rate_sample_bytes_per_second)\n                };\n\n                // Match the [linux] implementation and only generate a new\n                // sample delivery rate if either:\n                // - the sample was not app_limited\n                // - the new rate is higher than the previous value\n                //\n                // [linux] https://github.com/torvalds/linux/commit/eb8329e0a04db0061f714f033b4454326ba147f4\n                if !self.rate_sample.is_app_limited ||\n                    rate_sample_bandwidth > self.rate_sample.bandwidth\n                {\n                    self.update_delivery_rate(rate_sample_bandwidth);\n                }\n            }\n        }\n    }\n\n    fn update_delivery_rate(&mut self, bandwidth: Bandwidth) {\n        self.rate_sample.bandwidth = bandwidth;\n    }\n\n    pub fn update_app_limited(&mut self, v: bool) {\n        self.end_of_app_limited =\n            if v { self.last_sent_packet.max(1) } else { 0 };\n    }\n\n    pub fn app_limited(&mut self) -> bool {\n        self.end_of_app_limited != 0\n    }\n\n    #[cfg(test)]\n    pub fn delivered(&self) -> usize {\n        self.delivered\n    }\n\n    pub fn sample_delivery_rate(&self) -> Bandwidth {\n        self.rate_sample.bandwidth\n    }\n\n    #[cfg(test)]\n    pub fn sample_is_app_limited(&self) -> bool {\n        self.rate_sample.is_app_limited\n    }\n}\n\n#[derive(Debug)]\nstruct RateSample {\n    // The sample delivery_rate in bytes/sec\n    bandwidth: Bandwidth,\n\n    is_app_limited: bool,\n\n    interval: Duration,\n\n    delivered: usize,\n\n    prior_delivered: usize,\n\n    prior_time: Option<Instant>,\n\n    send_elapsed: Duration,\n\n    ack_elapsed: Duration,\n\n    rtt: Duration,\n}\n\nimpl RateSample {\n    const fn new() -> Self {\n        RateSample {\n            bandwidth: Bandwidth::zero(),\n            is_app_limited: false,\n            interval: Duration::ZERO,\n            delivered: 0,\n            prior_delivered: 0,\n            prior_time: None,\n            send_elapsed: Duration::ZERO,\n            ack_elapsed: Duration::ZERO,\n            rtt: Duration::ZERO,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use crate::packet;\n    use crate::ranges;\n    use crate::recovery::congestion::recovery::LegacyRecovery;\n    use crate::recovery::HandshakeStatus;\n    use crate::recovery::RecoveryOps;\n    use crate::test_utils;\n    use crate::Config;\n    use crate::OnAckReceivedOutcome;\n    use std::ops::Range;\n\n    // A [RateSample](delivery_rate::RateSample) is app_limited if it was\n    // generated when the [Rate](delivery_rate::Rate) was app_limited.\n    //\n    // The following test generates RateSamples before and after Rate is\n    // app_limited and asserts on app_limited status for samples.\n    #[test]\n    fn sample_is_app_limited() {\n        let config = Config::new(0xbabababa).unwrap();\n        let mut r = LegacyRecovery::new(&config);\n        let mut now = Instant::now();\n        let mss = r.max_datagram_size();\n\n        // Not App Limited prior to any activity\n        assert!(!r.congestion.delivery_rate.app_limited());\n        assert_eq!(r.congestion.delivery_rate.end_of_app_limited, 0);\n        assert!(!r.congestion.delivery_rate.sample_is_app_limited());\n\n        // Send/Ack first batch to generate a new delivery_rate sample.\n        let rtt = Duration::from_secs(2);\n        helper_send_and_ack_packets(&mut r, 0..4, now, rtt, mss);\n\n        // Marking Rate as app_limited.\n        r.delivery_rate_update_app_limited(true);\n        assert!(r.congestion.delivery_rate.app_limited());\n        assert_eq!(r.congestion.delivery_rate.end_of_app_limited, 3);\n\n        // Rate is app_limited.\n        assert!(r.congestion.delivery_rate.app_limited());\n        assert!(!r.congestion.delivery_rate.sample_is_app_limited());\n\n        // Send/Ack second batch to generate a new delivery_rate sample.\n        now += rtt;\n        helper_send_and_ack_packets(&mut r, 4..8, now, rtt, mss);\n\n        // Rate is no longer app limited since we sent a packet larger than\n        // `end_of_app_limited`\n        assert!(!r.congestion.delivery_rate.app_limited());\n        assert_eq!(r.congestion.delivery_rate.end_of_app_limited, 0);\n        // The RateSample is also app_limited\n        assert!(r.congestion.delivery_rate.sample_is_app_limited());\n    }\n\n    // A [RateSample](delivery_rate::RateSample) is is only updated if either not\n    // app_limited or greater than the previous value.\n    #[test]\n    fn app_limited_delivery_rate() {\n        // confirm that rate sample is not generated when app limited\n        let config = Config::new(0xbabababa).unwrap();\n        let mut r = LegacyRecovery::new(&config);\n        let mut now = Instant::now();\n        let mss = r.max_datagram_size();\n\n        // Not App Limited prior to any activity\n        assert!(!r.congestion.delivery_rate.app_limited());\n        assert_eq!(r.congestion.delivery_rate.end_of_app_limited, 0);\n        assert!(!r.congestion.delivery_rate.sample_is_app_limited());\n\n        // First batch\n        // Send/Ack first batch to generate a new delivery_rate sample.\n        let mut rtt = Duration::from_secs(2);\n        helper_send_and_ack_packets(&mut r, 0..2, now, rtt, mss);\n\n        // Marking Rate as app_limited.\n        r.delivery_rate_update_app_limited(true);\n        assert!(r.congestion.delivery_rate.app_limited());\n        assert_eq!(r.congestion.delivery_rate.end_of_app_limited, 1);\n        assert!(!r.congestion.delivery_rate.sample_is_app_limited());\n\n        let first_delivery_rate = r.delivery_rate().to_bytes_per_second();\n        let expected_delivery_rate = (mss * 2) as u64 / rtt.as_secs();\n        assert_eq!(expected_delivery_rate, 1200);\n        assert_eq!(first_delivery_rate, expected_delivery_rate);\n\n        // Second batch\n        // Since Rtt is larger, the delivery_rate will be smaller and not generate\n        // a new RateSample.\n        now += rtt;\n        rtt = Duration::from_secs(4);\n        helper_send_and_ack_packets(&mut r, 2..4, now, rtt, mss);\n\n        // Rate is no longer app limited since we sent a packet larger than\n        // `end_of_app_limited`\n        assert!(!r.congestion.delivery_rate.app_limited());\n        assert_eq!(r.congestion.delivery_rate.end_of_app_limited, 0);\n        // The RateSample is also app_limited\n        assert!(r.congestion.delivery_rate.sample_is_app_limited());\n\n        // Delivery rate NOT updated since the delivery rate is less than previous\n        // value\n        let expected_delivery_rate = (mss * 2) as u64 / rtt.as_secs();\n        assert_eq!(expected_delivery_rate, 600);\n        let app_limited_delivery_rate = r.delivery_rate().to_bytes_per_second();\n        assert_eq!(app_limited_delivery_rate, first_delivery_rate);\n\n        // Marking Rate as app_limited.\n        r.delivery_rate_update_app_limited(true);\n        assert!(r.congestion.delivery_rate.app_limited());\n        assert_eq!(r.congestion.delivery_rate.end_of_app_limited, 3);\n        // The RateSample is also app_limited\n        assert!(r.congestion.delivery_rate.sample_is_app_limited());\n\n        // Third batch\n        // Since Rtt is smaller, the delivery_rate will be larger and not generate\n        // a new RateSample even when app_limited.\n        now += rtt;\n        rtt = Duration::from_secs(1);\n        helper_send_and_ack_packets(&mut r, 4..6, now, rtt, mss);\n\n        // Rate is no longer app limited since we sent a packet larger than\n        // `end_of_app_limited`\n        assert!(!r.congestion.delivery_rate.app_limited());\n        assert_eq!(r.congestion.delivery_rate.end_of_app_limited, 0);\n        // The RateSample is also app_limited\n        assert!(r.congestion.delivery_rate.sample_is_app_limited());\n\n        // Delivery rate NOT updated since the delivery rate is less than previous\n        // value\n        let expected_delivery_rate = (mss * 2) as u64 / rtt.as_secs();\n        assert_eq!(expected_delivery_rate, 2400);\n        let app_limited_delivery_rate = r.delivery_rate().to_bytes_per_second();\n        assert_eq!(app_limited_delivery_rate, expected_delivery_rate);\n    }\n\n    #[test]\n    fn rate_check() {\n        let config = Config::new(0xbabababa).unwrap();\n        let mut r = LegacyRecovery::new(&config);\n\n        let now = Instant::now();\n        let mss = r.max_datagram_size();\n\n        // Send 2 packets.\n        for pn in 0..2 {\n            let pkt = test_utils::helper_packet_sent(pn, now, mss);\n\n            r.on_packet_sent(\n                pkt,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n        }\n\n        let rtt = Duration::from_millis(50);\n        let now = now + rtt;\n\n        // Ack 2 packets.\n        for pn in 0..2 {\n            let acked = Acked {\n                pkt_num: pn,\n                time_sent: now,\n                size: mss,\n                rtt,\n                delivered: 0,\n                delivered_time: now,\n                first_sent_time: now.checked_sub(rtt).unwrap(),\n                is_app_limited: false,\n            };\n\n            r.congestion.delivery_rate.update_rate_sample(&acked, now);\n        }\n\n        // Update rate sample after 1 rtt.\n        r.congestion.delivery_rate.generate_rate_sample(rtt);\n\n        // Bytes acked so far.\n        assert_eq!(r.congestion.delivery_rate.delivered(), 2400);\n\n        // Estimated delivery rate = (1200 x 2) / 0.05s = 48000.\n        assert_eq!(r.delivery_rate().to_bytes_per_second(), 48000);\n    }\n\n    #[test]\n    fn app_limited_cwnd_full() {\n        let config = Config::new(0xbabababa).unwrap();\n        let mut r = LegacyRecovery::new(&config);\n\n        let now = Instant::now();\n        let mss = r.max_datagram_size();\n\n        // Not App Limited prior to any activity\n        assert!(!r.app_limited());\n        assert!(!r.congestion.delivery_rate.sample_is_app_limited());\n\n        // Send 10 packets to fill cwnd.\n        for pn in 0..5 {\n            let pkt = test_utils::helper_packet_sent(pn, now, mss);\n            r.on_packet_sent(\n                pkt,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n        }\n\n        // App Limited after sending partial cwnd worth of data\n        assert!(r.app_limited());\n        assert!(!r.congestion.delivery_rate.sample_is_app_limited());\n\n        for pn in 5..10 {\n            let pkt = test_utils::helper_packet_sent(pn, now, mss);\n            r.on_packet_sent(\n                pkt,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n        }\n\n        // Not App Limited after sending full cwnd worth of data\n        assert!(!r.app_limited());\n        assert!(!r.congestion.delivery_rate.sample_is_app_limited());\n    }\n\n    fn helper_send_and_ack_packets(\n        recovery: &mut LegacyRecovery, range: Range<u64>, now: Instant,\n        rtt: Duration, mss: usize,\n    ) {\n        for pn in range.clone() {\n            let pkt = test_utils::helper_packet_sent(pn, now, mss);\n            recovery.on_packet_sent(\n                pkt,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n        }\n\n        let packet_count = range.clone().count();\n\n        // Ack packets, which generates a new delivery_rate\n        let mut acked = ranges::RangeSet::default();\n        acked.insert(range);\n\n        let ack_outcome = recovery\n            .on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now + rtt,\n                None,\n                \"\",\n            )\n            .unwrap();\n\n        assert_eq!(ack_outcome, OnAckReceivedOutcome {\n            lost_packets: 0,\n            lost_bytes: 0,\n            acked_bytes: mss * packet_count,\n            spurious_losses: 0,\n        });\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/congestion/hystart.rs",
    "content": "// Copyright (C) 2020, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! HyStart++\n//!\n//! This implementation is based on the following I-D:\n//!\n//! <https://datatracker.ietf.org/doc/html/draft-ietf-tcpm-hystartplusplus-04>\n\nuse std::cmp;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse super::Acked;\n\n/// Constants from I-D.\nconst MIN_RTT_THRESH: Duration = Duration::from_millis(4);\n\nconst MAX_RTT_THRESH: Duration = Duration::from_millis(16);\n\npub const N_RTT_SAMPLE: usize = 8;\n\npub const CSS_GROWTH_DIVISOR: usize = 4;\n\npub const CSS_ROUNDS: usize = 5;\n\n#[derive(Default)]\npub struct Hystart {\n    enabled: bool,\n\n    window_end: Option<u64>,\n\n    last_round_min_rtt: Duration,\n\n    current_round_min_rtt: Duration,\n\n    css_baseline_min_rtt: Duration,\n\n    rtt_sample_count: usize,\n\n    css_start_time: Option<Instant>,\n\n    css_round_count: usize,\n}\n\nimpl std::fmt::Debug for Hystart {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"window_end={:?} \", self.window_end)?;\n        write!(f, \"last_round_min_rtt={:?} \", self.last_round_min_rtt)?;\n        write!(f, \"current_round_min_rtt={:?} \", self.current_round_min_rtt)?;\n        write!(f, \"css_baseline_min_rtt={:?} \", self.css_baseline_min_rtt)?;\n        write!(f, \"rtt_sample_count={:?} \", self.rtt_sample_count)?;\n        write!(f, \"css_start_time={:?} \", self.css_start_time)?;\n        write!(f, \"css_round_count={:?}\", self.css_round_count)?;\n\n        Ok(())\n    }\n}\n\nimpl Hystart {\n    pub fn new(enabled: bool) -> Self {\n        Self {\n            enabled,\n\n            last_round_min_rtt: Duration::MAX,\n\n            current_round_min_rtt: Duration::MAX,\n\n            css_baseline_min_rtt: Duration::MAX,\n\n            ..Default::default()\n        }\n    }\n\n    pub fn enabled(&self) -> bool {\n        self.enabled\n    }\n\n    pub fn css_start_time(&self) -> Option<Instant> {\n        self.css_start_time\n    }\n\n    pub fn in_css(&self) -> bool {\n        self.enabled && self.css_start_time().is_some()\n    }\n\n    pub fn start_round(&mut self, pkt_num: u64) {\n        if self.window_end.is_none() {\n            self.window_end = Some(pkt_num);\n\n            self.last_round_min_rtt = self.current_round_min_rtt;\n\n            self.current_round_min_rtt = Duration::MAX;\n\n            self.rtt_sample_count = 0;\n        }\n    }\n\n    // On receiving ACK. Returns true if need to enter Congestion Avoidance.\n    pub fn on_packet_acked(\n        &mut self, packet: &Acked, rtt: Duration, now: Instant,\n    ) -> bool {\n        if !self.enabled {\n            return false;\n        }\n\n        self.current_round_min_rtt = cmp::min(self.current_round_min_rtt, rtt);\n\n        self.rtt_sample_count += 1;\n\n        // Slow Start.\n        if self.css_start_time().is_none() {\n            if self.rtt_sample_count >= N_RTT_SAMPLE &&\n                self.current_round_min_rtt != Duration::MAX &&\n                self.last_round_min_rtt != Duration::MAX\n            {\n                // clamp(min_rtt_thresh, last_round_min_rtt/8,\n                // max_rtt_thresh)\n                let rtt_thresh =\n                    cmp::max(self.last_round_min_rtt / 8, MIN_RTT_THRESH);\n                let rtt_thresh = cmp::min(rtt_thresh, MAX_RTT_THRESH);\n\n                // Check if we can exit to CSS.\n                if self.current_round_min_rtt >=\n                    self.last_round_min_rtt.saturating_add(rtt_thresh)\n                {\n                    self.css_baseline_min_rtt = self.current_round_min_rtt;\n                    self.css_start_time = Some(now);\n                }\n            }\n        } else {\n            // Conservative Slow Start.\n            if self.rtt_sample_count >= N_RTT_SAMPLE {\n                self.rtt_sample_count = 0;\n\n                if self.current_round_min_rtt < self.css_baseline_min_rtt {\n                    self.css_baseline_min_rtt = Duration::MAX;\n\n                    // Back to Slow Start.\n                    self.css_start_time = None;\n                    self.css_round_count = 0;\n                }\n            }\n        }\n\n        // Check if we reached the end of the round.\n        if let Some(end_pkt_num) = self.window_end {\n            if packet.pkt_num >= end_pkt_num {\n                // Start of a new round.\n                self.window_end = None;\n\n                if self.css_start_time().is_some() {\n                    self.css_round_count += 1;\n\n                    // End of CSS - exit to congestion avoidance.\n                    if self.css_round_count >= CSS_ROUNDS {\n                        self.css_round_count = 0;\n                        return true;\n                    }\n                }\n            }\n        }\n\n        false\n    }\n\n    // Return a cwnd increment during CSS (Conservative Slow Start).\n    pub fn css_cwnd_inc(&self, pkt_size: usize) -> usize {\n        pkt_size / CSS_GROWTH_DIVISOR\n    }\n\n    // Exit HyStart++ when entering congestion avoidance.\n    pub fn congestion_event(&mut self) {\n        self.window_end = None;\n        self.css_start_time = None;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn start_round() {\n        let mut hspp = Hystart::default();\n        let pkt_num = 100;\n\n        hspp.start_round(pkt_num);\n\n        assert_eq!(hspp.window_end, Some(pkt_num));\n        assert_eq!(hspp.current_round_min_rtt, Duration::MAX);\n    }\n\n    #[test]\n    fn css_cwnd_inc() {\n        let hspp = Hystart::default();\n        let datagram_size = 1200;\n\n        let css_cwnd_inc = hspp.css_cwnd_inc(datagram_size);\n\n        assert_eq!(datagram_size / CSS_GROWTH_DIVISOR, css_cwnd_inc);\n    }\n\n    #[test]\n    fn congestion_event() {\n        let mut hspp = Hystart::default();\n        let pkt_num = 100;\n\n        hspp.start_round(pkt_num);\n\n        assert_eq!(hspp.window_end, Some(pkt_num));\n\n        // When moving into CA mode, window_end should be cleared.\n        hspp.congestion_event();\n\n        assert_eq!(hspp.window_end, None);\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/congestion/mod.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Instant;\n\nuse self::recovery::Acked;\nuse super::bandwidth::Bandwidth;\nuse super::RecoveryConfig;\nuse super::Sent;\nuse crate::recovery::rtt;\nuse crate::recovery::rtt::RttStats;\nuse crate::recovery::CongestionControlAlgorithm;\nuse crate::StartupExit;\nuse crate::StartupExitReason;\n\npub struct SsThresh {\n    // Current slow start threshold.  Defaults to usize::MAX which\n    // indicates we're still in the initial slow start phase.\n    ssthresh: usize,\n\n    // Information about the slow start exit, if it already happened.\n    // Set on the first call to update().\n    startup_exit: Option<StartupExit>,\n}\n\nimpl Default for SsThresh {\n    fn default() -> Self {\n        Self {\n            ssthresh: usize::MAX,\n            startup_exit: None,\n        }\n    }\n}\n\nimpl SsThresh {\n    fn get(&self) -> usize {\n        self.ssthresh\n    }\n\n    fn startup_exit(&self) -> Option<StartupExit> {\n        self.startup_exit\n    }\n\n    fn update(&mut self, ssthresh: usize, in_css: bool) {\n        if self.startup_exit.is_none() {\n            let reason = if in_css {\n                // Exit happened in conservative slow start, attribute\n                // the exit to CSS.\n                StartupExitReason::ConservativeSlowStartRounds\n            } else {\n                // In normal slow start, attribute the exit to loss.\n                StartupExitReason::Loss\n            };\n            self.startup_exit = Some(StartupExit::new(ssthresh, None, reason));\n        }\n        self.ssthresh = ssthresh;\n    }\n}\n\npub struct Congestion {\n    // Congestion control.\n    pub(crate) cc_ops: &'static CongestionControlOps,\n\n    cubic_state: cubic::State,\n\n    // HyStart++.\n    pub(crate) hystart: hystart::Hystart,\n\n    // RFC6937 PRR.\n    pub(crate) prr: prr::PRR,\n\n    // The maximum size of a data aggregate scheduled and\n    // transmitted together.\n    send_quantum: usize,\n\n    pub(crate) congestion_window: usize,\n\n    pub(crate) ssthresh: SsThresh,\n\n    bytes_acked_sl: usize,\n\n    bytes_acked_ca: usize,\n\n    pub(crate) congestion_recovery_start_time: Option<Instant>,\n\n    pub(crate) app_limited: bool,\n\n    pub(crate) delivery_rate: delivery_rate::Rate,\n\n    /// Initial congestion window size in terms of packet count.\n    pub(crate) initial_congestion_window_packets: usize,\n\n    max_datagram_size: usize,\n\n    pub(crate) lost_count: usize,\n\n    pub(crate) enable_cubic_idle_restart_fix: bool,\n}\n\nimpl Congestion {\n    pub(crate) fn from_config(recovery_config: &RecoveryConfig) -> Self {\n        let initial_congestion_window = recovery_config.max_send_udp_payload_size *\n            recovery_config.initial_congestion_window_packets;\n\n        let mut cc = Congestion {\n            congestion_window: initial_congestion_window,\n\n            ssthresh: Default::default(),\n\n            bytes_acked_sl: 0,\n\n            bytes_acked_ca: 0,\n\n            congestion_recovery_start_time: None,\n\n            cc_ops: recovery_config.cc_algorithm.into(),\n\n            cubic_state: cubic::State::default(),\n\n            app_limited: false,\n\n            lost_count: 0,\n\n            initial_congestion_window_packets: recovery_config\n                .initial_congestion_window_packets,\n\n            max_datagram_size: recovery_config.max_send_udp_payload_size,\n\n            send_quantum: initial_congestion_window,\n\n            delivery_rate: delivery_rate::Rate::default(),\n\n            hystart: hystart::Hystart::new(recovery_config.hystart),\n\n            prr: prr::PRR::default(),\n\n            enable_cubic_idle_restart_fix: recovery_config\n                .enable_cubic_idle_restart_fix,\n        };\n\n        (cc.cc_ops.on_init)(&mut cc);\n\n        cc\n    }\n\n    pub(crate) fn in_congestion_recovery(&self, sent_time: Instant) -> bool {\n        match self.congestion_recovery_start_time {\n            Some(congestion_recovery_start_time) =>\n                sent_time <= congestion_recovery_start_time,\n\n            None => false,\n        }\n    }\n\n    /// The most recent data delivery rate estimate.\n    pub(crate) fn delivery_rate(&self) -> Bandwidth {\n        self.delivery_rate.sample_delivery_rate()\n    }\n\n    pub(crate) fn send_quantum(&self) -> usize {\n        self.send_quantum\n    }\n\n    pub(crate) fn congestion_window(&self) -> usize {\n        self.congestion_window\n    }\n\n    fn update_app_limited(&mut self, v: bool) {\n        self.app_limited = v;\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub(crate) fn on_packet_sent(\n        &mut self, bytes_in_flight: usize, sent_bytes: usize, now: Instant,\n        pkt: &mut Sent, bytes_lost: u64, in_flight: bool,\n    ) {\n        if in_flight {\n            self.update_app_limited(\n                (bytes_in_flight + sent_bytes) < self.congestion_window,\n            );\n\n            (self.cc_ops.on_packet_sent)(self, sent_bytes, bytes_in_flight, now);\n\n            self.prr.on_packet_sent(sent_bytes);\n\n            // HyStart++: Start of the round in a slow start.\n            if self.hystart.enabled() &&\n                self.congestion_window < self.ssthresh.get()\n            {\n                self.hystart.start_round(pkt.pkt_num);\n            }\n        }\n\n        pkt.time_sent = now;\n\n        // bytes_in_flight is already updated. Use previous value.\n        self.delivery_rate\n            .on_packet_sent(pkt, bytes_in_flight, bytes_lost);\n    }\n\n    pub(crate) fn on_packets_acked(\n        &mut self, bytes_in_flight: usize, acked: &mut Vec<Acked>,\n        rtt_stats: &RttStats, now: Instant,\n    ) {\n        // Update delivery rate sample per acked packet.\n        for pkt in acked.iter() {\n            self.delivery_rate.update_rate_sample(pkt, now);\n        }\n\n        // Fill in a rate sample.\n        self.delivery_rate.generate_rate_sample(*rtt_stats.min_rtt);\n\n        // Call congestion control hooks.\n        (self.cc_ops.on_packets_acked)(\n            self,\n            bytes_in_flight,\n            acked,\n            now,\n            rtt_stats,\n        );\n    }\n}\n\npub(crate) struct CongestionControlOps {\n    pub on_init: fn(r: &mut Congestion),\n\n    pub on_packet_sent: fn(\n        r: &mut Congestion,\n        sent_bytes: usize,\n        bytes_in_flight: usize,\n        now: Instant,\n    ),\n\n    pub on_packets_acked: fn(\n        r: &mut Congestion,\n        bytes_in_flight: usize,\n        packets: &mut Vec<Acked>,\n        now: Instant,\n        rtt_stats: &RttStats,\n    ),\n\n    pub congestion_event: fn(\n        r: &mut Congestion,\n        bytes_in_flight: usize,\n        lost_bytes: usize,\n        largest_lost_packet: &Sent,\n        now: Instant,\n    ),\n\n    pub checkpoint: fn(r: &mut Congestion),\n\n    pub rollback: fn(r: &mut Congestion) -> bool,\n\n    #[cfg(feature = \"qlog\")]\n    pub state_str: fn(r: &Congestion, now: Instant) -> &'static str,\n\n    pub debug_fmt: fn(\n        r: &Congestion,\n        formatter: &mut std::fmt::Formatter,\n    ) -> std::fmt::Result,\n}\n\nimpl From<CongestionControlAlgorithm> for &'static CongestionControlOps {\n    fn from(algo: CongestionControlAlgorithm) -> Self {\n        match algo {\n            CongestionControlAlgorithm::Reno => &reno::RENO,\n            CongestionControlAlgorithm::CUBIC => &cubic::CUBIC,\n            // Bbr2Gcongestion is routed to the congestion implementation in\n            // the gcongestion directory by Recovery::new_with_config;\n            // LegacyRecovery never gets a RecoveryConfig with the\n            // Bbr2Gcongestion algorithm.\n            CongestionControlAlgorithm::Bbr2Gcongestion => unreachable!(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn ssthresh_init() {\n        let ssthresh: SsThresh = Default::default();\n        assert_eq!(ssthresh.get(), usize::MAX);\n        assert_eq!(ssthresh.startup_exit(), None);\n    }\n\n    #[test]\n    fn ssthresh_in_css() {\n        let expected_startup_exit = StartupExit::new(\n            1000,\n            None,\n            StartupExitReason::ConservativeSlowStartRounds,\n        );\n        let mut ssthresh: SsThresh = Default::default();\n        ssthresh.update(1000, true);\n        assert_eq!(ssthresh.get(), 1000);\n        assert_eq!(ssthresh.startup_exit(), Some(expected_startup_exit));\n\n        ssthresh.update(2000, true);\n        assert_eq!(ssthresh.get(), 2000);\n        // startup_exit is only updated on the first update.\n        assert_eq!(ssthresh.startup_exit(), Some(expected_startup_exit));\n\n        ssthresh.update(500, false);\n        assert_eq!(ssthresh.get(), 500);\n        assert_eq!(ssthresh.startup_exit(), Some(expected_startup_exit));\n    }\n\n    #[test]\n    fn ssthresh_in_slow_start() {\n        let expected_startup_exit =\n            StartupExit::new(1000, None, StartupExitReason::Loss);\n        let mut ssthresh: SsThresh = Default::default();\n        ssthresh.update(1000, false);\n        assert_eq!(ssthresh.get(), 1000);\n        assert_eq!(ssthresh.startup_exit(), Some(expected_startup_exit));\n\n        ssthresh.update(2000, true);\n        assert_eq!(ssthresh.get(), 2000);\n        // startup_exit is only updated on the first update.\n        assert_eq!(ssthresh.startup_exit(), Some(expected_startup_exit));\n\n        ssthresh.update(500, false);\n        assert_eq!(ssthresh.get(), 500);\n        assert_eq!(ssthresh.startup_exit(), Some(expected_startup_exit));\n    }\n}\n\nmod cubic;\nmod delivery_rate;\nmod hystart;\nmod prr;\npub(crate) mod recovery;\nmod reno;\n\n#[cfg(test)]\nmod test_sender;\n"
  },
  {
    "path": "quiche/src/recovery/congestion/prr.rs",
    "content": "// Copyright (C) 2021, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Proportional Rate Reduction\n//!\n//! This implementation is based on the following RFC:\n//!\n//! <https://datatracker.ietf.org/doc/html/rfc6937>\n\nuse std::cmp;\n\n#[derive(Default, Debug)]\npub struct PRR {\n    // Total bytes delivered during recovery.\n    prr_delivered: usize,\n\n    // FlightSize at the start of recovery.\n    recoverfs: usize,\n\n    // Total bytes sent during recovery.\n    prr_out: usize,\n\n    // Total additional bytes can be sent for retransmit during recovery.\n    pub snd_cnt: usize,\n}\n\nimpl PRR {\n    pub fn on_packet_sent(&mut self, sent_bytes: usize) {\n        self.prr_out += sent_bytes;\n\n        self.snd_cnt = self.snd_cnt.saturating_sub(sent_bytes);\n    }\n\n    pub fn congestion_event(&mut self, bytes_in_flight: usize) {\n        self.prr_delivered = 0;\n\n        self.recoverfs = bytes_in_flight;\n\n        self.prr_out = 0;\n\n        self.snd_cnt = 0;\n    }\n\n    pub fn on_packet_acked(\n        &mut self, delivered_data: usize, pipe: usize, ssthresh: usize,\n        max_datagram_size: usize,\n    ) {\n        self.prr_delivered += delivered_data;\n\n        self.snd_cnt = if pipe > ssthresh {\n            // Proportional Rate Reduction.\n            if self.recoverfs > 0 {\n                (self.prr_delivered * ssthresh)\n                    .div_ceil(self.recoverfs)\n                    .saturating_sub(self.prr_out)\n            } else {\n                0\n            }\n        } else {\n            // PRR-SSRB.\n            let limit = cmp::max(\n                self.prr_delivered.saturating_sub(self.prr_out),\n                delivered_data,\n            ) + max_datagram_size;\n\n            // Attempt to catch up, as permitted by limit\n            cmp::min(ssthresh - pipe, limit)\n        };\n\n        // snd_cnt should be a positive number.\n        self.snd_cnt = cmp::max(self.snd_cnt, 0);\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn congestion_event() {\n        let mut prr = PRR::default();\n        let bytes_in_flight = 1000;\n\n        prr.congestion_event(bytes_in_flight);\n\n        assert_eq!(prr.recoverfs, bytes_in_flight);\n        assert_eq!(prr.snd_cnt, 0);\n    }\n\n    #[test]\n    fn on_packet_sent() {\n        let mut prr = PRR::default();\n        let bytes_in_flight = 1000;\n        let bytes_sent = 500;\n\n        prr.congestion_event(bytes_in_flight);\n\n        prr.on_packet_sent(bytes_sent);\n\n        assert_eq!(prr.prr_out, bytes_sent);\n        assert_eq!(prr.snd_cnt, 0);\n    }\n\n    #[test]\n    fn on_packet_acked_prr() {\n        let mut prr = PRR::default();\n        let max_datagram_size = 1000;\n        let bytes_in_flight = max_datagram_size * 10;\n        let ssthresh = bytes_in_flight / 2;\n        let acked = 1000;\n\n        prr.congestion_event(bytes_in_flight);\n\n        // pipe > ssthresh uses PRR algorithm.\n        let pipe = bytes_in_flight;\n\n        prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);\n\n        assert_eq!(prr.snd_cnt, 500);\n\n        let snd_cnt = prr.snd_cnt;\n\n        // send one more allowed by snd_cnt\n        prr.on_packet_sent(snd_cnt);\n\n        prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);\n\n        assert_eq!(prr.snd_cnt, 500);\n    }\n\n    #[test]\n    fn on_packet_acked_prr_overflow() {\n        let mut prr = PRR::default();\n        let max_datagram_size = 1000;\n        let bytes_in_flight = max_datagram_size * 10;\n        let ssthresh = bytes_in_flight / 2;\n        let acked = 1000;\n\n        prr.congestion_event(bytes_in_flight);\n\n        prr.on_packet_sent(max_datagram_size);\n\n        // pipe > ssthresh uses PRR algorithm.\n        let pipe = bytes_in_flight + max_datagram_size;\n\n        prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);\n\n        assert_eq!(prr.snd_cnt, 0);\n    }\n\n    #[test]\n    fn on_packet_acked_prr_zero_in_flight() {\n        let mut prr = PRR::default();\n        let max_datagram_size = 1000;\n        let bytes_in_flight = 0;\n        let ssthresh = 3000;\n        let acked = 1000;\n\n        prr.congestion_event(bytes_in_flight);\n\n        // pipe > ssthresh uses PRR algorithm.\n        let pipe = ssthresh + 1000;\n\n        prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);\n\n        assert_eq!(prr.snd_cnt, 0);\n    }\n\n    #[test]\n    fn on_packet_acked_prr_ssrb() {\n        let mut prr = PRR::default();\n        let max_datagram_size = 1000;\n        let bytes_in_flight = max_datagram_size * 10;\n        let ssthresh = bytes_in_flight / 2;\n        let acked = 1000;\n\n        prr.congestion_event(bytes_in_flight);\n\n        // pipe <= ssthresh uses PRR-SSRB algorithm.\n        let pipe = max_datagram_size;\n\n        prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);\n\n        assert_eq!(prr.snd_cnt, 2000);\n\n        let snd_cnt = prr.snd_cnt;\n\n        // send one more allowed by snd_cnt\n        prr.on_packet_sent(snd_cnt);\n\n        prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);\n\n        assert_eq!(prr.snd_cnt, 2000);\n    }\n\n    #[test]\n    fn on_packet_acked_prr_ssrb_overflow() {\n        let mut prr = PRR::default();\n        let max_datagram_size = 1000;\n        let bytes_in_flight = max_datagram_size * 10;\n        let ssthresh = bytes_in_flight / 2;\n        let acked = 500;\n\n        prr.congestion_event(bytes_in_flight);\n\n        // pipe <= ssthresh uses PRR-SSRB algorithm.\n        let pipe = max_datagram_size;\n\n        prr.on_packet_sent(max_datagram_size);\n\n        prr.on_packet_acked(acked, pipe, ssthresh, max_datagram_size);\n\n        assert_eq!(prr.snd_cnt, 1500);\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/congestion/recovery.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::cmp;\n\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse std::collections::VecDeque;\n\nuse super::RecoveryConfig;\nuse super::Sent;\n\nuse crate::packet::Epoch;\nuse crate::ranges::RangeSet;\nuse crate::recovery::Bandwidth;\nuse crate::recovery::HandshakeStatus;\nuse crate::recovery::OnLossDetectionTimeoutOutcome;\nuse crate::recovery::RecoveryOps;\nuse crate::recovery::StartupExit;\nuse crate::Error;\nuse crate::Result;\n\n#[cfg(feature = \"qlog\")]\nuse crate::recovery::QlogMetrics;\n\nuse crate::frame;\n\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventData;\n\nuse super::Congestion;\nuse crate::recovery::bytes_in_flight::BytesInFlight;\nuse crate::recovery::rtt::RttStats;\nuse crate::recovery::LossDetectionTimer;\nuse crate::recovery::OnAckReceivedOutcome;\nuse crate::recovery::ReleaseDecision;\nuse crate::recovery::ReleaseTime;\nuse crate::recovery::GRANULARITY;\nuse crate::recovery::INITIAL_PACKET_THRESHOLD;\nuse crate::recovery::INITIAL_TIME_THRESHOLD;\nuse crate::recovery::MAX_OUTSTANDING_NON_ACK_ELICITING;\nuse crate::recovery::MAX_PACKET_THRESHOLD;\nuse crate::recovery::MAX_PTO_PROBES_COUNT;\nuse crate::recovery::PACKET_REORDER_TIME_THRESHOLD;\n\n#[derive(Default)]\nstruct RecoveryEpoch {\n    /// The time the most recent ack-eliciting packet was sent.\n    time_of_last_ack_eliciting_packet: Option<Instant>,\n\n    /// The largest packet number acknowledged in the packet number space so\n    /// far.\n    largest_acked_packet: Option<u64>,\n\n    /// The time at which the next packet in that packet number space can be\n    /// considered lost based on exceeding the reordering window in time.\n    loss_time: Option<Instant>,\n\n    /// An association of packet numbers in a packet number space to information\n    /// about them.\n    sent_packets: VecDeque<Sent>,\n\n    loss_probes: usize,\n    in_flight_count: usize,\n\n    acked_frames: Vec<frame::Frame>,\n    lost_frames: Vec<frame::Frame>,\n\n    /// The largest packet number sent in the packet number space so far.\n    #[cfg(test)]\n    test_largest_sent_pkt_num_on_path: Option<u64>,\n}\n\nstruct AckedDetectionResult {\n    acked_bytes: usize,\n    spurious_losses: usize,\n    spurious_pkt_thresh: Option<u64>,\n    has_ack_eliciting: bool,\n    has_in_flight_spurious_loss: bool,\n}\n\nstruct LossDetectionResult {\n    largest_lost_pkt: Option<Sent>,\n    lost_packets: usize,\n    lost_bytes: usize,\n    pmtud_lost_bytes: usize,\n}\n\nimpl RecoveryEpoch {\n    // `peer_sent_ack_ranges` should not be used without validation.\n    fn detect_and_remove_acked_packets(\n        &mut self, now: Instant, peer_sent_ack_ranges: &RangeSet,\n        newly_acked: &mut Vec<Acked>, rtt_stats: &RttStats, skip_pn: Option<u64>,\n        trace_id: &str,\n    ) -> Result<AckedDetectionResult> {\n        newly_acked.clear();\n\n        let mut acked_bytes = 0;\n        let mut spurious_losses = 0;\n        let mut spurious_pkt_thresh = None;\n        let mut has_ack_eliciting = false;\n        let mut has_in_flight_spurious_loss = false;\n\n        let largest_ack_received = peer_sent_ack_ranges\n            .last()\n            .expect(\"ACK frames should always have at least one ack range\");\n        let largest_acked = self\n            .largest_acked_packet\n            .unwrap_or(0)\n            .max(largest_ack_received);\n\n        for peer_sent_range in peer_sent_ack_ranges.iter() {\n            if skip_pn.is_some_and(|skip_pn| peer_sent_range.contains(&skip_pn)) {\n                // https://www.rfc-editor.org/rfc/rfc9000#section-13.1\n                // An endpoint SHOULD treat receipt of an acknowledgment\n                // for a packet it did not send as\n                // a connection error of type PROTOCOL_VIOLATION\n                return Err(Error::OptimisticAckDetected);\n            }\n\n            // Because packets always have incrementing numbers, they are always\n            // in sorted order.\n            let start = if self\n                .sent_packets\n                .front()\n                .filter(|e| e.pkt_num >= peer_sent_range.start)\n                .is_some()\n            {\n                // Usually it will be the first packet.\n                0\n            } else {\n                self.sent_packets\n                    .binary_search_by_key(&peer_sent_range.start, |p| p.pkt_num)\n                    .unwrap_or_else(|e| e)\n            };\n\n            for unacked in self.sent_packets.range_mut(start..) {\n                if unacked.pkt_num >= peer_sent_range.end {\n                    break;\n                }\n\n                if unacked.time_acked.is_some() {\n                    // Already acked.\n                } else if unacked.time_lost.is_some() {\n                    // An acked packet was already declared lost.\n                    spurious_losses += 1;\n                    spurious_pkt_thresh\n                        .get_or_insert(largest_acked - unacked.pkt_num + 1);\n                    unacked.time_acked = Some(now);\n\n                    if unacked.in_flight {\n                        has_in_flight_spurious_loss = true;\n                    }\n                } else {\n                    if unacked.in_flight {\n                        self.in_flight_count -= 1;\n                        acked_bytes += unacked.size;\n                    }\n\n                    newly_acked.push(Acked {\n                        pkt_num: unacked.pkt_num,\n                        time_sent: unacked.time_sent,\n                        size: unacked.size,\n\n                        rtt: now.saturating_duration_since(unacked.time_sent),\n                        delivered: unacked.delivered,\n                        delivered_time: unacked.delivered_time,\n                        first_sent_time: unacked.first_sent_time,\n                        is_app_limited: unacked.is_app_limited,\n                    });\n\n                    trace!(\"{} packet newly acked {}\", trace_id, unacked.pkt_num);\n\n                    self.acked_frames\n                        .extend(std::mem::take(&mut unacked.frames));\n\n                    has_ack_eliciting |= unacked.ack_eliciting;\n                    unacked.time_acked = Some(now);\n                }\n            }\n        }\n\n        self.drain_acked_and_lost_packets(now - rtt_stats.rtt());\n\n        Ok(AckedDetectionResult {\n            acked_bytes,\n            spurious_losses,\n            spurious_pkt_thresh,\n            has_ack_eliciting,\n            has_in_flight_spurious_loss,\n        })\n    }\n\n    fn detect_lost_packets(\n        &mut self, loss_delay: Duration, pkt_thresh: u64, now: Instant,\n        trace_id: &str, epoch: Epoch,\n    ) -> LossDetectionResult {\n        self.loss_time = None;\n\n        // Minimum time of kGranularity before packets are deemed lost.\n        let loss_delay = cmp::max(loss_delay, GRANULARITY);\n        let largest_acked = self.largest_acked_packet.unwrap_or(0);\n\n        // Packets sent before this time are deemed lost.\n        let lost_send_time = now.checked_sub(loss_delay).unwrap();\n\n        let mut lost_packets = 0;\n        let mut lost_bytes = 0;\n        let mut pmtud_lost_bytes = 0;\n\n        let mut largest_lost_pkt = None;\n\n        let unacked_iter = self.sent_packets\n        .iter_mut()\n        // Skip packets that follow the largest acked packet.\n        .take_while(|p| p.pkt_num <= largest_acked)\n        // Skip packets that have already been acked or lost.\n        .filter(|p| p.time_acked.is_none() && p.time_lost.is_none());\n\n        for unacked in unacked_iter {\n            // Mark packet as lost, or set time when it should be marked.\n            if unacked.time_sent <= lost_send_time ||\n                largest_acked >= unacked.pkt_num + pkt_thresh\n            {\n                self.lost_frames.extend(unacked.frames.drain(..));\n\n                unacked.time_lost = Some(now);\n\n                if unacked.is_pmtud_probe {\n                    pmtud_lost_bytes += unacked.size;\n                    self.in_flight_count -= 1;\n\n                    // Do not track PMTUD probes losses.\n                    continue;\n                }\n\n                if unacked.in_flight {\n                    lost_bytes += unacked.size;\n\n                    // Frames have already been removed from the packet, so\n                    // cloning the whole packet should be relatively cheap.\n                    largest_lost_pkt = Some(unacked.clone());\n\n                    self.in_flight_count -= 1;\n\n                    trace!(\n                        \"{} packet {} lost on epoch {}\",\n                        trace_id,\n                        unacked.pkt_num,\n                        epoch\n                    );\n                }\n\n                lost_packets += 1;\n            } else {\n                let loss_time = match self.loss_time {\n                    None => unacked.time_sent + loss_delay,\n\n                    Some(loss_time) =>\n                        cmp::min(loss_time, unacked.time_sent + loss_delay),\n                };\n\n                self.loss_time = Some(loss_time);\n                break;\n            }\n        }\n\n        LossDetectionResult {\n            largest_lost_pkt,\n            lost_packets,\n            lost_bytes,\n            pmtud_lost_bytes,\n        }\n    }\n\n    fn drain_acked_and_lost_packets(&mut self, loss_thresh: Instant) {\n        // In order to avoid removing elements from the middle of the list\n        // (which would require copying other elements to compact the list),\n        // we only remove a contiguous range of elements from the start of the\n        // list.\n        //\n        // This means that acked or lost elements coming after this will not\n        // be removed at this point, but their removal is delayed for a later\n        // time, once the gaps have been filled.\n        while let Some(pkt) = self.sent_packets.front() {\n            if let Some(time_lost) = pkt.time_lost {\n                if time_lost > loss_thresh {\n                    break;\n                }\n            }\n\n            if pkt.time_acked.is_none() && pkt.time_lost.is_none() {\n                break;\n            }\n\n            self.sent_packets.pop_front();\n        }\n    }\n}\n\npub struct LegacyRecovery {\n    epochs: [RecoveryEpoch; Epoch::count()],\n\n    loss_timer: LossDetectionTimer,\n\n    pto_count: u32,\n\n    rtt_stats: RttStats,\n\n    lost_spurious_count: usize,\n\n    pkt_thresh: u64,\n\n    time_thresh: f64,\n\n    bytes_in_flight: BytesInFlight,\n\n    bytes_sent: usize,\n\n    bytes_lost: u64,\n\n    pub max_datagram_size: usize,\n\n    #[cfg(feature = \"qlog\")]\n    qlog_metrics: QlogMetrics,\n\n    #[cfg(feature = \"qlog\")]\n    qlog_prev_cc_state: &'static str,\n\n    /// How many non-ack-eliciting packets have been sent.\n    outstanding_non_ack_eliciting: usize,\n\n    pub congestion: Congestion,\n\n    /// A resusable list of acks.\n    newly_acked: Vec<Acked>,\n}\n\nimpl LegacyRecovery {\n    pub fn new_with_config(recovery_config: &RecoveryConfig) -> Self {\n        Self {\n            epochs: Default::default(),\n\n            loss_timer: Default::default(),\n\n            pto_count: 0,\n\n            rtt_stats: RttStats::new(\n                recovery_config.initial_rtt,\n                recovery_config.max_ack_delay,\n            ),\n\n            lost_spurious_count: 0,\n\n            pkt_thresh: INITIAL_PACKET_THRESHOLD,\n\n            time_thresh: INITIAL_TIME_THRESHOLD,\n\n            bytes_in_flight: Default::default(),\n\n            bytes_sent: 0,\n\n            bytes_lost: 0,\n\n            max_datagram_size: recovery_config.max_send_udp_payload_size,\n\n            #[cfg(feature = \"qlog\")]\n            qlog_metrics: QlogMetrics::default(),\n\n            #[cfg(feature = \"qlog\")]\n            qlog_prev_cc_state: \"\",\n\n            outstanding_non_ack_eliciting: 0,\n\n            congestion: Congestion::from_config(recovery_config),\n\n            newly_acked: Vec::new(),\n        }\n    }\n\n    #[cfg(test)]\n    pub fn new(config: &crate::Config) -> Self {\n        Self::new_with_config(&RecoveryConfig::from_config(config))\n    }\n\n    fn loss_time_and_space(&self) -> (Option<Instant>, Epoch) {\n        let mut epoch = Epoch::Initial;\n        let mut time = self.epochs[epoch].loss_time;\n\n        // Iterate over all packet number spaces starting from Handshake.\n        for e in [Epoch::Handshake, Epoch::Application] {\n            let new_time = self.epochs[e].loss_time;\n            if time.is_none() || new_time < time {\n                time = new_time;\n                epoch = e;\n            }\n        }\n\n        (time, epoch)\n    }\n\n    fn pto_time_and_space(\n        &self, handshake_status: HandshakeStatus, now: Instant,\n    ) -> (Option<Instant>, Epoch) {\n        let mut duration = self.pto() * 2_u32.pow(self.pto_count);\n\n        // Arm PTO from now when there are no inflight packets.\n        if self.bytes_in_flight.is_zero() {\n            if handshake_status.has_handshake_keys {\n                return (Some(now + duration), Epoch::Handshake);\n            } else {\n                return (Some(now + duration), Epoch::Initial);\n            }\n        }\n\n        let mut pto_timeout = None;\n        let mut pto_space = Epoch::Initial;\n\n        // Iterate over all packet number spaces.\n        for e in [Epoch::Initial, Epoch::Handshake, Epoch::Application] {\n            let epoch = &self.epochs[e];\n            if epoch.in_flight_count == 0 {\n                continue;\n            }\n\n            if e == Epoch::Application {\n                // Skip Application Data until handshake completes.\n                if !handshake_status.completed {\n                    return (pto_timeout, pto_space);\n                }\n\n                // Include max_ack_delay and backoff for Application Data.\n                duration +=\n                    self.rtt_stats.max_ack_delay * 2_u32.pow(self.pto_count);\n            }\n\n            let new_time = epoch\n                .time_of_last_ack_eliciting_packet\n                .map(|t| t + duration);\n\n            if pto_timeout.is_none() || new_time < pto_timeout {\n                pto_timeout = new_time;\n                pto_space = e;\n            }\n        }\n\n        (pto_timeout, pto_space)\n    }\n\n    fn set_loss_detection_timer(\n        &mut self, handshake_status: HandshakeStatus, now: Instant,\n    ) {\n        let (earliest_loss_time, _) = self.loss_time_and_space();\n\n        if let Some(to) = earliest_loss_time {\n            // Time threshold loss detection.\n            self.loss_timer.update(to);\n            return;\n        }\n\n        if self.bytes_in_flight.is_zero() &&\n            handshake_status.peer_verified_address\n        {\n            self.loss_timer.clear();\n            return;\n        }\n\n        // PTO timer.\n        if let (Some(timeout), _) = self.pto_time_and_space(handshake_status, now)\n        {\n            self.loss_timer.update(timeout);\n        }\n    }\n\n    fn detect_lost_packets(\n        &mut self, epoch: Epoch, now: Instant, trace_id: &str,\n    ) -> (usize, usize) {\n        let loss_delay = cmp::max(self.rtt_stats.latest_rtt, self.rtt())\n            .mul_f64(self.time_thresh);\n\n        let loss = self.epochs[epoch].detect_lost_packets(\n            loss_delay,\n            self.pkt_thresh,\n            now,\n            trace_id,\n            epoch,\n        );\n\n        if let Some(pkt) = loss.largest_lost_pkt {\n            if !self.congestion.in_congestion_recovery(pkt.time_sent) {\n                (self.congestion.cc_ops.checkpoint)(&mut self.congestion);\n            }\n\n            (self.congestion.cc_ops.congestion_event)(\n                &mut self.congestion,\n                self.bytes_in_flight.get(),\n                loss.lost_bytes,\n                &pkt,\n                now,\n            );\n\n            self.bytes_in_flight\n                .saturating_subtract(loss.lost_bytes, now);\n        };\n\n        self.bytes_in_flight\n            .saturating_subtract(loss.pmtud_lost_bytes, now);\n\n        self.epochs[epoch]\n            .drain_acked_and_lost_packets(now - self.rtt_stats.rtt());\n\n        self.congestion.lost_count += loss.lost_packets;\n\n        (loss.lost_packets, loss.lost_bytes)\n    }\n}\n\nimpl RecoveryOps for LegacyRecovery {\n    /// Returns whether or not we should elicit an ACK even if we wouldn't\n    /// otherwise have constructed an ACK eliciting packet.\n    fn should_elicit_ack(&self, epoch: Epoch) -> bool {\n        self.epochs[epoch].loss_probes > 0 ||\n            self.outstanding_non_ack_eliciting >=\n                MAX_OUTSTANDING_NON_ACK_ELICITING\n    }\n\n    fn next_acked_frame(&mut self, epoch: Epoch) -> Option<frame::Frame> {\n        self.epochs[epoch].acked_frames.pop()\n    }\n\n    fn next_lost_frame(&mut self, epoch: Epoch) -> Option<frame::Frame> {\n        self.epochs[epoch].lost_frames.pop()\n    }\n\n    fn get_largest_acked_on_epoch(&self, epoch: Epoch) -> Option<u64> {\n        self.epochs[epoch].largest_acked_packet\n    }\n\n    fn has_lost_frames(&self, epoch: Epoch) -> bool {\n        !self.epochs[epoch].lost_frames.is_empty()\n    }\n\n    fn loss_probes(&self, epoch: Epoch) -> usize {\n        self.epochs[epoch].loss_probes\n    }\n\n    #[cfg(test)]\n    fn inc_loss_probes(&mut self, epoch: Epoch) {\n        self.epochs[epoch].loss_probes += 1;\n    }\n\n    fn ping_sent(&mut self, epoch: Epoch) {\n        self.epochs[epoch].loss_probes =\n            self.epochs[epoch].loss_probes.saturating_sub(1);\n    }\n\n    fn on_packet_sent(\n        &mut self, mut pkt: Sent, epoch: Epoch,\n        handshake_status: HandshakeStatus, now: Instant, trace_id: &str,\n    ) {\n        let ack_eliciting = pkt.ack_eliciting;\n        let in_flight = pkt.in_flight;\n        let sent_bytes = pkt.size;\n\n        if ack_eliciting {\n            self.outstanding_non_ack_eliciting = 0;\n        } else {\n            self.outstanding_non_ack_eliciting += 1;\n        }\n\n        if in_flight && ack_eliciting {\n            self.epochs[epoch].time_of_last_ack_eliciting_packet = Some(now);\n        }\n\n        self.congestion.on_packet_sent(\n            self.bytes_in_flight.get(),\n            sent_bytes,\n            now,\n            &mut pkt,\n            self.bytes_lost,\n            in_flight,\n        );\n\n        if in_flight {\n            self.epochs[epoch].in_flight_count += 1;\n            self.bytes_in_flight.add(sent_bytes, now);\n\n            self.set_loss_detection_timer(handshake_status, now);\n        }\n\n        self.bytes_sent += sent_bytes;\n\n        #[cfg(test)]\n        {\n            self.epochs[epoch].test_largest_sent_pkt_num_on_path = self.epochs\n                [epoch]\n                .test_largest_sent_pkt_num_on_path\n                .max(Some(pkt.pkt_num));\n        }\n\n        self.epochs[epoch].sent_packets.push_back(pkt);\n\n        trace!(\"{trace_id} {self:?}\");\n    }\n\n    fn get_packet_send_time(&self, now: Instant) -> Instant {\n        now\n    }\n\n    // `peer_sent_ack_ranges` should not be used without validation.\n    fn on_ack_received(\n        &mut self, peer_sent_ack_ranges: &RangeSet, ack_delay: u64, epoch: Epoch,\n        handshake_status: HandshakeStatus, now: Instant, skip_pn: Option<u64>,\n        trace_id: &str,\n    ) -> Result<OnAckReceivedOutcome> {\n        let AckedDetectionResult {\n            acked_bytes,\n            spurious_losses,\n            spurious_pkt_thresh,\n            has_ack_eliciting,\n            has_in_flight_spurious_loss,\n        } = self.epochs[epoch].detect_and_remove_acked_packets(\n            now,\n            peer_sent_ack_ranges,\n            &mut self.newly_acked,\n            &self.rtt_stats,\n            skip_pn,\n            trace_id,\n        )?;\n\n        self.lost_spurious_count += spurious_losses;\n        if let Some(thresh) = spurious_pkt_thresh {\n            self.pkt_thresh =\n                self.pkt_thresh.max(thresh.min(MAX_PACKET_THRESHOLD));\n            self.time_thresh = PACKET_REORDER_TIME_THRESHOLD;\n        }\n\n        // Undo congestion window update.\n        if has_in_flight_spurious_loss {\n            (self.congestion.cc_ops.rollback)(&mut self.congestion);\n        }\n\n        if self.newly_acked.is_empty() {\n            return Ok(OnAckReceivedOutcome::default());\n        }\n\n        let largest_newly_acked = self.newly_acked.last().unwrap();\n\n        // Update `largest_acked_packet` based on the validated `newly_acked`\n        // value.\n        let largest_acked_pkt_num = self.epochs[epoch]\n            .largest_acked_packet\n            .unwrap_or(0)\n            .max(largest_newly_acked.pkt_num);\n        self.epochs[epoch].largest_acked_packet = Some(largest_acked_pkt_num);\n\n        // Check if largest packet is newly acked.\n        if largest_newly_acked.pkt_num == largest_acked_pkt_num &&\n            has_ack_eliciting\n        {\n            let latest_rtt = now - largest_newly_acked.time_sent;\n            self.rtt_stats.update_rtt(\n                latest_rtt,\n                Duration::from_micros(ack_delay),\n                now,\n                handshake_status.completed,\n            );\n        }\n\n        // Detect and mark lost packets without removing them from the sent\n        // packets list.\n        let (lost_packets, lost_bytes) =\n            self.detect_lost_packets(epoch, now, trace_id);\n\n        self.congestion.on_packets_acked(\n            self.bytes_in_flight.get(),\n            &mut self.newly_acked,\n            &self.rtt_stats,\n            now,\n        );\n\n        self.bytes_in_flight.saturating_subtract(acked_bytes, now);\n\n        self.pto_count = 0;\n\n        self.set_loss_detection_timer(handshake_status, now);\n\n        self.epochs[epoch]\n            .drain_acked_and_lost_packets(now - self.rtt_stats.rtt());\n\n        Ok(OnAckReceivedOutcome {\n            lost_packets,\n            lost_bytes,\n            acked_bytes,\n            spurious_losses,\n        })\n    }\n\n    fn on_loss_detection_timeout(\n        &mut self, handshake_status: HandshakeStatus, now: Instant,\n        trace_id: &str,\n    ) -> OnLossDetectionTimeoutOutcome {\n        let (earliest_loss_time, epoch) = self.loss_time_and_space();\n\n        if earliest_loss_time.is_some() {\n            // Time threshold loss detection.\n            let (lost_packets, lost_bytes) =\n                self.detect_lost_packets(epoch, now, trace_id);\n\n            self.set_loss_detection_timer(handshake_status, now);\n\n            trace!(\"{trace_id} {self:?}\");\n            return OnLossDetectionTimeoutOutcome {\n                lost_packets,\n                lost_bytes,\n            };\n        }\n\n        let epoch = if self.bytes_in_flight.get() > 0 {\n            // Send new data if available, else retransmit old data. If neither\n            // is available, send a single PING frame.\n            let (_, e) = self.pto_time_and_space(handshake_status, now);\n\n            e\n        } else {\n            // Client sends an anti-deadlock packet: Initial is padded to earn\n            // more anti-amplification credit, a Handshake packet proves address\n            // ownership.\n            if handshake_status.has_handshake_keys {\n                Epoch::Handshake\n            } else {\n                Epoch::Initial\n            }\n        };\n\n        self.pto_count += 1;\n\n        let epoch = &mut self.epochs[epoch];\n\n        epoch.loss_probes =\n            cmp::min(self.pto_count as usize, MAX_PTO_PROBES_COUNT);\n\n        let unacked_iter = epoch.sent_packets\n            .iter_mut()\n            // Skip packets that have already been acked or lost, and packets\n            // that don't contain either CRYPTO or STREAM frames.\n            .filter(|p| p.has_data && p.time_acked.is_none() && p.time_lost.is_none())\n            // Only return as many packets as the number of probe packets that\n            // will be sent.\n            .take(epoch.loss_probes);\n\n        // Retransmit the frames from the oldest sent packets on PTO. However\n        // the packets are not actually declared lost (so there is no effect to\n        // congestion control), we just reschedule the data they carried.\n        //\n        // This will also trigger sending an ACK and retransmitting frames like\n        // HANDSHAKE_DONE and MAX_DATA / MAX_STREAM_DATA as well, in addition\n        // to CRYPTO and STREAM, if the original packet carried them.\n        for unacked in unacked_iter {\n            epoch.lost_frames.extend_from_slice(&unacked.frames);\n        }\n\n        self.set_loss_detection_timer(handshake_status, now);\n\n        trace!(\"{trace_id} {self:?}\");\n\n        OnLossDetectionTimeoutOutcome {\n            lost_packets: 0,\n            lost_bytes: 0,\n        }\n    }\n\n    fn on_pkt_num_space_discarded(\n        &mut self, epoch: Epoch, handshake_status: HandshakeStatus, now: Instant,\n    ) {\n        let epoch = &mut self.epochs[epoch];\n\n        let unacked_bytes = epoch\n            .sent_packets\n            .iter()\n            .filter(|p| {\n                p.in_flight && p.time_acked.is_none() && p.time_lost.is_none()\n            })\n            .fold(0, |acc, p| acc + p.size);\n\n        self.bytes_in_flight.saturating_subtract(unacked_bytes, now);\n\n        epoch.sent_packets.clear();\n        epoch.lost_frames.clear();\n        epoch.acked_frames.clear();\n\n        epoch.time_of_last_ack_eliciting_packet = None;\n        epoch.loss_time = None;\n        epoch.loss_probes = 0;\n        epoch.in_flight_count = 0;\n\n        self.set_loss_detection_timer(handshake_status, now);\n    }\n\n    fn on_path_change(\n        &mut self, epoch: Epoch, now: Instant, trace_id: &str,\n    ) -> (usize, usize) {\n        // Time threshold loss detection.\n        self.detect_lost_packets(epoch, now, trace_id)\n    }\n\n    fn loss_detection_timer(&self) -> Option<Instant> {\n        self.loss_timer.time\n    }\n\n    fn cwnd(&self) -> usize {\n        self.congestion.congestion_window()\n    }\n\n    fn cwnd_available(&self) -> usize {\n        // Ignore cwnd when sending probe packets.\n        if self.epochs.iter().any(|e| e.loss_probes > 0) {\n            return usize::MAX;\n        }\n\n        // Open more space (snd_cnt) for PRR when allowed.\n        self.cwnd().saturating_sub(self.bytes_in_flight.get()) +\n            self.congestion.prr.snd_cnt\n    }\n\n    fn rtt(&self) -> Duration {\n        self.rtt_stats.rtt()\n    }\n\n    fn min_rtt(&self) -> Option<Duration> {\n        self.rtt_stats.min_rtt()\n    }\n\n    fn max_rtt(&self) -> Option<Duration> {\n        self.rtt_stats.max_rtt()\n    }\n\n    fn rttvar(&self) -> Duration {\n        self.rtt_stats.rttvar\n    }\n\n    fn pto(&self) -> Duration {\n        self.rtt() + cmp::max(self.rtt_stats.rttvar * 4, GRANULARITY)\n    }\n\n    /// The most recent data delivery rate estimate.\n    fn delivery_rate(&self) -> Bandwidth {\n        self.congestion.delivery_rate()\n    }\n\n    fn max_bandwidth(&self) -> Option<Bandwidth> {\n        // TODO implement\n        None\n    }\n\n    /// Statistics from when a CCA first exited the startup phase.\n    fn startup_exit(&self) -> Option<StartupExit> {\n        self.congestion.ssthresh.startup_exit()\n    }\n\n    fn max_datagram_size(&self) -> usize {\n        self.max_datagram_size\n    }\n\n    fn pmtud_update_max_datagram_size(&mut self, new_max_datagram_size: usize) {\n        // Congestion Window is updated only when it's not updated already.\n        // Update cwnd if it hasn't been updated yet.\n        if self.cwnd() ==\n            self.max_datagram_size *\n                self.congestion.initial_congestion_window_packets\n        {\n            self.congestion.congestion_window = new_max_datagram_size *\n                self.congestion.initial_congestion_window_packets;\n        }\n\n        self.max_datagram_size = new_max_datagram_size;\n    }\n\n    fn update_max_datagram_size(&mut self, new_max_datagram_size: usize) {\n        self.pmtud_update_max_datagram_size(\n            self.max_datagram_size.min(new_max_datagram_size),\n        )\n    }\n\n    #[cfg(test)]\n    fn sent_packets_len(&self, epoch: Epoch) -> usize {\n        self.epochs[epoch].sent_packets.len()\n    }\n\n    #[cfg(test)]\n    fn in_flight_count(&self, epoch: Epoch) -> usize {\n        self.epochs[epoch].in_flight_count\n    }\n\n    fn bytes_in_flight(&self) -> usize {\n        self.bytes_in_flight.get()\n    }\n\n    fn bytes_in_flight_duration(&self) -> Duration {\n        self.bytes_in_flight.get_duration()\n    }\n\n    #[cfg(test)]\n    fn pacing_rate(&self) -> u64 {\n        0\n    }\n\n    #[cfg(test)]\n    fn pto_count(&self) -> u32 {\n        self.pto_count\n    }\n\n    #[cfg(test)]\n    fn pkt_thresh(&self) -> Option<u64> {\n        Some(self.pkt_thresh)\n    }\n\n    #[cfg(test)]\n    fn time_thresh(&self) -> f64 {\n        self.time_thresh\n    }\n\n    #[cfg(test)]\n    fn lost_spurious_count(&self) -> usize {\n        self.lost_spurious_count\n    }\n\n    #[cfg(test)]\n    fn detect_lost_packets_for_test(\n        &mut self, epoch: Epoch, now: Instant,\n    ) -> (usize, usize) {\n        self.detect_lost_packets(epoch, now, \"\")\n    }\n\n    // FIXME only used by gcongestion\n    fn on_app_limited(&mut self) {\n        // Not implemented for legacy recovery, update_app_limited and\n        // delivery_rate_update_app_limited used instead.\n    }\n\n    #[cfg(test)]\n    fn largest_sent_pkt_num_on_path(&self, epoch: Epoch) -> Option<u64> {\n        self.epochs[epoch].test_largest_sent_pkt_num_on_path\n    }\n\n    #[cfg(test)]\n    fn app_limited(&self) -> bool {\n        self.congestion.app_limited\n    }\n\n    fn update_app_limited(&mut self, v: bool) {\n        self.congestion.update_app_limited(v);\n    }\n\n    // FIXME only used by congestion\n    fn delivery_rate_update_app_limited(&mut self, v: bool) {\n        self.congestion.delivery_rate.update_app_limited(v);\n    }\n\n    // FIXME only used by congestion\n    fn update_max_ack_delay(&mut self, max_ack_delay: Duration) {\n        self.rtt_stats.max_ack_delay = max_ack_delay;\n    }\n\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self, now: Instant) -> &'static str {\n        (self.congestion.cc_ops.state_str)(&self.congestion, now)\n    }\n\n    #[cfg(feature = \"qlog\")]\n    fn get_updated_qlog_event_data(&mut self) -> Option<EventData> {\n        let qlog_metrics = QlogMetrics {\n            min_rtt: *self.rtt_stats.min_rtt,\n            smoothed_rtt: self.rtt(),\n            latest_rtt: self.rtt_stats.latest_rtt,\n            rttvar: self.rtt_stats.rttvar,\n            cwnd: self.cwnd() as u64,\n            bytes_in_flight: self.bytes_in_flight.get() as u64,\n            ssthresh: Some(self.congestion.ssthresh.get() as u64),\n            lost_packets: Some(self.congestion.lost_count as u64),\n            lost_bytes: Some(self.bytes_lost),\n            pto_count: Some(self.pto_count),\n            ..Default::default()\n        };\n\n        self.qlog_metrics.maybe_update(qlog_metrics)\n    }\n\n    #[cfg(feature = \"qlog\")]\n    fn get_updated_qlog_cc_state(\n        &mut self, now: Instant,\n    ) -> Option<&'static str> {\n        let cc_state = self.state_str(now);\n        if cc_state != self.qlog_prev_cc_state {\n            self.qlog_prev_cc_state = cc_state;\n            Some(cc_state)\n        } else {\n            None\n        }\n    }\n\n    fn send_quantum(&self) -> usize {\n        self.congestion.send_quantum()\n    }\n\n    fn get_next_release_time(&self) -> ReleaseDecision {\n        ReleaseDecision {\n            time: ReleaseTime::Immediate,\n            allow_burst: false,\n        }\n    }\n\n    fn gcongestion_enabled(&self) -> bool {\n        false\n    }\n\n    fn lost_count(&self) -> usize {\n        self.congestion.lost_count\n    }\n\n    fn bytes_lost(&self) -> u64 {\n        self.bytes_lost\n    }\n}\n\nimpl std::fmt::Debug for LegacyRecovery {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"timer={:?} \", self.loss_timer)?;\n        write!(f, \"latest_rtt={:?} \", self.rtt_stats.latest_rtt)?;\n        write!(f, \"srtt={:?} \", self.rtt_stats.smoothed_rtt)?;\n        write!(f, \"min_rtt={:?} \", *self.rtt_stats.min_rtt)?;\n        write!(f, \"rttvar={:?} \", self.rtt_stats.rttvar)?;\n        write!(f, \"cwnd={} \", self.cwnd())?;\n        write!(f, \"ssthresh={} \", self.congestion.ssthresh.get())?;\n        write!(f, \"bytes_in_flight={} \", self.bytes_in_flight.get())?;\n        write!(f, \"app_limited={} \", self.congestion.app_limited)?;\n        write!(\n            f,\n            \"congestion_recovery_start_time={:?} \",\n            self.congestion.congestion_recovery_start_time\n        )?;\n        write!(f, \"{:?} \", self.congestion.delivery_rate)?;\n\n        if self.congestion.hystart.enabled() {\n            write!(f, \"hystart={:?} \", self.congestion.hystart)?;\n        }\n\n        // CC-specific debug info\n        (self.congestion.cc_ops.debug_fmt)(&self.congestion, f)?;\n\n        Ok(())\n    }\n}\n\n#[derive(Clone)]\npub struct Acked {\n    pub pkt_num: u64,\n\n    pub time_sent: Instant,\n\n    pub size: usize,\n\n    pub rtt: Duration,\n\n    pub delivered: usize,\n\n    pub delivered_time: Instant,\n\n    pub first_sent_time: Instant,\n\n    pub is_app_limited: bool,\n}\n"
  },
  {
    "path": "quiche/src/recovery/congestion/reno.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Reno Congestion Control\n//!\n//! Note that Slow Start can use HyStart++ when enabled.\n\nuse std::cmp;\nuse std::time::Instant;\n\nuse super::rtt::RttStats;\nuse super::Acked;\nuse super::Sent;\n\nuse super::Congestion;\nuse super::CongestionControlOps;\nuse crate::recovery::LOSS_REDUCTION_FACTOR;\nuse crate::recovery::MINIMUM_WINDOW_PACKETS;\n\npub(crate) static RENO: CongestionControlOps = CongestionControlOps {\n    on_init,\n    on_packet_sent,\n    on_packets_acked,\n    congestion_event,\n    checkpoint,\n    rollback,\n    #[cfg(feature = \"qlog\")]\n    state_str,\n    debug_fmt,\n};\n\npub fn on_init(_r: &mut Congestion) {}\n\npub fn on_packet_sent(\n    _r: &mut Congestion, _sent_bytes: usize, _bytes_in_flight: usize,\n    _now: Instant,\n) {\n}\n\nfn on_packets_acked(\n    r: &mut Congestion, _bytes_in_flight: usize, packets: &mut Vec<Acked>,\n    now: Instant, rtt_stats: &RttStats,\n) {\n    for pkt in packets.drain(..) {\n        on_packet_acked(r, &pkt, now, rtt_stats);\n    }\n}\n\nfn on_packet_acked(\n    r: &mut Congestion, packet: &Acked, now: Instant, rtt_stats: &RttStats,\n) {\n    if r.in_congestion_recovery(packet.time_sent) {\n        return;\n    }\n\n    if r.app_limited {\n        return;\n    }\n\n    if r.congestion_window < r.ssthresh.get() {\n        // In Slow slart, bytes_acked_sl is used for counting\n        // acknowledged bytes.\n        r.bytes_acked_sl += packet.size;\n\n        if r.hystart.in_css() {\n            r.congestion_window += r.hystart.css_cwnd_inc(r.max_datagram_size);\n        } else {\n            r.congestion_window += r.max_datagram_size;\n        }\n\n        if r.hystart.on_packet_acked(packet, rtt_stats.latest_rtt, now) {\n            // Exit to congestion avoidance if CSS ends.\n            r.ssthresh.update(r.congestion_window, true);\n        }\n    } else {\n        // Congestion avoidance.\n        r.bytes_acked_ca += packet.size;\n\n        if r.bytes_acked_ca >= r.congestion_window {\n            r.bytes_acked_ca -= r.congestion_window;\n            r.congestion_window += r.max_datagram_size;\n        }\n    }\n}\n\nfn congestion_event(\n    r: &mut Congestion, _bytes_in_flight: usize, _lost_bytes: usize,\n    largest_lost_pkt: &Sent, now: Instant,\n) {\n    // Start a new congestion event if packet was sent after the\n    // start of the previous congestion recovery period.\n    let time_sent = largest_lost_pkt.time_sent;\n\n    if !r.in_congestion_recovery(time_sent) {\n        r.congestion_recovery_start_time = Some(now);\n\n        r.congestion_window =\n            (r.congestion_window as f64 * LOSS_REDUCTION_FACTOR) as usize;\n\n        r.congestion_window = cmp::max(\n            r.congestion_window,\n            r.max_datagram_size * MINIMUM_WINDOW_PACKETS,\n        );\n\n        r.bytes_acked_ca =\n            (r.congestion_window as f64 * LOSS_REDUCTION_FACTOR) as usize;\n\n        r.ssthresh.update(r.congestion_window, r.hystart.in_css());\n\n        if r.hystart.in_css() {\n            r.hystart.congestion_event();\n        }\n    }\n}\n\nfn checkpoint(_r: &mut Congestion) {}\n\nfn rollback(_r: &mut Congestion) -> bool {\n    true\n}\n\n#[cfg(feature = \"qlog\")]\npub fn state_str(r: &Congestion, now: Instant) -> &'static str {\n    if r.hystart.in_css() {\n        \"conservative_slow_start\"\n    } else if r.congestion_window < r.ssthresh.get() {\n        \"slow_start\"\n    } else if r.in_congestion_recovery(now) {\n        \"recovery\"\n    } else {\n        \"congestion_avoidance\"\n    }\n}\n\nfn debug_fmt(_r: &Congestion, _f: &mut std::fmt::Formatter) -> std::fmt::Result {\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::CongestionControlAlgorithm;\n\n    use super::*;\n\n    use crate::recovery::congestion::recovery::LegacyRecovery;\n    use crate::recovery::congestion::test_sender::TestSender;\n    use crate::recovery::RecoveryOps;\n\n    use std::time::Duration;\n\n    fn test_sender() -> TestSender {\n        TestSender::new(CongestionControlAlgorithm::Reno, false)\n    }\n\n    #[test]\n    fn reno_init() {\n        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        cfg.set_cc_algorithm(CongestionControlAlgorithm::Reno);\n\n        let r = LegacyRecovery::new(&cfg);\n\n        assert!(r.cwnd() > 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n    }\n\n    #[test]\n    fn reno_slow_start() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        // Send initcwnd full MSS packets to become no longer app limited\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n\n        let cwnd_prev = sender.congestion_window;\n\n        sender.ack_n_packets(1, size);\n\n        // Check if cwnd increased by packet size (slow start).\n        assert_eq!(sender.congestion_window, cwnd_prev + size);\n    }\n\n    #[test]\n    fn reno_slow_start_multi_acks() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        // Send initcwnd full MSS packets to become no longer app limited\n        for _ in 0..sender.initial_congestion_window_packets {\n            sender.send_packet(size);\n        }\n\n        let cwnd_prev = sender.congestion_window;\n\n        sender.ack_n_packets(3, size);\n\n        // Acked 3 packets.\n        assert_eq!(sender.congestion_window, cwnd_prev + size * 3);\n    }\n\n    #[test]\n    fn reno_congestion_event() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        let prev_cwnd = sender.congestion_window;\n\n        sender.send_packet(size);\n        sender.lose_n_packets(1, size, None);\n\n        // In Reno, after congestion event, cwnd will be cut in half.\n        assert_eq!(prev_cwnd / 2, sender.congestion_window);\n    }\n\n    #[test]\n    fn reno_congestion_avoidance() {\n        let mut sender = test_sender();\n        let size = sender.max_datagram_size;\n\n        // Send initcwnd full MSS packets to become no longer app limited\n        for _ in 0..14 {\n            sender.send_packet(size);\n        }\n\n        let rtt = Duration::from_millis(100);\n        let prev_cwnd = sender.congestion_window;\n\n        sender.advance_time(rtt);\n        sender.lose_n_packets(1, size, None);\n\n        // After congestion event, cwnd will be reduced.\n        let cur_cwnd = (prev_cwnd as f64 * LOSS_REDUCTION_FACTOR) as usize;\n        assert_eq!(sender.congestion_window, cur_cwnd);\n\n        sender.update_rtt(rtt);\n        sender.advance_time(2 * rtt);\n\n        sender.ack_n_packets(13, size);\n        // Acked packets were sent before the loss event, window does not\n        // increase.\n        assert_eq!(sender.congestion_window, cur_cwnd);\n\n        for _ in 0..7 {\n            sender.send_packet(size);\n        }\n        sender.advance_time(rtt);\n        sender.ack_n_packets(2, size);\n        // Not enough ACKed, window does not increase.\n        assert_eq!(sender.congestion_window, cur_cwnd);\n\n        sender.ack_n_packets(1, size);\n        // Expect cwnd increased by MSS\n        assert_eq!(sender.congestion_window, cur_cwnd + size);\n\n        for _ in 0..7 {\n            sender.send_packet(size);\n        }\n\n        // Expect a second window increase after a cwnd's worth of ACKs.\n        sender.ack_n_packets(5, size);\n        // Not yet, one more ACK needed.\n        assert_eq!(sender.congestion_window, cur_cwnd + size);\n        sender.ack_n_packets(1, size);\n        // Expect cwnd increased by MSS\n        assert_eq!(sender.congestion_window, cur_cwnd + 2 * size);\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/congestion/test_sender.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::VecDeque;\nuse std::ops::Deref;\nuse std::ops::DerefMut;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse super::rtt::RttStats;\nuse super::Acked;\nuse super::Congestion;\nuse super::RecoveryConfig;\nuse super::Sent;\nuse crate::CongestionControlAlgorithm;\nuse crate::DEFAULT_INITIAL_RTT;\n\npub(crate) struct TestSender {\n    cc: Congestion,\n    pub(crate) next_pkt: u64,\n    pub(crate) next_ack: u64,\n    pub(crate) bytes_in_flight: usize,\n    pub(crate) time: Instant,\n    rtt_stats: RttStats,\n    sent_packets: VecDeque<Sent>,\n}\n\nimpl TestSender {\n    pub(crate) fn new(algo: CongestionControlAlgorithm, hystart: bool) -> Self {\n        let mut cfg = crate::Config::new(crate::PROTOCOL_VERSION).unwrap();\n        cfg.set_cc_algorithm(algo);\n        cfg.enable_hystart(hystart);\n\n        TestSender {\n            next_pkt: 0,\n            next_ack: 0,\n            bytes_in_flight: 0,\n            time: Instant::now(),\n            rtt_stats: RttStats::new(\n                DEFAULT_INITIAL_RTT,\n                Duration::from_micros(0),\n            ),\n            cc: Congestion::from_config(&RecoveryConfig::from_config(&cfg)),\n            sent_packets: VecDeque::new(),\n        }\n    }\n\n    pub(crate) fn send_packet(&mut self, bytes: usize) {\n        let mut sent = Sent {\n            pkt_num: self.next_pkt,\n            frames: Default::default(),\n            time_sent: self.time,\n            time_acked: None,\n            time_lost: None,\n            size: bytes,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: self.time,\n            first_sent_time: self.time,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        self.cc.on_packet_sent(\n            self.bytes_in_flight,\n            bytes,\n            self.time,\n            &mut sent,\n            0,\n            true,\n        );\n\n        self.sent_packets.push_back(sent);\n\n        self.bytes_in_flight += bytes;\n        self.next_pkt += 1;\n    }\n\n    pub(crate) fn inject_ack(&mut self, acked: Acked, now: Instant) {\n        let _ = self.sent_packets.pop_front().unwrap();\n\n        self.cc.on_packets_acked(\n            self.bytes_in_flight,\n            &mut vec![acked],\n            &self.rtt_stats,\n            now,\n        );\n    }\n\n    pub(crate) fn ack_n_packets(&mut self, n: usize, bytes: usize) {\n        let mut acked = Vec::new();\n\n        for _ in 0..n {\n            let unacked = self.sent_packets.pop_front().unwrap();\n\n            acked.push(Acked {\n                pkt_num: unacked.pkt_num,\n                time_sent: unacked.time_sent,\n                size: unacked.size,\n\n                rtt: self.time.saturating_duration_since(unacked.time_sent),\n                delivered: unacked.delivered,\n                delivered_time: unacked.delivered_time,\n                first_sent_time: unacked.first_sent_time,\n                is_app_limited: unacked.is_app_limited,\n            });\n\n            self.next_ack += 1;\n        }\n\n        self.cc.on_packets_acked(\n            self.bytes_in_flight,\n            &mut acked,\n            &self.rtt_stats,\n            self.time,\n        );\n\n        self.bytes_in_flight -= n * bytes;\n    }\n\n    pub(crate) fn lose_n_packets(\n        &mut self, n: usize, bytes: usize, time_sent: Option<Instant>,\n    ) {\n        let mut unacked = None;\n\n        for _ in 0..n {\n            self.next_ack += 1;\n            unacked = self.sent_packets.pop_front();\n        }\n\n        let mut unacked = unacked.unwrap();\n        if let Some(time) = time_sent {\n            unacked.time_sent = time;\n        }\n\n        if !self.cc.in_congestion_recovery(unacked.time_sent) {\n            (self.cc.cc_ops.checkpoint)(&mut self.cc);\n        }\n\n        (self.cc_ops.congestion_event)(\n            &mut self.cc,\n            self.bytes_in_flight,\n            n * bytes,\n            &unacked,\n            self.time,\n        );\n\n        self.cc.lost_count += n;\n        self.bytes_in_flight -= n * bytes;\n    }\n\n    pub(crate) fn update_rtt(&mut self, rtt: Duration) {\n        self.rtt_stats\n            .update_rtt(rtt, Duration::ZERO, self.time, true)\n    }\n\n    pub(crate) fn advance_time(&mut self, period: Duration) {\n        self.time += period;\n    }\n}\n\nimpl Deref for TestSender {\n    type Target = Congestion;\n\n    fn deref(&self) -> &Self::Target {\n        &self.cc\n    }\n}\n\nimpl DerefMut for TestSender {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.cc\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr/bandwidth_sampler.rs",
    "content": "// Copyright (c) 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::VecDeque;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse super::Acked;\nuse crate::recovery::gcongestion::Bandwidth;\nuse crate::recovery::gcongestion::Lost;\n\nuse super::windowed_filter::WindowedFilter;\n\n#[derive(Debug)]\nstruct ConnectionStateMap<T> {\n    packet_map: VecDeque<(u64, Option<T>)>,\n}\n\nimpl<T> Default for ConnectionStateMap<T> {\n    fn default() -> Self {\n        ConnectionStateMap {\n            packet_map: VecDeque::new(),\n        }\n    }\n}\n\nimpl<T> ConnectionStateMap<T> {\n    fn insert(&mut self, pkt_num: u64, val: T) {\n        if let Some((last_pkt, _)) = self.packet_map.back() {\n            assert!(pkt_num > *last_pkt, \"{} > {}\", pkt_num, *last_pkt);\n        }\n\n        self.packet_map.push_back((pkt_num, Some(val)));\n    }\n\n    fn take(&mut self, pkt_num: u64) -> Option<T> {\n        // First we check if the next packet is the one we are looking for\n        let first = self.packet_map.front()?;\n        if first.0 == pkt_num {\n            return self.packet_map.pop_front().and_then(|(_, v)| v);\n        }\n        // Use binary search\n        let ret =\n            match self.packet_map.binary_search_by_key(&pkt_num, |&(n, _)| n) {\n                Ok(found) =>\n                    self.packet_map.get_mut(found).and_then(|(_, v)| v.take()),\n                Err(_) => None,\n            };\n\n        while let Some((_, None)) = self.packet_map.front() {\n            self.packet_map.pop_front();\n        }\n\n        ret\n    }\n\n    #[cfg(test)]\n    fn peek(&self, pkt_num: u64) -> Option<&T> {\n        // Use binary search\n        match self.packet_map.binary_search_by_key(&pkt_num, |&(n, _)| n) {\n            Ok(found) => self.packet_map.get(found).and_then(|(_, v)| v.as_ref()),\n            Err(_) => None,\n        }\n    }\n\n    fn remove_obsolete(&mut self, least_acked: u64) {\n        while match self.packet_map.front() {\n            Some(&(p, _)) if p < least_acked => {\n                self.packet_map.pop_front();\n                true\n            },\n            _ => false,\n        } {}\n    }\n}\n\n#[derive(Debug)]\npub struct BandwidthSampler {\n    /// The total number of congestion controlled bytes sent during the\n    /// connection.\n    total_bytes_sent: usize,\n    total_bytes_acked: usize,\n    total_bytes_lost: usize,\n    total_bytes_neutered: usize,\n    last_sent_packet: u64,\n    last_acked_packet: u64,\n    is_app_limited: bool,\n    last_acked_packet_ack_time: Instant,\n    total_bytes_sent_at_last_acked_packet: usize,\n    last_acked_packet_sent_time: Instant,\n    recent_ack_points: RecentAckPoints,\n    a0_candidates: VecDeque<AckPoint>,\n    connection_state_map: ConnectionStateMap<ConnectionStateOnSentPacket>,\n    max_ack_height_tracker: MaxAckHeightTracker,\n    /// The packet that will be acknowledged after this one will cause the\n    /// sampler to exit the app-limited phase.\n    end_of_app_limited_phase: Option<u64>,\n    overestimate_avoidance: bool,\n    // If true, apply the fix to A0 point selection logic so the\n    // implementation is consistent with the behavior of the\n    // google/quiche implementation.\n    choose_a0_point_fix: bool,\n    limit_max_ack_height_tracker_by_send_rate: bool,\n\n    total_bytes_acked_after_last_ack_event: usize,\n}\n\n/// A subset of [`ConnectionStateOnSentPacket`] which is returned\n/// to the caller when the packet is acked or lost.\n#[derive(Debug, Default, Clone, Copy)]\npub struct SendTimeState {\n    /// Whether other states in this object is valid.\n    pub is_valid: bool,\n    /// Whether the sender is app limited at the time the packet was sent.\n    /// App limited bandwidth sample might be artificially low because the\n    /// sender did not have enough data to send in order to saturate the\n    /// link.\n    pub is_app_limited: bool,\n    /// Total number of sent bytes at the time the packet was sent.\n    /// Includes the packet itself.\n    pub total_bytes_sent: usize,\n    /// Total number of acked bytes at the time the packet was sent.\n    pub total_bytes_acked: usize,\n    /// Total number of lost bytes at the time the packet was sent.\n    #[allow(dead_code)]\n    pub total_bytes_lost: usize,\n    /// Total number of inflight bytes at the time the packet was sent.\n    /// Includes the packet itself.\n    /// It should be equal to `total_bytes_sent` minus the sum of\n    /// `total_bytes_acked`, `total_bytes_lost` and total neutered bytes.\n    pub bytes_in_flight: usize,\n}\n\n#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]\nstruct ExtraAckedEvent {\n    /// The excess bytes acknowlwedged in the time delta for this event.\n    extra_acked: usize,\n    /// The bytes acknowledged and time delta from the event.\n    bytes_acked: usize,\n    time_delta: Duration,\n    /// The round trip of the event.\n    round: usize,\n}\n\n// BandwidthSample holds per-packet rate measurements\n// This is the internal struct used by BandwidthSampler to track rates\nstruct BandwidthSample {\n    /// The bandwidth at that particular sample.\n    bandwidth: Bandwidth,\n    /// The RTT measurement at this particular sample.  Does not correct for\n    /// delayed ack time.\n    rtt: Duration,\n    /// `send_rate` is computed from the current packet being acked('P') and\n    /// an earlier packet that is acked before P was sent.\n    /// <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-04.html#name-send-rate>\n    send_rate: Option<Bandwidth>,\n    // ack_rate tracks the acknowledgment rate for this sample\n    /// `ack_rate` is computed as bytes_acked_delta / time_delta between ack\n    /// points. <https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-04.html#name-ack-rate>\n    ack_rate: Bandwidth,\n    /// States captured when the packet was sent.\n    state_at_send: SendTimeState,\n}\n\n/// [`AckPoint`] represents a point on the ack line.\n#[derive(Debug, Clone, Copy)]\nstruct AckPoint {\n    ack_time: Instant,\n    total_bytes_acked: usize,\n}\n\n/// [`RecentAckPoints`] maintains the most recent 2 ack points at distinct\n/// times.\n#[derive(Debug, Default)]\nstruct RecentAckPoints {\n    ack_points: [Option<AckPoint>; 2],\n}\n\n// [`ConnectionStateOnSentPacket`] represents the information about a sent\n// packet and the state of the connection at the moment the packet was sent,\n// specifically the information about the most recently acknowledged packet at\n// that moment.\n#[derive(Debug)]\nstruct ConnectionStateOnSentPacket {\n    /// Time at which the packet is sent.\n    sent_time: Instant,\n    /// Size of the packet.\n    size: usize,\n    /// The value of [`BandwidthSampler::total_bytes_sent_at_last_acked_packet`]\n    /// at the time the packet was sent.\n    total_bytes_sent_at_last_acked_packet: usize,\n    /// The value of [`BandwidthSampler::last_acked_packet_sent_time`] at the\n    /// time the packet was sent.\n    last_acked_packet_sent_time: Instant,\n    /// The value of [`BandwidthSampler::last_acked_packet_ack_time`] at the\n    /// time the packet was sent.\n    last_acked_packet_ack_time: Instant,\n    /// Send time states that are returned to the congestion controller when the\n    /// packet is acked or lost.\n    send_time_state: SendTimeState,\n}\n\n/// [`MaxAckHeightTracker`] is part of the [`BandwidthSampler`]. It is called\n/// after every ack event to keep track the degree of ack\n/// aggregation(a.k.a \"ack height\").\n#[derive(Debug)]\nstruct MaxAckHeightTracker {\n    /// Tracks the maximum number of bytes acked faster than the estimated\n    /// bandwidth.\n    max_ack_height_filter: WindowedFilter<ExtraAckedEvent, usize, usize>,\n    /// The time this aggregation started and the number of bytes acked during\n    /// it.\n    aggregation_epoch_start_time: Option<Instant>,\n    aggregation_epoch_bytes: usize,\n    /// The last sent packet number before the current aggregation epoch\n    /// started.\n    last_sent_packet_number_before_epoch: u64,\n    /// The number of ack aggregation epochs ever started, including the ongoing\n    /// one. Stats only.\n    num_ack_aggregation_epochs: u64,\n    ack_aggregation_bandwidth_threshold: f64,\n    start_new_aggregation_epoch_after_full_round: bool,\n    reduce_extra_acked_on_bandwidth_increase: bool,\n}\n\n/// Measurements collected from a congestion event, used for bandwidth\n/// estimation and congestion control in BBR.\n#[derive(Default)]\npub(crate) struct CongestionEventSample {\n    /// The maximum bandwidth sample from all acked packets.\n    pub sample_max_bandwidth: Option<Bandwidth>,\n    /// Whether [`Self::sample_max_bandwidth`] is from a app-limited sample.\n    pub sample_is_app_limited: bool,\n    /// The minimum rtt sample from all acked packets.\n    pub sample_rtt: Option<Duration>,\n    /// For each packet p in acked packets, this is the max value of\n    /// INFLIGHT(p), where INFLIGHT(p) is the number of bytes acked while p\n    /// is inflight.\n    pub sample_max_inflight: usize,\n    /// The send state of the largest packet in acked_packets, unless it is\n    /// empty. If acked_packets is empty, it's the send state of the largest\n    /// packet in lost_packets.\n    pub last_packet_send_state: SendTimeState,\n    /// The number of extra bytes acked from this ack event, compared to what is\n    /// expected from the flow's bandwidth. Larger value means more ack\n    /// aggregation.\n    pub extra_acked: usize,\n\n    /// The maximum send rate observed across all acked packets in this event.\n    /// Computed as bytes_sent_delta / time_delta between packet send times.\n    pub sample_max_send_rate: Option<Bandwidth>,\n    /// The maximum ack rate observed across all acked packets in this event.\n    /// Computed as bytes_acked_delta / time_delta between ack times.\n    pub sample_max_ack_rate: Option<Bandwidth>,\n}\n\nimpl MaxAckHeightTracker {\n    pub(crate) fn new(window: usize, overestimate_avoidance: bool) -> Self {\n        MaxAckHeightTracker {\n            max_ack_height_filter: WindowedFilter::new(window),\n            aggregation_epoch_start_time: None,\n            aggregation_epoch_bytes: 0,\n            last_sent_packet_number_before_epoch: 0,\n            num_ack_aggregation_epochs: 0,\n            ack_aggregation_bandwidth_threshold: if overestimate_avoidance {\n                2.0\n            } else {\n                1.0\n            },\n            start_new_aggregation_epoch_after_full_round: true,\n            reduce_extra_acked_on_bandwidth_increase: true,\n        }\n    }\n\n    #[allow(dead_code)]\n    fn reset(&mut self, new_height: usize, new_time: usize) {\n        self.max_ack_height_filter.reset(\n            ExtraAckedEvent {\n                extra_acked: new_height,\n                bytes_acked: 0,\n                time_delta: Duration::ZERO,\n                round: new_time,\n            },\n            new_time,\n        );\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    fn update(\n        &mut self, bandwidth_estimate: Bandwidth, is_new_max_bandwidth: bool,\n        round_trip_count: usize, last_sent_packet_number: u64,\n        last_acked_packet_number: u64, ack_time: Instant, bytes_acked: usize,\n    ) -> usize {\n        let mut force_new_epoch = false;\n\n        if self.reduce_extra_acked_on_bandwidth_increase && is_new_max_bandwidth {\n            // Save and clear existing entries.\n            let mut best =\n                self.max_ack_height_filter.get_best().unwrap_or_default();\n            let mut second_best = self\n                .max_ack_height_filter\n                .get_second_best()\n                .unwrap_or_default();\n            let mut third_best = self\n                .max_ack_height_filter\n                .get_third_best()\n                .unwrap_or_default();\n            self.max_ack_height_filter.clear();\n\n            // Reinsert the heights into the filter after recalculating.\n            let expected_bytes_acked =\n                bandwidth_estimate.to_bytes_per_period(best.time_delta) as usize;\n            if expected_bytes_acked < best.bytes_acked {\n                best.extra_acked = best.bytes_acked - expected_bytes_acked;\n                self.max_ack_height_filter.update(best, best.round);\n            }\n\n            let expected_bytes_acked = bandwidth_estimate\n                .to_bytes_per_period(second_best.time_delta)\n                as usize;\n            if expected_bytes_acked < second_best.bytes_acked {\n                second_best.extra_acked =\n                    second_best.bytes_acked - expected_bytes_acked;\n                self.max_ack_height_filter\n                    .update(second_best, second_best.round);\n            }\n\n            let expected_bytes_acked = bandwidth_estimate\n                .to_bytes_per_period(third_best.time_delta)\n                as usize;\n            if expected_bytes_acked < third_best.bytes_acked {\n                third_best.extra_acked =\n                    third_best.bytes_acked - expected_bytes_acked;\n                self.max_ack_height_filter\n                    .update(third_best, third_best.round);\n            }\n        }\n\n        // If any packet sent after the start of the epoch has been acked, start a\n        // new epoch.\n        if self.start_new_aggregation_epoch_after_full_round &&\n            last_acked_packet_number >\n                self.last_sent_packet_number_before_epoch\n        {\n            force_new_epoch = true;\n        }\n\n        let epoch_start_time = match self.aggregation_epoch_start_time {\n            Some(time) if !force_new_epoch => time,\n            _ => {\n                self.aggregation_epoch_bytes = bytes_acked;\n                self.aggregation_epoch_start_time = Some(ack_time);\n                self.last_sent_packet_number_before_epoch =\n                    last_sent_packet_number;\n                self.num_ack_aggregation_epochs += 1;\n                return 0;\n            },\n        };\n\n        // Compute how many bytes are expected to be delivered, assuming max\n        // bandwidth is correct.\n        let aggregation_delta = ack_time.duration_since(epoch_start_time);\n        let expected_bytes_acked =\n            bandwidth_estimate.to_bytes_per_period(aggregation_delta) as usize;\n        // Reset the current aggregation epoch as soon as the ack arrival rate is\n        // less than or equal to the max bandwidth.\n        if self.aggregation_epoch_bytes <=\n            (self.ack_aggregation_bandwidth_threshold *\n                expected_bytes_acked as f64) as usize\n        {\n            // Reset to start measuring a new aggregation epoch.\n            self.aggregation_epoch_bytes = bytes_acked;\n            self.aggregation_epoch_start_time = Some(ack_time);\n            self.last_sent_packet_number_before_epoch = last_sent_packet_number;\n            self.num_ack_aggregation_epochs += 1;\n            return 0;\n        }\n\n        self.aggregation_epoch_bytes += bytes_acked;\n\n        // Compute how many extra bytes were delivered vs max bandwidth.\n        let extra_bytes_acked =\n            self.aggregation_epoch_bytes - expected_bytes_acked;\n\n        let new_event = ExtraAckedEvent {\n            extra_acked: extra_bytes_acked,\n            bytes_acked: self.aggregation_epoch_bytes,\n            time_delta: aggregation_delta,\n            round: 0,\n        };\n\n        self.max_ack_height_filter\n            .update(new_event, round_trip_count);\n        extra_bytes_acked\n    }\n}\n\nimpl From<(Instant, usize, usize, &BandwidthSampler)>\n    for ConnectionStateOnSentPacket\n{\n    fn from(\n        (sent_time, size, bytes_in_flight, sampler): (\n            Instant,\n            usize,\n            usize,\n            &BandwidthSampler,\n        ),\n    ) -> Self {\n        ConnectionStateOnSentPacket {\n            sent_time,\n            size,\n            total_bytes_sent_at_last_acked_packet: sampler\n                .total_bytes_sent_at_last_acked_packet,\n            last_acked_packet_sent_time: sampler.last_acked_packet_sent_time,\n            last_acked_packet_ack_time: sampler.last_acked_packet_ack_time,\n            send_time_state: SendTimeState {\n                is_valid: true,\n                is_app_limited: sampler.is_app_limited,\n                total_bytes_sent: sampler.total_bytes_sent,\n                total_bytes_acked: sampler.total_bytes_acked,\n                total_bytes_lost: sampler.total_bytes_lost,\n                bytes_in_flight,\n            },\n        }\n    }\n}\n\nimpl RecentAckPoints {\n    fn update(&mut self, ack_time: Instant, total_bytes_acked: usize) {\n        assert!(\n            total_bytes_acked >=\n                self.ack_points[1].map(|p| p.total_bytes_acked).unwrap_or(0)\n        );\n\n        self.ack_points[0] = self.ack_points[1];\n        self.ack_points[1] = Some(AckPoint {\n            ack_time,\n            total_bytes_acked,\n        });\n    }\n\n    fn clear(&mut self) {\n        self.ack_points = Default::default();\n    }\n\n    fn most_recent(&self) -> Option<AckPoint> {\n        self.ack_points[1]\n    }\n\n    fn less_recent_point(&self, choose_a0_point_fix: bool) -> Option<AckPoint> {\n        if choose_a0_point_fix {\n            self.ack_points[0]\n                .filter(|ack_point| ack_point.total_bytes_acked > 0)\n                .or(self.ack_points[1])\n        } else {\n            self.ack_points[0].or(self.ack_points[1])\n        }\n    }\n}\n\nimpl BandwidthSampler {\n    pub(crate) fn new(\n        max_height_tracker_window_length: usize, overestimate_avoidance: bool,\n        choose_a0_point_fix: bool,\n    ) -> Self {\n        BandwidthSampler {\n            total_bytes_sent: 0,\n            total_bytes_acked: 0,\n            total_bytes_lost: 0,\n            total_bytes_neutered: 0,\n            total_bytes_sent_at_last_acked_packet: 0,\n            last_acked_packet_sent_time: Instant::now(),\n            last_acked_packet_ack_time: Instant::now(),\n            is_app_limited: true,\n            connection_state_map: ConnectionStateMap::default(),\n            max_ack_height_tracker: MaxAckHeightTracker::new(\n                max_height_tracker_window_length,\n                overestimate_avoidance,\n            ),\n            total_bytes_acked_after_last_ack_event: 0,\n            overestimate_avoidance,\n            choose_a0_point_fix,\n            limit_max_ack_height_tracker_by_send_rate: false,\n\n            last_sent_packet: 0,\n            last_acked_packet: 0,\n            recent_ack_points: RecentAckPoints::default(),\n            a0_candidates: VecDeque::new(),\n            end_of_app_limited_phase: None,\n        }\n    }\n\n    #[allow(dead_code)]\n    pub(crate) fn is_app_limited(&self) -> bool {\n        self.is_app_limited\n    }\n\n    pub(crate) fn on_packet_sent(\n        &mut self, sent_time: Instant, packet_number: u64, bytes: usize,\n        bytes_in_flight: usize, has_retransmittable_data: bool,\n    ) {\n        self.last_sent_packet = packet_number;\n\n        if !has_retransmittable_data {\n            return;\n        }\n\n        self.total_bytes_sent += bytes;\n\n        // If there are no packets in flight, the time at which the new\n        // transmission opens can be treated as the A_0 point for the\n        // purpose of bandwidth sampling. This underestimates bandwidth to\n        // some extent, and produces some artificially low samples for\n        // most packets in flight, but it provides with samples at\n        // important points where we would not have them otherwise, most\n        // importantly at the beginning of the connection.\n        if bytes_in_flight == 0 {\n            self.last_acked_packet_ack_time = sent_time;\n            if self.overestimate_avoidance {\n                self.recent_ack_points.clear();\n                self.recent_ack_points\n                    .update(sent_time, self.total_bytes_acked);\n                self.a0_candidates.clear();\n                self.a0_candidates\n                    .push_back(self.recent_ack_points.most_recent().unwrap());\n            }\n\n            self.total_bytes_sent_at_last_acked_packet = self.total_bytes_sent;\n\n            // In this situation ack compression is not a concern, set send rate\n            // to effectively infinite.\n            self.last_acked_packet_sent_time = sent_time;\n        }\n\n        self.connection_state_map.insert(\n            packet_number,\n            (sent_time, bytes, bytes_in_flight + bytes, &*self).into(),\n        );\n    }\n\n    pub(crate) fn on_packet_neutered(&mut self, packet_number: u64) {\n        if let Some(pkt) = self.connection_state_map.take(packet_number) {\n            self.total_bytes_neutered += pkt.size;\n        }\n    }\n\n    pub(crate) fn on_congestion_event(\n        &mut self, ack_time: Instant, acked_packets: &[Acked],\n        lost_packets: &[Lost], mut max_bandwidth: Option<Bandwidth>,\n        est_bandwidth_upper_bound: Bandwidth, round_trip_count: usize,\n    ) -> CongestionEventSample {\n        let mut last_lost_packet_send_state = SendTimeState::default();\n        let mut last_acked_packet_send_state = SendTimeState::default();\n        let mut last_lost_packet_num = 0u64;\n        let mut last_acked_packet_num = 0u64;\n\n        for packet in lost_packets {\n            let send_state =\n                self.on_packet_lost(packet.packet_number, packet.bytes_lost);\n            if send_state.is_valid {\n                last_lost_packet_send_state = send_state;\n                last_lost_packet_num = packet.packet_number;\n            }\n        }\n\n        if acked_packets.is_empty() {\n            // Only populate send state for a loss-only event.\n            return CongestionEventSample {\n                last_packet_send_state: last_lost_packet_send_state,\n                ..Default::default()\n            };\n        }\n\n        let mut event_sample = CongestionEventSample::default();\n\n        let mut max_send_rate = None;\n        let mut max_ack_rate = None;\n        for packet in acked_packets {\n            let sample =\n                match self.on_packet_acknowledged(ack_time, packet.pkt_num) {\n                    Some(sample) if sample.state_at_send.is_valid => sample,\n                    _ => continue,\n                };\n\n            last_acked_packet_send_state = sample.state_at_send;\n            last_acked_packet_num = packet.pkt_num;\n\n            event_sample.sample_rtt = Some(\n                sample\n                    .rtt\n                    .min(*event_sample.sample_rtt.get_or_insert(sample.rtt)),\n            );\n\n            if Some(sample.bandwidth) > event_sample.sample_max_bandwidth {\n                event_sample.sample_max_bandwidth = Some(sample.bandwidth);\n                event_sample.sample_is_app_limited =\n                    sample.state_at_send.is_app_limited;\n            }\n            max_send_rate = max_send_rate.max(sample.send_rate);\n            max_ack_rate = max_ack_rate.max(Some(sample.ack_rate));\n\n            let inflight_sample = self.total_bytes_acked -\n                last_acked_packet_send_state.total_bytes_acked;\n            if inflight_sample > event_sample.sample_max_inflight {\n                event_sample.sample_max_inflight = inflight_sample;\n            }\n        }\n\n        if !last_lost_packet_send_state.is_valid {\n            event_sample.last_packet_send_state = last_acked_packet_send_state;\n        } else if !last_acked_packet_send_state.is_valid {\n            event_sample.last_packet_send_state = last_lost_packet_send_state;\n        } else {\n            // If two packets are inflight and an alarm is armed to lose a packet\n            // and it wakes up late, then the first of two in flight packets could\n            // have been acknowledged before the wakeup, which re-evaluates loss\n            // detection, and could declare the later of the two lost.\n            event_sample.last_packet_send_state =\n                if last_acked_packet_num > last_lost_packet_num {\n                    last_acked_packet_send_state\n                } else {\n                    last_lost_packet_send_state\n                };\n        }\n\n        let is_new_max_bandwidth =\n            event_sample.sample_max_bandwidth > max_bandwidth;\n        max_bandwidth = event_sample.sample_max_bandwidth.max(max_bandwidth);\n\n        if self.limit_max_ack_height_tracker_by_send_rate {\n            max_bandwidth = max_bandwidth.max(max_send_rate);\n        }\n\n        let bandwidth_estimate = if let Some(max_bandwidth) = max_bandwidth {\n            max_bandwidth.min(est_bandwidth_upper_bound)\n        } else {\n            est_bandwidth_upper_bound\n        };\n\n        event_sample.extra_acked = self.on_ack_event_end(\n            bandwidth_estimate,\n            is_new_max_bandwidth,\n            round_trip_count,\n        );\n\n        event_sample.sample_max_send_rate = max_send_rate;\n        event_sample.sample_max_ack_rate = max_ack_rate;\n\n        event_sample\n    }\n\n    fn on_packet_lost(\n        &mut self, packet_number: u64, bytes_lost: usize,\n    ) -> SendTimeState {\n        let mut send_time_state = SendTimeState::default();\n\n        self.total_bytes_lost += bytes_lost;\n        if let Some(state) = self.connection_state_map.take(packet_number) {\n            send_time_state = state.send_time_state;\n            send_time_state.is_valid = true;\n        }\n\n        send_time_state\n    }\n\n    fn on_ack_event_end(\n        &mut self, bandwidth_estimate: Bandwidth, is_new_max_bandwidth: bool,\n        round_trip_count: usize,\n    ) -> usize {\n        let newly_acked_bytes =\n            self.total_bytes_acked - self.total_bytes_acked_after_last_ack_event;\n\n        if newly_acked_bytes == 0 {\n            return 0;\n        }\n\n        self.total_bytes_acked_after_last_ack_event = self.total_bytes_acked;\n        let extra_acked = self.max_ack_height_tracker.update(\n            bandwidth_estimate,\n            is_new_max_bandwidth,\n            round_trip_count,\n            self.last_sent_packet,\n            self.last_acked_packet,\n            self.last_acked_packet_ack_time,\n            newly_acked_bytes,\n        );\n        // If `extra_acked` is zero, i.e. this ack event marks the start of a new\n        // ack aggregation epoch, save `less_recent_point`, which is the\n        // last ack point of the previous epoch, as a A0 candidate.\n        if self.overestimate_avoidance && extra_acked == 0 {\n            self.a0_candidates.push_back(\n                self.recent_ack_points\n                    .less_recent_point(self.choose_a0_point_fix)\n                    .unwrap(),\n            );\n        }\n\n        extra_acked\n    }\n\n    fn on_packet_acknowledged(\n        &mut self, ack_time: Instant, packet_number: u64,\n    ) -> Option<BandwidthSample> {\n        self.last_acked_packet = packet_number;\n        let sent_packet = self.connection_state_map.take(packet_number)?;\n\n        self.total_bytes_acked += sent_packet.size;\n        self.total_bytes_sent_at_last_acked_packet =\n            sent_packet.send_time_state.total_bytes_sent;\n        self.last_acked_packet_sent_time = sent_packet.sent_time;\n        self.last_acked_packet_ack_time = ack_time;\n        if self.overestimate_avoidance {\n            self.recent_ack_points\n                .update(ack_time, self.total_bytes_acked);\n        }\n\n        if self.is_app_limited {\n            // Exit app-limited phase in two cases:\n            // (1) end_of_app_limited_phase is not initialized, i.e., so far all\n            // packets are sent while there are buffered packets or pending data.\n            // (2) The current acked packet is after the sent packet marked as the\n            // end of the app limit phase.\n            if self.end_of_app_limited_phase.is_none() ||\n                Some(packet_number) > self.end_of_app_limited_phase\n            {\n                self.is_app_limited = false;\n            }\n        }\n\n        // No send rate indicates that the sampler is supposed to discard the\n        // current send rate sample and use only the ack rate.\n        let send_rate = if sent_packet.sent_time >\n            sent_packet.last_acked_packet_sent_time\n        {\n            Some(Bandwidth::from_bytes_and_time_delta(\n                sent_packet.send_time_state.total_bytes_sent -\n                    sent_packet.total_bytes_sent_at_last_acked_packet,\n                sent_packet.sent_time - sent_packet.last_acked_packet_sent_time,\n            ))\n        } else {\n            None\n        };\n\n        let a0 = if self.overestimate_avoidance {\n            Self::choose_a0_point(\n                &mut self.a0_candidates,\n                sent_packet.send_time_state.total_bytes_acked,\n                self.choose_a0_point_fix,\n            )\n        } else {\n            None\n        };\n\n        let a0 = a0.unwrap_or(AckPoint {\n            ack_time: sent_packet.last_acked_packet_ack_time,\n            total_bytes_acked: sent_packet.send_time_state.total_bytes_acked,\n        });\n\n        // During the slope calculation, ensure that ack time of the current\n        // packet is always larger than the time of the previous packet,\n        // otherwise division by zero or integer underflow can occur.\n        if ack_time <= a0.ack_time {\n            return None;\n        }\n\n        let ack_rate = Bandwidth::from_bytes_and_time_delta(\n            self.total_bytes_acked - a0.total_bytes_acked,\n            ack_time.duration_since(a0.ack_time),\n        );\n\n        let bandwidth = if let Some(send_rate) = send_rate {\n            send_rate.min(ack_rate)\n        } else {\n            ack_rate\n        };\n\n        // Note: this sample does not account for delayed acknowledgement time.\n        // This means that the RTT measurements here can be artificially\n        // high, especially on low bandwidth connections.\n        let rtt = ack_time.duration_since(sent_packet.sent_time);\n\n        Some(BandwidthSample {\n            bandwidth,\n            rtt,\n            send_rate,\n            ack_rate,\n            state_at_send: SendTimeState {\n                is_valid: true,\n                ..sent_packet.send_time_state\n            },\n        })\n    }\n\n    fn choose_a0_point(\n        a0_candidates: &mut VecDeque<AckPoint>, total_bytes_acked: usize,\n        choose_a0_point_fix: bool,\n    ) -> Option<AckPoint> {\n        if a0_candidates.is_empty() {\n            return None;\n        }\n\n        while let Some(candidate) = a0_candidates.get(1) {\n            if candidate.total_bytes_acked > total_bytes_acked {\n                if choose_a0_point_fix {\n                    break;\n                } else {\n                    return Some(*candidate);\n                }\n            }\n            a0_candidates.pop_front();\n        }\n\n        Some(a0_candidates[0])\n    }\n\n    pub(crate) fn total_bytes_acked(&self) -> usize {\n        self.total_bytes_acked\n    }\n\n    pub(crate) fn total_bytes_lost(&self) -> usize {\n        self.total_bytes_lost\n    }\n\n    #[allow(dead_code)]\n    pub(crate) fn reset_max_ack_height_tracker(\n        &mut self, new_height: usize, new_time: usize,\n    ) {\n        self.max_ack_height_tracker.reset(new_height, new_time);\n    }\n\n    pub(crate) fn max_ack_height(&self) -> Option<usize> {\n        self.max_ack_height_tracker\n            .max_ack_height_filter\n            .get_best()\n            .map(|b| b.extra_acked)\n    }\n\n    pub(crate) fn on_app_limited(&mut self) {\n        self.is_app_limited = true;\n        self.end_of_app_limited_phase = Some(self.last_sent_packet);\n    }\n\n    pub(crate) fn remove_obsolete_packets(&mut self, least_acked: u64) {\n        // A packet can become obsolete when it is removed from\n        // QuicUnackedPacketMap's view of inflight before it is acked or\n        // marked as lost. For example, when\n        // QuicSentPacketManager::RetransmitCryptoPackets retransmits a crypto\n        // packet, the packet is removed from QuicUnackedPacketMap's\n        // inflight, but is not marked as acked or lost in the\n        // BandwidthSampler.\n        self.connection_state_map.remove_obsolete(least_acked);\n    }\n}\n\n#[cfg(test)]\nmod bandwidth_sampler_tests {\n    use rstest::rstest;\n\n    use super::*;\n\n    const REGULAR_PACKET_SIZE: usize = 1280;\n\n    struct TestSender {\n        sampler: BandwidthSampler,\n        sampler_app_limited_at_start: bool,\n        bytes_in_flight: usize,\n        clock: Instant,\n        max_bandwidth: Bandwidth,\n        est_bandwidth_upper_bound: Bandwidth,\n        round_trip_count: usize,\n    }\n\n    impl TestSender {\n        fn new(overestimate_avoidance: bool, choose_a0_point_fix: bool) -> Self {\n            let sampler = BandwidthSampler::new(\n                0,\n                overestimate_avoidance,\n                choose_a0_point_fix,\n            );\n            TestSender {\n                sampler_app_limited_at_start: sampler.is_app_limited(),\n                sampler,\n                bytes_in_flight: 0,\n                clock: Instant::now(),\n                max_bandwidth: Bandwidth::zero(),\n                est_bandwidth_upper_bound: Bandwidth::infinite(),\n                round_trip_count: 0,\n            }\n        }\n\n        fn get_packet_size(&self, pkt_num: u64) -> usize {\n            self.sampler\n                .connection_state_map\n                .peek(pkt_num)\n                .unwrap()\n                .size\n        }\n\n        fn get_packet_time(&self, pkt_num: u64) -> Instant {\n            self.sampler\n                .connection_state_map\n                .peek(pkt_num)\n                .unwrap()\n                .sent_time\n        }\n\n        fn number_of_tracked_packets(&self) -> usize {\n            self.sampler.connection_state_map.packet_map.len()\n        }\n\n        fn make_acked_packet(&self, pkt_num: u64) -> Acked {\n            let time_sent = self.get_packet_time(pkt_num);\n\n            Acked { pkt_num, time_sent }\n        }\n\n        fn make_lost_packet(&self, pkt_num: u64) -> Lost {\n            let size = self.get_packet_size(pkt_num);\n            Lost {\n                packet_number: pkt_num,\n                bytes_lost: size,\n            }\n        }\n\n        fn ack_packet(&mut self, pkt_num: u64) -> BandwidthSample {\n            let size = self.get_packet_size(pkt_num);\n            self.bytes_in_flight -= size;\n\n            let sample = self.sampler.on_congestion_event(\n                self.clock,\n                &[self.make_acked_packet(pkt_num)],\n                &[],\n                Some(self.max_bandwidth),\n                self.est_bandwidth_upper_bound,\n                self.round_trip_count,\n            );\n\n            let sample_max_bandwidth = sample.sample_max_bandwidth.unwrap();\n            self.max_bandwidth = self.max_bandwidth.max(sample_max_bandwidth);\n\n            let bandwidth_sample = BandwidthSample {\n                bandwidth: sample_max_bandwidth,\n                rtt: sample.sample_rtt.unwrap(),\n                send_rate: None,\n                // Use zero for ack_rate in test helper\n                ack_rate: Bandwidth::zero(),\n                state_at_send: sample.last_packet_send_state,\n            };\n            assert!(bandwidth_sample.state_at_send.is_valid);\n            bandwidth_sample\n        }\n\n        fn lose_packet(&mut self, pkt_num: u64) -> SendTimeState {\n            let size = self.get_packet_size(pkt_num);\n            self.bytes_in_flight -= size;\n\n            let sample = self.sampler.on_congestion_event(\n                self.clock,\n                &[],\n                &[self.make_lost_packet(pkt_num)],\n                Some(self.max_bandwidth),\n                self.est_bandwidth_upper_bound,\n                self.round_trip_count,\n            );\n\n            assert!(sample.last_packet_send_state.is_valid);\n            assert_eq!(sample.sample_max_bandwidth, None);\n            assert_eq!(sample.sample_rtt, None);\n            sample.last_packet_send_state\n        }\n\n        fn on_congestion_event(\n            &mut self, acked: &[u64], lost: &[u64],\n        ) -> CongestionEventSample {\n            let acked = acked\n                .iter()\n                .map(|pkt| {\n                    let acked_size = self.get_packet_size(*pkt);\n                    self.bytes_in_flight -= acked_size;\n\n                    self.make_acked_packet(*pkt)\n                })\n                .collect::<Vec<_>>();\n\n            let lost = lost\n                .iter()\n                .map(|pkt| {\n                    let lost = self.make_lost_packet(*pkt);\n                    self.bytes_in_flight -= lost.bytes_lost;\n                    lost\n                })\n                .collect::<Vec<_>>();\n\n            let sample = self.sampler.on_congestion_event(\n                self.clock,\n                &acked,\n                &lost,\n                Some(self.max_bandwidth),\n                self.est_bandwidth_upper_bound,\n                self.round_trip_count,\n            );\n\n            self.max_bandwidth =\n                self.max_bandwidth.max(sample.sample_max_bandwidth.unwrap());\n\n            sample\n        }\n\n        fn send_packet(\n            &mut self, pkt_num: u64, pkt_sz: usize,\n            has_retransmittable_data: bool,\n        ) {\n            self.sampler.on_packet_sent(\n                self.clock,\n                pkt_num,\n                pkt_sz,\n                self.bytes_in_flight,\n                has_retransmittable_data,\n            );\n            if has_retransmittable_data {\n                self.bytes_in_flight += pkt_sz;\n            }\n        }\n\n        fn advance_time(&mut self, delta: Duration) {\n            self.clock += delta;\n        }\n\n        // Sends one packet and acks it.  Then, send 20 packets.  Finally, send\n        // another 20 packets while acknowledging previous 20.\n        fn send_40_and_ack_first_20(&mut self, time_between_packets: Duration) {\n            // Send 20 packets at a constant inter-packet time.\n            for i in 1..=20 {\n                self.send_packet(i, REGULAR_PACKET_SIZE, true);\n                self.advance_time(time_between_packets);\n            }\n\n            // Ack packets 1 to 20, while sending new packets at the same rate as\n            // before.\n            for i in 1..=20 {\n                self.ack_packet(i);\n                self.send_packet(i + 20, REGULAR_PACKET_SIZE, true);\n                self.advance_time(time_between_packets);\n            }\n        }\n    }\n\n    #[rstest]\n    fn send_and_wait(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let mut time_between_packets = Duration::from_millis(10);\n        let mut expected_bandwidth =\n            Bandwidth::from_bytes_per_second(REGULAR_PACKET_SIZE as u64 * 100);\n\n        // Send packets at the constant bandwidth.\n        for i in 1..20 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n            let current_sample = test_sender.ack_packet(i);\n            assert_eq!(expected_bandwidth, current_sample.bandwidth);\n        }\n\n        // Send packets at the exponentially decreasing bandwidth.\n        for i in 20..25 {\n            time_between_packets *= 2;\n            expected_bandwidth = expected_bandwidth * 0.5;\n\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n            let current_sample = test_sender.ack_packet(i);\n            assert_eq!(expected_bandwidth, current_sample.bandwidth);\n        }\n\n        test_sender.sampler.remove_obsolete_packets(25);\n        assert_eq!(0, test_sender.number_of_tracked_packets());\n        assert_eq!(0, test_sender.bytes_in_flight);\n    }\n\n    #[rstest]\n    fn send_time_state(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(10);\n\n        // Send packets 1-5.\n        for i in 1..=5 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            assert_eq!(\n                test_sender.sampler.total_bytes_sent,\n                REGULAR_PACKET_SIZE * i as usize\n            );\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Ack packet 1.\n        let send_time_state = test_sender.ack_packet(1).state_at_send;\n        assert_eq!(REGULAR_PACKET_SIZE, send_time_state.total_bytes_sent);\n        assert_eq!(0, send_time_state.total_bytes_acked);\n        assert_eq!(0, send_time_state.total_bytes_lost);\n        assert_eq!(REGULAR_PACKET_SIZE, test_sender.sampler.total_bytes_acked);\n\n        // Lose packet 2.\n        let send_time_state = test_sender.lose_packet(2);\n        assert_eq!(REGULAR_PACKET_SIZE * 2, send_time_state.total_bytes_sent);\n        assert_eq!(0, send_time_state.total_bytes_acked);\n        assert_eq!(0, send_time_state.total_bytes_lost);\n        assert_eq!(REGULAR_PACKET_SIZE, test_sender.sampler.total_bytes_lost);\n\n        // Lose packet 3.\n        let send_time_state = test_sender.lose_packet(3);\n        assert_eq!(REGULAR_PACKET_SIZE * 3, send_time_state.total_bytes_sent);\n        assert_eq!(0, send_time_state.total_bytes_acked);\n        assert_eq!(0, send_time_state.total_bytes_lost);\n        assert_eq!(\n            REGULAR_PACKET_SIZE * 2,\n            test_sender.sampler.total_bytes_lost\n        );\n\n        // Send packets 6-10.\n        for i in 6..=10 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            assert_eq!(\n                test_sender.sampler.total_bytes_sent,\n                REGULAR_PACKET_SIZE * i as usize\n            );\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Ack all inflight packets.\n        let mut acked_packet_count = 1;\n        assert_eq!(\n            REGULAR_PACKET_SIZE * acked_packet_count,\n            test_sender.sampler.total_bytes_acked\n        );\n        for i in 4..=10 {\n            let send_time_state = test_sender.ack_packet(i).state_at_send;\n            acked_packet_count += 1;\n            assert_eq!(\n                REGULAR_PACKET_SIZE * acked_packet_count,\n                test_sender.sampler.total_bytes_acked\n            );\n            assert_eq!(\n                REGULAR_PACKET_SIZE * i as usize,\n                send_time_state.total_bytes_sent\n            );\n\n            if i <= 5 {\n                assert_eq!(0, send_time_state.total_bytes_acked);\n                assert_eq!(0, send_time_state.total_bytes_lost);\n            } else {\n                assert_eq!(\n                    REGULAR_PACKET_SIZE,\n                    send_time_state.total_bytes_acked\n                );\n                assert_eq!(\n                    REGULAR_PACKET_SIZE * 2,\n                    send_time_state.total_bytes_lost\n                );\n            }\n\n            // This equation works because there is no neutered bytes.\n            assert_eq!(\n                send_time_state.total_bytes_sent -\n                    send_time_state.total_bytes_acked -\n                    send_time_state.total_bytes_lost,\n                send_time_state.bytes_in_flight\n            );\n\n            test_sender.advance_time(time_between_packets);\n        }\n    }\n\n    /// Test the sampler during regular windowed sender scenario with fixed CWND\n    /// of 20.\n    #[rstest]\n    fn send_paced(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(1);\n        let expected_bandwidth =\n            Bandwidth::from_kbits_per_second(REGULAR_PACKET_SIZE as u64 * 8);\n\n        test_sender.send_40_and_ack_first_20(time_between_packets);\n\n        // Ack the packets 21 to 40, arriving at the correct bandwidth.\n        for i in 21..=40 {\n            let last_bandwidth = test_sender.ack_packet(i).bandwidth;\n            assert_eq!(expected_bandwidth, last_bandwidth);\n            test_sender.advance_time(time_between_packets);\n        }\n        test_sender.sampler.remove_obsolete_packets(41);\n        assert_eq!(0, test_sender.number_of_tracked_packets());\n        assert_eq!(0, test_sender.bytes_in_flight);\n    }\n\n    /// Test the sampler in a scenario where 50% of packets is consistently\n    /// lost.\n    #[rstest]\n    fn send_with_losses(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(1);\n        let expected_bandwidth =\n            Bandwidth::from_kbits_per_second(REGULAR_PACKET_SIZE as u64 / 2 * 8);\n\n        // Send 20 packets, each 1 ms apart.\n        for i in 1..=20 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Ack packets 1 to 20, losing every even-numbered packet, while sending\n        // new packets at the same rate as before.\n        for i in 1..=20 {\n            if i % 2 == 0 {\n                test_sender.ack_packet(i);\n            } else {\n                test_sender.lose_packet(i);\n            }\n            test_sender.send_packet(i + 20, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Ack the packets 21 to 40 with the same loss pattern.\n        for i in 21..=40 {\n            if i % 2 == 0 {\n                let last_bandwidth = test_sender.ack_packet(i).bandwidth;\n                assert_eq!(expected_bandwidth, last_bandwidth);\n            } else {\n                test_sender.lose_packet(i);\n            }\n            test_sender.advance_time(time_between_packets);\n        }\n        test_sender.sampler.remove_obsolete_packets(41);\n        assert_eq!(0, test_sender.number_of_tracked_packets());\n        assert_eq!(0, test_sender.bytes_in_flight);\n    }\n\n    /// Test the sampler in a scenario where the 50% of packets are not\n    /// congestion controlled (specifically, non-retransmittable data is not\n    /// congestion controlled).  Should be functionally consistent in behavior\n    /// with the [`send_with_losses`] test.\n    #[rstest]\n    fn not_congestion_controlled(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(1);\n        let expected_bandwidth =\n            Bandwidth::from_kbits_per_second(REGULAR_PACKET_SIZE as u64 / 2 * 8);\n\n        // Send 20 packets, each 1 ms apart. Every even packet is not congestion\n        // controlled.\n        for i in 1..=20 {\n            let has_retransmittable_data = i % 2 == 0;\n            test_sender.send_packet(\n                i,\n                REGULAR_PACKET_SIZE,\n                has_retransmittable_data,\n            );\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Ensure only congestion controlled packets are tracked.\n        assert_eq!(10, test_sender.number_of_tracked_packets());\n\n        // Ack packets 2 to 21, ignoring every even-numbered packet, while sending\n        // new packets at the same rate as before.\n        for i in 1..=20 {\n            if i % 2 == 0 {\n                test_sender.ack_packet(i);\n            }\n            let has_retransmittable_data = i % 2 == 0;\n            test_sender.send_packet(\n                i + 20,\n                REGULAR_PACKET_SIZE,\n                has_retransmittable_data,\n            );\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Ack the packets 22 to 41 with the same congestion controlled pattern.\n        for i in 21..=40 {\n            if i % 2 == 0 {\n                let last_bandwidth = test_sender.ack_packet(i).bandwidth;\n                assert_eq!(expected_bandwidth, last_bandwidth);\n            }\n            test_sender.advance_time(time_between_packets);\n        }\n\n        test_sender.sampler.remove_obsolete_packets(41);\n        // Since only congestion controlled packets are entered into the map, it\n        // has to be empty at this point.\n        assert_eq!(0, test_sender.number_of_tracked_packets());\n        assert_eq!(0, test_sender.bytes_in_flight);\n    }\n\n    /// Simulate a situation where ACKs arrive in burst and earlier than usual,\n    /// thus producing an ACK rate which is higher than the original send rate.\n    #[rstest]\n    fn compressed_ack(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(1);\n        let expected_bandwidth =\n            Bandwidth::from_kbits_per_second(REGULAR_PACKET_SIZE as u64 * 8);\n\n        test_sender.send_40_and_ack_first_20(time_between_packets);\n\n        // Simulate an RTT somewhat lower than the one for 1-to-21 transmission.\n        test_sender.advance_time(time_between_packets * 15);\n\n        // Ack the packets 21 to 40 almost immediately at once.\n        let ridiculously_small_time_delta = Duration::from_micros(20);\n        let mut last_bandwidth = Bandwidth::zero();\n        for i in 21..=40 {\n            last_bandwidth = test_sender.ack_packet(i).bandwidth;\n            test_sender.advance_time(ridiculously_small_time_delta);\n        }\n        assert_eq!(expected_bandwidth, last_bandwidth);\n\n        test_sender.sampler.remove_obsolete_packets(41);\n        // Since only congestion controlled packets are entered into the map, it\n        // has to be empty at this point.\n        assert_eq!(0, test_sender.number_of_tracked_packets());\n        assert_eq!(0, test_sender.bytes_in_flight);\n    }\n\n    /// Tests receiving ACK packets in the reverse order.\n    #[rstest]\n    fn reordered_ack(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(1);\n        let expected_bandwidth =\n            Bandwidth::from_kbits_per_second(REGULAR_PACKET_SIZE as u64 * 8);\n\n        test_sender.send_40_and_ack_first_20(time_between_packets);\n\n        // Ack the packets 21 to 40 in the reverse order, while sending packets 41\n        // to 60.\n        for i in 0..20 {\n            let last_bandwidth = test_sender.ack_packet(40 - i).bandwidth;\n            assert_eq!(expected_bandwidth, last_bandwidth);\n            test_sender.send_packet(41 + i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Ack the packets 41 to 60, now in the regular order.\n        for i in 41..=60 {\n            let last_bandwidth = test_sender.ack_packet(i).bandwidth;\n            assert_eq!(expected_bandwidth, last_bandwidth);\n            test_sender.advance_time(time_between_packets);\n        }\n\n        test_sender.sampler.remove_obsolete_packets(61);\n        assert_eq!(0, test_sender.number_of_tracked_packets());\n        assert_eq!(0, test_sender.bytes_in_flight);\n    }\n\n    /// Test the app-limited logic.\n    #[rstest]\n    fn app_limited(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(1);\n        let expected_bandwidth =\n            Bandwidth::from_kbits_per_second(REGULAR_PACKET_SIZE as u64 * 8);\n\n        for i in 1..=20 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n        }\n\n        for i in 1..=20 {\n            let sample = test_sender.ack_packet(i);\n            assert_eq!(\n                sample.state_at_send.is_app_limited,\n                test_sender.sampler_app_limited_at_start,\n                \"{i}\"\n            );\n            test_sender.send_packet(i + 20, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // We are now app-limited. Ack 21 to 40 as usual, but do not send anything\n        // for now.\n        test_sender.sampler.on_app_limited();\n        for i in 21..=40 {\n            let sample = test_sender.ack_packet(i);\n            assert!(!sample.state_at_send.is_app_limited, \"{i}\");\n            assert_eq!(expected_bandwidth, sample.bandwidth, \"{i}\");\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Enter quiescence.\n        test_sender.advance_time(Duration::from_secs(1));\n\n        // Send packets 41 to 60, all of which would be marked as app-limited.\n        for i in 41..=60 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Ack packets 41 to 60, while sending packets 61 to 80.  41 to 60 should\n        // be app-limited and underestimate the bandwidth due to that.\n        for i in 41..=60 {\n            let sample = test_sender.ack_packet(i);\n            assert!(sample.state_at_send.is_app_limited, \"{i}\");\n            if !overestimate_avoidance || choose_a0_point_fix || i < 43 {\n                assert!(\n                    sample.bandwidth < expected_bandwidth * 0.7,\n                    \"{} {:?} vs {:?}\",\n                    i,\n                    sample.bandwidth,\n                    expected_bandwidth * 0.7\n                );\n            } else {\n                // Needs further investigation: when using overestimate_avoidance,\n                // sample.bandwidth increases 17 packet earlier than expected.\n                assert_eq!(sample.bandwidth, expected_bandwidth, \"{i}\");\n            }\n            test_sender.send_packet(i + 20, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // Run out of packets, and then ack packet 61 to 80, all of which should\n        // have correct non-app-limited samples.\n        for i in 61..=80 {\n            let sample = test_sender.ack_packet(i);\n            assert!(!sample.state_at_send.is_app_limited, \"{i}\");\n            assert_eq!(sample.bandwidth, expected_bandwidth, \"{i}\");\n            test_sender.advance_time(time_between_packets);\n        }\n\n        test_sender.sampler.remove_obsolete_packets(81);\n        assert_eq!(0, test_sender.number_of_tracked_packets());\n        assert_eq!(0, test_sender.bytes_in_flight);\n    }\n\n    /// Test the samples taken at the first flight of packets sent.\n    #[rstest]\n    fn first_round_trip(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(1);\n        let rtt = Duration::from_millis(800);\n        let num_packets = 10;\n        let num_bytes = REGULAR_PACKET_SIZE * num_packets;\n        let real_bandwidth = Bandwidth::from_bytes_and_time_delta(num_bytes, rtt);\n\n        for i in 1..=10 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n        }\n        test_sender.advance_time(rtt - time_between_packets * num_packets as _);\n\n        let mut last_sample = Bandwidth::zero();\n        for i in 1..=10 {\n            let sample = test_sender.ack_packet(i).bandwidth;\n            assert!(sample > last_sample);\n            last_sample = sample;\n            test_sender.advance_time(time_between_packets);\n        }\n\n        // The final measured sample for the first flight of sample is expected to\n        // be smaller than the real bandwidth, yet it should not lose more\n        // than 10%. The specific value of the error depends on the\n        // difference between the RTT and the time it takes to exhaust the\n        // congestion window (i.e. in the limit when all packets are sent\n        // simultaneously, last sample would indicate the real bandwidth).\n        assert!(last_sample < real_bandwidth);\n        assert!(last_sample > real_bandwidth * 0.9);\n    }\n\n    /// Test sampler's ability to remove obsolete packets.\n    #[rstest]\n    fn remove_obsolete_packets(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n\n        for i in 1..=5 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n        }\n        test_sender.advance_time(Duration::from_millis(100));\n        assert_eq!(5, test_sender.number_of_tracked_packets());\n        test_sender.sampler.remove_obsolete_packets(4);\n        assert_eq!(2, test_sender.number_of_tracked_packets());\n        test_sender.lose_packet(4);\n        test_sender.sampler.remove_obsolete_packets(5);\n        assert_eq!(1, test_sender.number_of_tracked_packets());\n        test_sender.ack_packet(5);\n        test_sender.sampler.remove_obsolete_packets(6);\n        assert_eq!(0, test_sender.number_of_tracked_packets());\n    }\n\n    #[rstest]\n    fn neuter_packet(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        test_sender.send_packet(1, REGULAR_PACKET_SIZE, true);\n        assert_eq!(test_sender.sampler.total_bytes_neutered, 0);\n        test_sender.advance_time(Duration::from_millis(10));\n        test_sender.sampler.on_packet_neutered(1);\n        assert!(0 < test_sender.sampler.total_bytes_neutered);\n        assert_eq!(0, test_sender.sampler.total_bytes_acked);\n\n        // If packet 1 is acked it should not produce a bandwidth sample.\n        let acked = Acked {\n            pkt_num: 1,\n            time_sent: test_sender.clock,\n        };\n        test_sender.advance_time(Duration::from_millis(10));\n        let sample = test_sender.sampler.on_congestion_event(\n            test_sender.clock,\n            &[acked],\n            &[],\n            Some(test_sender.max_bandwidth),\n            test_sender.est_bandwidth_upper_bound,\n            test_sender.round_trip_count,\n        );\n\n        assert_eq!(0, test_sender.sampler.total_bytes_acked);\n        assert!(sample.sample_max_bandwidth.is_none());\n        assert!(!sample.sample_is_app_limited);\n        assert!(sample.sample_rtt.is_none());\n        assert_eq!(sample.sample_max_inflight, 0);\n        assert_eq!(sample.extra_acked, 0);\n    }\n\n    /// Make sure a default constructed [`CongestionEventSample`] has the\n    /// correct initial values for\n    /// [`BandwidthSampler::on_congestion_event()`] to work.\n    #[rstest]\n    fn congestion_event_sample_default_values() {\n        let sample = CongestionEventSample::default();\n        assert!(sample.sample_max_bandwidth.is_none());\n        assert!(!sample.sample_is_app_limited);\n        assert!(sample.sample_rtt.is_none());\n        assert_eq!(sample.sample_max_inflight, 0);\n        assert_eq!(sample.extra_acked, 0);\n    }\n\n    /// 1) Send 2 packets, 2) Ack both in 1 event, 3) Repeat.\n    #[rstest]\n    fn two_acked_packets_per_event(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(10);\n        let sending_rate = Bandwidth::from_bytes_and_time_delta(\n            REGULAR_PACKET_SIZE,\n            time_between_packets,\n        );\n\n        for i in 1..21 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n            if i % 2 != 0 {\n                continue;\n            }\n\n            let sample = test_sender.on_congestion_event(&[i - 1, i], &[]);\n            assert_eq!(sending_rate, sample.sample_max_bandwidth.unwrap());\n            assert_eq!(time_between_packets, sample.sample_rtt.unwrap());\n            assert_eq!(2 * REGULAR_PACKET_SIZE, sample.sample_max_inflight);\n            assert!(sample.last_packet_send_state.is_valid);\n            assert_eq!(\n                2 * REGULAR_PACKET_SIZE,\n                sample.last_packet_send_state.bytes_in_flight\n            );\n            assert_eq!(\n                i as usize * REGULAR_PACKET_SIZE,\n                sample.last_packet_send_state.total_bytes_sent\n            );\n            assert_eq!(\n                (i - 2) as usize * REGULAR_PACKET_SIZE,\n                sample.last_packet_send_state.total_bytes_acked\n            );\n            assert_eq!(0, sample.last_packet_send_state.total_bytes_lost);\n            test_sender.sampler.remove_obsolete_packets(i - 2);\n        }\n    }\n\n    #[rstest]\n    fn lose_every_other_packet(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(10);\n        let sending_rate = Bandwidth::from_bytes_and_time_delta(\n            REGULAR_PACKET_SIZE,\n            time_between_packets,\n        );\n\n        for i in 1..21 {\n            test_sender.send_packet(i, REGULAR_PACKET_SIZE, true);\n            test_sender.advance_time(time_between_packets);\n            if i % 2 != 0 {\n                continue;\n            }\n            // Ack packet i and lose i-1.\n            let sample = test_sender.on_congestion_event(&[i], &[i - 1]);\n            // Losing 50% packets means sending rate is twice the bandwidth.\n\n            assert_eq!(sending_rate, sample.sample_max_bandwidth.unwrap() * 2.);\n            assert_eq!(time_between_packets, sample.sample_rtt.unwrap());\n            assert_eq!(REGULAR_PACKET_SIZE, sample.sample_max_inflight);\n            assert!(sample.last_packet_send_state.is_valid);\n            assert_eq!(\n                2 * REGULAR_PACKET_SIZE,\n                sample.last_packet_send_state.bytes_in_flight\n            );\n            assert_eq!(\n                i as usize * REGULAR_PACKET_SIZE,\n                sample.last_packet_send_state.total_bytes_sent\n            );\n            assert_eq!(\n                (i - 2) as usize * REGULAR_PACKET_SIZE / 2,\n                sample.last_packet_send_state.total_bytes_acked\n            );\n            assert_eq!(\n                (i - 2) as usize * REGULAR_PACKET_SIZE / 2,\n                sample.last_packet_send_state.total_bytes_lost\n            );\n            test_sender.sampler.remove_obsolete_packets(i - 2);\n        }\n    }\n\n    #[rstest]\n    fn ack_height_respect_bandwidth_estimate_upper_bound(\n        #[values(false, true)] overestimate_avoidance: bool,\n        #[values(false, true)] choose_a0_point_fix: bool,\n    ) {\n        let mut test_sender =\n            TestSender::new(overestimate_avoidance, choose_a0_point_fix);\n        let time_between_packets = Duration::from_millis(10);\n        let first_packet_sending_rate = Bandwidth::from_bytes_and_time_delta(\n            REGULAR_PACKET_SIZE,\n            time_between_packets,\n        );\n\n        // Send packets 1 to 4 and ack packet 1.\n        test_sender.send_packet(1, REGULAR_PACKET_SIZE, true);\n        test_sender.advance_time(time_between_packets);\n        test_sender.send_packet(2, REGULAR_PACKET_SIZE, true);\n        test_sender.send_packet(3, REGULAR_PACKET_SIZE, true);\n        test_sender.send_packet(4, REGULAR_PACKET_SIZE, true);\n        let sample = test_sender.on_congestion_event(&[1], &[]);\n        assert_eq!(\n            first_packet_sending_rate,\n            sample.sample_max_bandwidth.unwrap()\n        );\n        assert_eq!(first_packet_sending_rate, test_sender.max_bandwidth);\n\n        // Ack packet 2, 3 and 4, all of which uses S(1) to calculate ack rate\n        // since there were no acks at the time they were sent.\n        test_sender.round_trip_count += 1;\n        test_sender.est_bandwidth_upper_bound = first_packet_sending_rate * 0.3;\n        test_sender.advance_time(time_between_packets);\n\n        let sample = test_sender.on_congestion_event(&[2, 3, 4], &[]);\n\n        assert_eq!(\n            first_packet_sending_rate * 2.,\n            sample.sample_max_bandwidth.unwrap()\n        );\n        assert_eq!(\n            test_sender.max_bandwidth,\n            sample.sample_max_bandwidth.unwrap()\n        );\n        assert!(2 * REGULAR_PACKET_SIZE < sample.extra_acked);\n    }\n}\n\n#[cfg(test)]\nmod max_ack_height_tracker_tests {\n    use rstest::rstest;\n\n    use super::*;\n\n    struct TestTracker {\n        tracker: MaxAckHeightTracker,\n        bandwidth: Bandwidth,\n        start: Instant,\n        now: Instant,\n        last_sent_packet_number: u64,\n        last_acked_packet_number: u64,\n        rtt: Duration,\n    }\n\n    impl TestTracker {\n        fn new(overestimate_avoidance: bool) -> Self {\n            let mut tracker =\n                MaxAckHeightTracker::new(10, overestimate_avoidance);\n            tracker.ack_aggregation_bandwidth_threshold = 1.8;\n            tracker.start_new_aggregation_epoch_after_full_round = true;\n            let start = Instant::now();\n            TestTracker {\n                tracker,\n                start,\n                now: start + Duration::from_millis(1),\n                bandwidth: Bandwidth::from_bytes_per_second(10 * 1000),\n                last_sent_packet_number: 0,\n                last_acked_packet_number: 0,\n                rtt: Duration::from_millis(60),\n            }\n        }\n\n        // Run a full aggregation episode, which is one or more aggregated acks,\n        // followed by a quiet period in which no ack happens.\n        // After this function returns, the time is set to the earliest point at\n        // which any ack event will cause tracker_.Update() to start a new\n        // aggregation.\n        fn aggregation_episode(\n            &mut self, aggregation_bandwidth: Bandwidth,\n            aggregation_duration: Duration, bytes_per_ack: usize,\n            expect_new_aggregation_epoch: bool,\n        ) {\n            assert!(aggregation_bandwidth >= self.bandwidth);\n            let start_time = self.now;\n\n            let aggregation_bytes =\n                (aggregation_bandwidth * aggregation_duration) as usize;\n\n            let num_acks = aggregation_bytes / bytes_per_ack;\n            assert_eq!(aggregation_bytes, num_acks * bytes_per_ack);\n\n            let time_between_acks = Duration::from_micros(\n                aggregation_duration.as_micros() as u64 / num_acks as u64,\n            );\n            assert_eq!(aggregation_duration, time_between_acks * num_acks as u32);\n\n            // The total duration of aggregation time and quiet period.\n            let total_duration = Duration::from_micros(\n                (aggregation_bytes as u64 * 8 * 1000000) /\n                    self.bandwidth.to_bits_per_second(),\n            );\n\n            assert_eq!(aggregation_bytes as u64, self.bandwidth * total_duration);\n\n            let mut last_extra_acked = 0;\n\n            for bytes in (0..aggregation_bytes).step_by(bytes_per_ack) {\n                let extra_acked = self.tracker.update(\n                    self.bandwidth,\n                    true,\n                    self.round_trip_count(),\n                    self.last_sent_packet_number,\n                    self.last_acked_packet_number,\n                    self.now,\n                    bytes_per_ack,\n                );\n                // `extra_acked` should be 0 if either\n                // [1] We are at the beginning of a aggregation epoch(bytes==0)\n                // and the     the current tracker implementation\n                // can identify it, or [2] We are not really\n                // aggregating acks.\n                if (bytes == 0 && expect_new_aggregation_epoch) ||\n                    (aggregation_bandwidth == self.bandwidth)\n                {\n                    assert_eq!(0, extra_acked);\n                } else {\n                    assert!(last_extra_acked < extra_acked);\n                }\n                self.now += time_between_acks;\n                last_extra_acked = extra_acked;\n            }\n\n            // Advance past the quiet period.\n            self.now = start_time + total_duration;\n        }\n\n        fn round_trip_count(&self) -> usize {\n            ((self.now - self.start).as_micros() / self.rtt.as_micros()) as usize\n        }\n    }\n\n    fn test_inner(\n        overestimate_avoidance: bool, bandwidth_gain: f64,\n        agg_duration: Duration, byte_per_ack: usize,\n    ) {\n        let mut test_tracker = TestTracker::new(overestimate_avoidance);\n\n        let rnd = |tracker: &mut TestTracker, expect: bool| {\n            tracker.aggregation_episode(\n                tracker.bandwidth * bandwidth_gain,\n                agg_duration,\n                byte_per_ack,\n                expect,\n            );\n        };\n\n        rnd(&mut test_tracker, true);\n        rnd(&mut test_tracker, true);\n\n        test_tracker.now = test_tracker\n            .now\n            .checked_sub(Duration::from_millis(1))\n            .unwrap();\n\n        if test_tracker.tracker.ack_aggregation_bandwidth_threshold > 1.1 {\n            rnd(&mut test_tracker, true);\n            assert_eq!(3, test_tracker.tracker.num_ack_aggregation_epochs);\n        } else {\n            rnd(&mut test_tracker, false);\n            assert_eq!(2, test_tracker.tracker.num_ack_aggregation_epochs);\n        }\n    }\n\n    #[rstest]\n    fn very_aggregated_large_acks(\n        #[values(false, true)] overestimate_avoidance: bool,\n    ) {\n        test_inner(overestimate_avoidance, 20.0, Duration::from_millis(6), 1200)\n    }\n\n    #[rstest]\n    fn very_aggregated_small_acks(\n        #[values(false, true)] overestimate_avoidance: bool,\n    ) {\n        test_inner(overestimate_avoidance, 20., Duration::from_millis(6), 300)\n    }\n\n    #[rstest]\n    fn somewhat_aggregated_large_acks(\n        #[values(false, true)] overestimate_avoidance: bool,\n    ) {\n        test_inner(overestimate_avoidance, 2.0, Duration::from_millis(50), 1000)\n    }\n\n    #[rstest]\n    fn somewhat_aggregated_small_acks(\n        #[values(false, true)] overestimate_avoidance: bool,\n    ) {\n        test_inner(overestimate_avoidance, 2.0, Duration::from_millis(50), 100)\n    }\n\n    #[rstest]\n    fn not_aggregated(#[values(false, true)] overestimate_avoidance: bool) {\n        let mut test_tracker = TestTracker::new(overestimate_avoidance);\n        test_tracker.aggregation_episode(\n            test_tracker.bandwidth,\n            Duration::from_millis(100),\n            100,\n            true,\n        );\n        assert!(2 < test_tracker.tracker.num_ack_aggregation_epochs);\n    }\n\n    #[rstest]\n    fn start_new_epoch_after_a_full_round(\n        #[values(false, true)] overestimate_avoidance: bool,\n    ) {\n        let mut test_tracker = TestTracker::new(overestimate_avoidance);\n\n        test_tracker.last_sent_packet_number = 10;\n\n        test_tracker.aggregation_episode(\n            test_tracker.bandwidth * 2.0,\n            Duration::from_millis(50),\n            100,\n            true,\n        );\n\n        test_tracker.last_acked_packet_number = 11;\n\n        // Update with a tiny bandwidth causes a very low expected bytes acked,\n        // which in turn causes the current epoch to continue if the\n        // `tracker` doesn't check the packet numbers.\n        test_tracker.tracker.update(\n            test_tracker.bandwidth * 0.1,\n            true,\n            test_tracker.round_trip_count(),\n            test_tracker.last_sent_packet_number,\n            test_tracker.last_acked_packet_number,\n            test_tracker.now,\n            100,\n        );\n\n        assert_eq!(2, test_tracker.tracker.num_ack_aggregation_epochs)\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr/windowed_filter.rs",
    "content": "// Copyright (c) 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Implements Kathleen Nichols' algorithm for tracking the minimum (or maximum)\n// estimate of a stream of samples over some fixed time interval. (E.g.,\n// the minimum RTT over the past five minutes.) The algorithm keeps track of\n// the best, second best, and third best min (or max) estimates, maintaining an\n// invariant that the measurement time of the n'th best >= n-1'th best.\n\n// The algorithm works as follows. On a reset, all three estimates are set to\n// the same sample. The second best estimate is then recorded in the second\n// quarter of the window, and a third best estimate is recorded in the second\n// half of the window, bounding the worst case error when the true min is\n// monotonically increasing (or true max is monotonically decreasing) over the\n// window.\n//\n// A new best sample replaces all three estimates, since the new best is lower\n// (or higher) than everything else in the window and it is the most recent.\n// The window thus effectively gets reset on every new min. The same property\n// holds true for second best and third best estimates. Specifically, when a\n// sample arrives that is better than the second best but not better than the\n// best, it replaces the second and third best estimates but not the best\n// estimate. Similarly, a sample that is better than the third best estimate\n// but not the other estimates replaces only the third best estimate.\n//\n// Finally, when the best expires, it is replaced by the second best, which in\n// turn is replaced by the third best. The newest sample replaces the third\n// best.\n\nuse std::ops::Div;\nuse std::ops::Sub;\n\n#[derive(Debug, Clone, Copy)]\nstruct Sample<T, I> {\n    sample: T,\n    time: I,\n}\n\n#[derive(Debug)]\npub struct WindowedFilter<T, I, D> {\n    window_length: D,\n    estimates: [Option<Sample<T, I>>; 3],\n}\n\nimpl<T, I, D> WindowedFilter<T, I, D>\nwhere\n    T: Ord + Copy,\n    I: Sub<I, Output = D> + Copy,\n    D: Ord + Div<usize, Output = D> + Copy,\n{\n    pub fn new(window_length: D) -> Self {\n        WindowedFilter {\n            window_length,\n            estimates: [None, None, None],\n        }\n    }\n\n    pub fn reset(&mut self, new_sample: T, new_time: I) {\n        let sample = Some(Sample {\n            sample: new_sample,\n            time: new_time,\n        });\n\n        self.estimates = [sample, sample, sample];\n    }\n\n    pub fn get_best(&self) -> Option<T> {\n        self.estimates[0].as_ref().map(|e| e.sample)\n    }\n\n    pub fn get_second_best(&self) -> Option<T> {\n        self.estimates[1].as_ref().map(|e| e.sample)\n    }\n\n    pub fn get_third_best(&self) -> Option<T> {\n        self.estimates[2].as_ref().map(|e| e.sample)\n    }\n\n    pub fn clear(&mut self) {\n        self.estimates = [None, None, None];\n    }\n\n    pub fn update(&mut self, new_sample: T, new_time: I) {\n        // Reset all estimates if they have not yet been initialized, if new\n        // sample is a new best, or if the newest recorded estimate is too\n        // old.\n        if match &self.estimates[0] {\n            None => true,\n            Some(best) if new_sample > best.sample => true,\n            _ =>\n                new_time - self.estimates[2].as_ref().unwrap().time >\n                    self.window_length,\n        } {\n            return self.reset(new_sample, new_time);\n        }\n\n        if new_sample > self.estimates[1].unwrap().sample {\n            self.estimates[1] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n            self.estimates[2] = self.estimates[1];\n        } else if new_sample > self.estimates[2].unwrap().sample {\n            self.estimates[2] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n        }\n\n        // Expire and update estimates as necessary.\n        if new_time - self.estimates[0].unwrap().time > self.window_length {\n            // The best estimate hasn't been updated for an entire window, so\n            // promote second and third best estimates.\n            self.estimates[0] = self.estimates[1];\n            self.estimates[1] = self.estimates[2];\n            self.estimates[2] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n            // Need to iterate one more time. Check if the new best estimate is\n            // outside the window as well, since it may also have been recorded a\n            // long time ago. Don't need to iterate once more since we cover that\n            // case at the beginning of the method.\n            if new_time - self.estimates[0].unwrap().time > self.window_length {\n                self.estimates[0] = self.estimates[1];\n                self.estimates[1] = self.estimates[2];\n            }\n            return;\n        }\n\n        if self.estimates[1].unwrap().sample == self.estimates[0].unwrap().sample &&\n            new_time - self.estimates[1].unwrap().time > self.window_length / 4\n        {\n            // A quarter of the window has passed without a better sample, so the\n            // second-best estimate is taken from the second quarter of the\n            // window.\n            self.estimates[1] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n            self.estimates[2] = self.estimates[1];\n            return;\n        }\n\n        if self.estimates[2].unwrap().sample == self.estimates[1].unwrap().sample &&\n            new_time - self.estimates[2].unwrap().time > self.window_length / 2\n        {\n            // We've passed a half of the window without a better estimate, so\n            // take a third-best estimate from the second half of the\n            // window.\n            self.estimates[2] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr.rs",
    "content": "// Copyright (c) 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod bandwidth_sampler;\nmod windowed_filter;\n\npub use bandwidth_sampler::BandwidthSampler;\npub use bandwidth_sampler::SendTimeState;\n\nuse super::Acked;\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr2/drain.rs",
    "content": "// Copyright (c) 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Instant;\n\nuse crate::recovery::gcongestion::bbr2::Params;\nuse crate::recovery::gcongestion::Acked;\nuse crate::recovery::gcongestion::Lost;\nuse crate::recovery::RecoveryStats;\n\nuse super::mode::Cycle;\nuse super::mode::Mode;\nuse super::mode::ModeImpl;\nuse super::network_model::BBRv2NetworkModel;\nuse super::BBRv2CongestionEvent;\nuse super::Limits;\n\n#[derive(Debug)]\npub(super) struct Drain {\n    pub(super) model: BBRv2NetworkModel,\n    pub(super) cycle: Cycle,\n}\n\nimpl ModeImpl for Drain {\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self) -> &'static str {\n        \"bbr_drain\"\n    }\n\n    fn is_probing_for_bandwidth(&self) -> bool {\n        false\n    }\n\n    fn on_congestion_event(\n        mut self, _prior_in_flight: usize, event_time: Instant,\n        _acked_packets: &[Acked], _lost_packets: &[Lost],\n        congestion_event: &mut BBRv2CongestionEvent,\n        _target_bytes_inflight: usize, params: &Params,\n        _recovery_stats: &mut RecoveryStats, _cwnd: usize,\n    ) -> Mode {\n        self.model.set_pacing_gain(params.drain_pacing_gain);\n        // Only STARTUP can transition to DRAIN, both of them use the same cwnd\n        // gain.\n        self.model.set_cwnd_gain(params.drain_cwnd_gain);\n\n        let drain_target = self.drain_target();\n        if congestion_event.bytes_in_flight <= drain_target {\n            return self.into_probe_bw(\n                event_time,\n                Some(congestion_event),\n                params,\n            );\n        }\n\n        Mode::Drain(self)\n    }\n\n    fn get_cwnd_limits(&self, _params: &Params) -> Limits<usize> {\n        Limits {\n            lo: 0,\n            hi: self.model.inflight_lo(),\n        }\n    }\n\n    fn on_exit_quiescence(\n        self, _now: Instant, _quiescence_start_time: Instant, _params: &Params,\n    ) -> Mode {\n        Mode::Drain(self)\n    }\n\n    fn enter(\n        &mut self, _: Instant, _: Option<&BBRv2CongestionEvent>, _params: &Params,\n    ) {\n    }\n\n    fn leave(&mut self, _: Instant, _: Option<&BBRv2CongestionEvent>) {}\n}\n\nimpl Drain {\n    fn into_probe_bw(\n        mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,\n        params: &Params,\n    ) -> Mode {\n        self.leave(now, congestion_event);\n        let mut next_mode = Mode::probe_bw(self.model, self.cycle);\n        next_mode.enter(now, congestion_event, params);\n        next_mode\n    }\n\n    fn drain_target(&self) -> usize {\n        self.model.bdp0()\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr2/mode.rs",
    "content": "// Copyright (c) 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::fmt::Debug;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse crate::recovery::gcongestion::bbr2::Params;\nuse crate::recovery::gcongestion::Lost;\nuse crate::recovery::RecoveryStats;\n\nuse super::drain::Drain;\nuse super::network_model::BBRv2NetworkModel;\nuse super::probe_bw::ProbeBW;\nuse super::probe_rtt::ProbeRTT;\nuse super::startup::Startup;\nuse super::Acked;\nuse super::BBRv2CongestionEvent;\nuse super::Limits;\n\n#[derive(Debug, Default, PartialEq)]\npub(super) enum CyclePhase {\n    #[default]\n    NotStarted,\n    Up,\n    Down,\n    Cruise,\n    Refill,\n}\n\nimpl CyclePhase {\n    pub(super) fn pacing_gain(&self, params: &Params) -> f32 {\n        match self {\n            CyclePhase::Up => params.probe_bw_probe_up_pacing_gain,\n            CyclePhase::Down => params.probe_bw_probe_down_pacing_gain,\n            _ => params.probe_bw_default_pacing_gain,\n        }\n    }\n\n    pub(super) fn cwnd_gain(&self, params: &Params) -> f32 {\n        match self {\n            CyclePhase::Up => params.probe_bw_up_cwnd_gain,\n            _ => params.probe_bw_cwnd_gain,\n        }\n    }\n}\n\n#[derive(Debug)]\npub(super) struct Cycle {\n    pub(super) start_time: Instant,\n    pub(super) phase: CyclePhase,\n    pub(super) rounds_in_phase: usize,\n    pub(super) phase_start_time: Instant,\n    pub(super) rounds_since_probe: usize,\n    pub(super) probe_wait_time: Option<Duration>,\n    pub(super) probe_up_rounds: usize,\n    pub(super) probe_up_bytes: Option<usize>,\n    pub(super) probe_up_acked: usize,\n    pub(super) probe_up_app_limited_since_inflight_hi_limited: bool,\n    // Whether max bandwidth filter window has advanced in this cycle. It is\n    // advanced once per cycle.\n    pub(super) has_advanced_max_bw: bool,\n    pub(super) is_sample_from_probing: bool,\n\n    pub(super) last_cycle_probed_too_high: bool,\n    pub(super) last_cycle_stopped_risky_probe: bool,\n}\n\nimpl Default for Cycle {\n    fn default() -> Self {\n        let now = Instant::now();\n\n        Cycle {\n            start_time: now,\n            phase_start_time: now,\n\n            phase: CyclePhase::NotStarted,\n            rounds_in_phase: 0,\n            rounds_since_probe: 0,\n            probe_wait_time: None,\n            probe_up_rounds: 0,\n            probe_up_bytes: None,\n            probe_up_acked: 0,\n            probe_up_app_limited_since_inflight_hi_limited: false,\n            has_advanced_max_bw: false,\n            is_sample_from_probing: false,\n            last_cycle_probed_too_high: false,\n            last_cycle_stopped_risky_probe: false,\n        }\n    }\n}\n\n#[enum_dispatch::enum_dispatch]\npub(super) trait ModeImpl: Debug {\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self) -> &'static str;\n\n    fn enter(\n        &mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,\n        params: &Params,\n    );\n\n    fn leave(\n        &mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,\n    );\n\n    fn is_probing_for_bandwidth(&self) -> bool;\n\n    #[allow(clippy::too_many_arguments)]\n    fn on_congestion_event(\n        self, prior_in_flight: usize, event_time: Instant,\n        acked_packets: &[Acked], lost_packets: &[Lost],\n        congestion_event: &mut BBRv2CongestionEvent,\n        target_bytes_inflight: usize, params: &Params,\n        recovery_stats: &mut RecoveryStats, cwnd: usize,\n    ) -> Mode;\n\n    fn get_cwnd_limits(&self, params: &Params) -> Limits<usize>;\n\n    fn on_exit_quiescence(\n        self, now: Instant, quiescence_start_time: Instant, params: &Params,\n    ) -> Mode;\n}\n\n#[enum_dispatch::enum_dispatch(ModeImpl)]\n#[derive(Debug)]\npub(super) enum Mode {\n    Startup(Startup),\n    Drain(Drain),\n    ProbeBW(ProbeBW),\n    ProbeRTT(ProbeRTT),\n    Placheolder(Placeholder),\n}\n\nimpl Default for Mode {\n    fn default() -> Self {\n        Mode::Placheolder(Placeholder {})\n    }\n}\n\nimpl Mode {\n    pub(super) fn startup(model: BBRv2NetworkModel) -> Self {\n        Mode::Startup(Startup { model })\n    }\n\n    pub(super) fn drain(model: BBRv2NetworkModel) -> Self {\n        Mode::Drain(Drain {\n            model,\n            cycle: Default::default(),\n        })\n    }\n\n    pub(super) fn probe_bw(model: BBRv2NetworkModel, cycle: Cycle) -> Self {\n        Mode::ProbeBW(ProbeBW { model, cycle })\n    }\n\n    pub(super) fn probe_rtt(model: BBRv2NetworkModel, cycle: Cycle) -> Self {\n        Mode::ProbeRTT(ProbeRTT::new(model, cycle))\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub(super) fn do_on_congestion_event(\n        &mut self, prior_in_flight: usize, event_time: Instant,\n        acked_packets: &[Acked], lost_packets: &[Lost],\n        congestion_event: &mut BBRv2CongestionEvent,\n        target_bytes_inflight: usize, params: &Params,\n        recovery_stats: &mut RecoveryStats, cwnd: usize,\n    ) -> bool {\n        let mode_before = std::mem::discriminant(self);\n\n        *self = std::mem::take(self).on_congestion_event(\n            prior_in_flight,\n            event_time,\n            acked_packets,\n            lost_packets,\n            congestion_event,\n            target_bytes_inflight,\n            params,\n            recovery_stats,\n            cwnd,\n        );\n\n        let mode_after = std::mem::discriminant(self);\n\n        mode_before != mode_after\n    }\n\n    pub(super) fn do_on_exit_quiescence(\n        &mut self, now: Instant, quiescence_start_time: Instant, params: &Params,\n    ) {\n        *self = std::mem::take(self).on_exit_quiescence(\n            now,\n            quiescence_start_time,\n            params,\n        )\n    }\n\n    pub fn network_model(&self) -> &BBRv2NetworkModel {\n        match self {\n            Mode::Startup(Startup { model }) => model,\n            Mode::Drain(Drain { model, .. }) => model,\n            Mode::ProbeBW(ProbeBW { model, .. }) => model,\n            Mode::ProbeRTT(ProbeRTT { model, .. }) => model,\n            Mode::Placheolder(_) => unreachable!(),\n        }\n    }\n\n    pub fn network_model_mut(&mut self) -> &mut BBRv2NetworkModel {\n        match self {\n            Mode::Startup(Startup { model }) => model,\n            Mode::Drain(Drain { model, .. }) => model,\n            Mode::ProbeBW(ProbeBW { model, .. }) => model,\n            Mode::ProbeRTT(ProbeRTT { model, .. }) => model,\n            Mode::Placheolder(_) => unreachable!(),\n        }\n    }\n}\n\n#[derive(Debug, Default)]\npub(super) struct Placeholder {}\n\nimpl ModeImpl for Placeholder {\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self) -> &'static str {\n        unreachable!()\n    }\n\n    fn enter(\n        &mut self, _: Instant, _: Option<&BBRv2CongestionEvent>, _params: &Params,\n    ) {\n        unreachable!()\n    }\n\n    fn leave(&mut self, _: Instant, _: Option<&BBRv2CongestionEvent>) {\n        unreachable!()\n    }\n\n    fn is_probing_for_bandwidth(&self) -> bool {\n        unreachable!()\n    }\n\n    fn on_congestion_event(\n        self, _: usize, _: Instant, _: &[Acked], _: &[Lost],\n        _: &mut BBRv2CongestionEvent, _: usize, _params: &Params,\n        _recovery_stats: &mut RecoveryStats, _cwnd: usize,\n    ) -> Mode {\n        unreachable!()\n    }\n\n    fn get_cwnd_limits(&self, _params: &Params) -> Limits<usize> {\n        unreachable!()\n    }\n\n    fn on_exit_quiescence(\n        self, _: Instant, _: Instant, _params: &Params,\n    ) -> Mode {\n        unreachable!()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::recovery::gcongestion::bbr2::DEFAULT_PARAMS;\n    use crate::BbrParams;\n\n    #[test]\n    fn cycle_params() {\n        let custom_bbr_settings = BbrParams {\n            probe_bw_up_cwnd_gain: Some(2.25),\n            probe_bw_cwnd_gain: Some(2.0),\n            ..Default::default()\n        };\n        let params = &DEFAULT_PARAMS.with_overrides(&custom_bbr_settings);\n\n        assert_eq!(CyclePhase::Up.pacing_gain(params), 1.25);\n        assert_eq!(CyclePhase::Up.cwnd_gain(params), 2.25);\n\n        assert_eq!(CyclePhase::Down.pacing_gain(params), 0.9);\n        assert_eq!(CyclePhase::Down.cwnd_gain(params), 2.0);\n\n        assert_eq!(CyclePhase::NotStarted.pacing_gain(params), 1.0);\n        assert_eq!(CyclePhase::NotStarted.cwnd_gain(params), 2.0);\n\n        assert_eq!(CyclePhase::Cruise.pacing_gain(params), 1.0);\n        assert_eq!(CyclePhase::Cruise.cwnd_gain(params), 2.0);\n\n        assert_eq!(CyclePhase::Refill.pacing_gain(params), 1.0);\n        assert_eq!(CyclePhase::Refill.cwnd_gain(params), 2.0);\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr2/network_model.rs",
    "content": "// Copyright (c) 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::ops::Add;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse crate::recovery::gcongestion::bbr::BandwidthSampler;\nuse crate::recovery::gcongestion::bbr2::Params;\nuse crate::recovery::gcongestion::Bandwidth;\nuse crate::recovery::gcongestion::Lost;\n\nuse super::Acked;\nuse super::BBRv2CongestionEvent;\nuse super::BwLoMode;\n\npub(super) const DEFAULT_MSS: usize = 1300;\n\n#[derive(Debug)]\nstruct RoundTripCounter {\n    round_trip_count: usize,\n    last_sent_packet: u64,\n    // The last sent packet number of the current round trip.\n    end_of_round_trip: Option<u64>,\n}\n\nimpl RoundTripCounter {\n    /// Must be called in ascending packet number order.\n    fn on_packet_sent(&mut self, packet_number: u64) {\n        self.last_sent_packet = packet_number;\n    }\n\n    /// Return whether a round trip has just completed.\n    fn on_packets_acked(&mut self, last_acked_packet: u64) -> bool {\n        match self.end_of_round_trip {\n            Some(pkt) if last_acked_packet <= pkt => false,\n            _ => {\n                self.round_trip_count += 1;\n                self.end_of_round_trip = Some(self.last_sent_packet);\n                true\n            },\n        }\n    }\n\n    fn restart_round(&mut self) {\n        self.end_of_round_trip = Some(self.last_sent_packet)\n    }\n}\n\n#[derive(Debug)]\nstruct MinRttFilter {\n    min_rtt: Duration,\n    min_rtt_timestamp: Instant,\n}\n\nimpl MinRttFilter {\n    fn get(&self) -> Duration {\n        self.min_rtt\n    }\n\n    fn get_timestamps(&self) -> Instant {\n        self.min_rtt_timestamp\n    }\n\n    fn update(&mut self, sample_rtt: Duration, now: Instant) {\n        if sample_rtt < self.min_rtt {\n            self.min_rtt = sample_rtt;\n            self.min_rtt_timestamp = now;\n        }\n    }\n\n    fn force_update(&mut self, sample_rtt: Duration, now: Instant) {\n        self.min_rtt = sample_rtt;\n        self.min_rtt_timestamp = now;\n    }\n}\n\n#[derive(Debug)]\nstruct MaxBandwidthFilter {\n    max_bandwidth: [Bandwidth; 2],\n}\n\nimpl MaxBandwidthFilter {\n    fn get(&self) -> Bandwidth {\n        self.max_bandwidth[0].max(self.max_bandwidth[1])\n    }\n\n    fn update(&mut self, sample: Bandwidth) {\n        self.max_bandwidth[1] = self.max_bandwidth[1].max(sample);\n    }\n\n    fn advance(&mut self) {\n        if self.max_bandwidth[1] == Bandwidth::zero() {\n            return;\n        }\n\n        self.max_bandwidth[0] = self.max_bandwidth[1];\n        self.max_bandwidth[1] = Bandwidth::zero();\n    }\n}\n\n/// Bbr2NetworkModel takes low level congestion signals(packets sent/acked/lost)\n/// as input and produces BBRv2 model parameters like inflight_(hi|lo),\n/// bandwidth_(hi|lo), bandwidth and rtt estimates, etc.\n#[derive(Debug)]\npub(super) struct BBRv2NetworkModel {\n    round_trip_counter: RoundTripCounter,\n    /// Bandwidth sampler provides BBR with the bandwidth measurements at\n    /// individual points.\n    bandwidth_sampler: BandwidthSampler,\n    /// The filter that tracks the maximum bandwidth over multiple recent round\n    /// trips.\n    max_bandwidth_filter: MaxBandwidthFilter,\n    min_rtt_filter: MinRttFilter,\n    /// Bytes lost in the current round. Updated once per congestion event.\n    bytes_lost_in_round: usize,\n    /// Number of loss marking events in the current round.\n    loss_events_in_round: usize,\n\n    /// A max of bytes delivered among all congestion events in the current\n    /// round. A congestions event's bytes delivered is the total bytes\n    /// acked between time Ts and Ta, which is the time when the largest\n    /// acked packet(within the congestion event) was sent and acked,\n    /// respectively.\n    max_bytes_delivered_in_round: usize,\n\n    /// The minimum bytes in flight during this round.\n    min_bytes_in_flight_in_round: usize,\n    /// True if sending was limited by inflight_hi anytime in the current round.\n    inflight_hi_limited_in_round: bool,\n\n    /// Max bandwidth in the current round. Updated once per congestion event.\n    bandwidth_latest: Bandwidth,\n    /// Max bandwidth of recent rounds. Updated once per round.\n    bandwidth_lo: Option<Bandwidth>,\n    prior_bandwidth_lo: Option<Bandwidth>,\n\n    /// Max inflight in the current round. Updated once per congestion event.\n    inflight_latest: usize,\n    /// Max inflight of recent rounds. Updated once per round.\n    inflight_lo: usize,\n    inflight_hi: usize,\n\n    cwnd_gain: f32,\n    pacing_gain: f32,\n\n    /// Whether we are cwnd limited prior to the start of the current\n    /// aggregation epoch.\n    cwnd_limited_before_aggregation_epoch: bool,\n\n    /// STARTUP-centric fields which experimentally used by PROBE_UP.\n    full_bandwidth_reached: bool,\n    full_bandwidth_baseline: Bandwidth,\n    rounds_without_bandwidth_growth: usize,\n\n    /// Used by STARTUP and PROBE_UP to decide when to exit.\n    rounds_with_queueing: usize,\n\n    /// Determines whether app limited rounds with no bandwidth growth count\n    /// towards the rounds threshold to exit startup.\n    ignore_app_limited_for_no_bandwidth_growth: bool,\n\n    /// The most recent send rate from the BandwidthSampler.\n    latest_send_rate: Option<Bandwidth>,\n    /// The most recent ack rate from the BandwidthSampler.\n    latest_ack_rate: Option<Bandwidth>,\n}\n\nimpl BBRv2NetworkModel {\n    pub(super) fn new(params: &Params, initial_rtt: Duration) -> Self {\n        BBRv2NetworkModel {\n            min_bytes_in_flight_in_round: usize::MAX,\n            inflight_hi_limited_in_round: false,\n            bandwidth_sampler: BandwidthSampler::new(\n                params.initial_max_ack_height_filter_window,\n                params.enable_overestimate_avoidance,\n                params.choose_a0_point_fix,\n            ),\n            round_trip_counter: RoundTripCounter {\n                round_trip_count: 0,\n                last_sent_packet: 0,\n                end_of_round_trip: None,\n            },\n            min_rtt_filter: MinRttFilter {\n                min_rtt: initial_rtt,\n                min_rtt_timestamp: Instant::now(),\n            },\n            max_bandwidth_filter: MaxBandwidthFilter {\n                max_bandwidth: [Bandwidth::zero(), Bandwidth::zero()],\n            },\n            cwnd_limited_before_aggregation_epoch: false,\n            cwnd_gain: params.startup_cwnd_gain,\n            pacing_gain: params.startup_pacing_gain,\n            full_bandwidth_reached: false,\n            bytes_lost_in_round: 0,\n            loss_events_in_round: 0,\n            max_bytes_delivered_in_round: 0,\n            bandwidth_latest: Bandwidth::zero(),\n            bandwidth_lo: None,\n            prior_bandwidth_lo: None,\n            inflight_latest: 0,\n            inflight_lo: usize::MAX,\n            inflight_hi: usize::MAX,\n\n            full_bandwidth_baseline: Bandwidth::zero(),\n            rounds_without_bandwidth_growth: 0,\n            rounds_with_queueing: 0,\n\n            ignore_app_limited_for_no_bandwidth_growth: params\n                .ignore_app_limited_for_no_bandwidth_growth,\n\n            latest_send_rate: None,\n            latest_ack_rate: None,\n        }\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub(super) fn send_rate(&self) -> Option<Bandwidth> {\n        self.latest_send_rate\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub(super) fn ack_rate(&self) -> Option<Bandwidth> {\n        self.latest_ack_rate\n    }\n\n    pub(super) fn max_ack_height(&self) -> usize {\n        self.bandwidth_sampler.max_ack_height().unwrap_or(0)\n    }\n\n    pub(super) fn bandwidth_estimate(&self) -> Bandwidth {\n        match (self.bandwidth_lo, self.max_bandwidth()) {\n            (None, b) => b,\n            (Some(a), b) => a.min(b),\n        }\n    }\n\n    pub(super) fn bdp(&self, bandwidth: Bandwidth, gain: f32) -> usize {\n        (bandwidth * gain).to_bytes_per_period(self.min_rtt()) as usize\n    }\n\n    pub(super) fn bdp1(&self, bandwidth: Bandwidth) -> usize {\n        self.bdp(bandwidth, 1.0)\n    }\n\n    pub(super) fn bdp0(&self) -> usize {\n        self.bdp1(self.max_bandwidth())\n    }\n\n    pub(super) fn min_rtt(&self) -> Duration {\n        self.min_rtt_filter.get()\n    }\n\n    pub(super) fn min_rtt_timestamp(&self) -> Instant {\n        self.min_rtt_filter.get_timestamps()\n    }\n\n    pub(super) fn max_bandwidth(&self) -> Bandwidth {\n        self.max_bandwidth_filter.get()\n    }\n\n    pub(super) fn on_packet_sent(\n        &mut self, sent_time: Instant, bytes_in_flight: usize,\n        packet_number: u64, bytes: usize, is_retransmissible: bool,\n    ) {\n        // Updating the min here ensures a more realistic (0) value when flows\n        // exit quiescence.\n        self.min_bytes_in_flight_in_round =\n            self.min_bytes_in_flight_in_round.min(bytes_in_flight);\n\n        if bytes_in_flight + bytes >= self.inflight_hi {\n            self.inflight_hi_limited_in_round = true;\n        }\n        self.round_trip_counter.on_packet_sent(packet_number);\n\n        self.bandwidth_sampler.on_packet_sent(\n            sent_time,\n            packet_number,\n            bytes,\n            bytes_in_flight,\n            is_retransmissible,\n        );\n    }\n\n    pub(super) fn on_congestion_event_start(\n        &mut self, acked_packets: &[Acked], lost_packets: &[Lost],\n        congestion_event: &mut BBRv2CongestionEvent, params: &Params,\n    ) {\n        let prior_bytes_acked = self.total_bytes_acked();\n        let prior_bytes_lost = self.total_bytes_lost();\n\n        let event_time = congestion_event.event_time;\n\n        congestion_event.end_of_round_trip =\n            if let Some(largest_acked) = acked_packets.last() {\n                self.round_trip_counter\n                    .on_packets_acked(largest_acked.pkt_num)\n            } else {\n                false\n            };\n\n        let sample = self.bandwidth_sampler.on_congestion_event(\n            event_time,\n            acked_packets,\n            lost_packets,\n            Some(self.max_bandwidth()),\n            self.bandwidth_lo.unwrap_or(Bandwidth::infinite()),\n            self.round_trip_count(),\n        );\n\n        if sample.extra_acked == 0 {\n            self.cwnd_limited_before_aggregation_epoch = congestion_event\n                .prior_bytes_in_flight >=\n                congestion_event.prior_cwnd;\n        }\n\n        if sample.last_packet_send_state.is_valid {\n            congestion_event.last_packet_send_state =\n                sample.last_packet_send_state;\n        }\n\n        // Avoid updating `max_bandwidth_filter` if a) this is a loss-only event,\n        // or b) all packets in `acked_packets` did not generate valid\n        // samples. (e.g. ack of ack-only packets). In both cases,\n        // total_bytes_acked() will not change.\n        if let Some(sample_max) = sample.sample_max_bandwidth {\n            if prior_bytes_acked != self.total_bytes_acked() {\n                congestion_event.sample_max_bandwidth = Some(sample_max);\n                if !sample.sample_is_app_limited ||\n                    sample_max > self.max_bandwidth()\n                {\n                    self.max_bandwidth_filter.update(sample_max);\n                }\n            }\n        }\n\n        if let Some(rtt_sample) = sample.sample_rtt {\n            congestion_event.sample_min_rtt = Some(rtt_sample);\n            self.min_rtt_filter.update(rtt_sample, event_time);\n        }\n\n        self.latest_send_rate = sample.sample_max_send_rate;\n        self.latest_ack_rate = sample.sample_max_ack_rate;\n\n        congestion_event.bytes_acked =\n            self.total_bytes_acked() - prior_bytes_acked;\n        congestion_event.bytes_lost = self.total_bytes_lost() - prior_bytes_lost;\n\n        congestion_event.bytes_in_flight = congestion_event\n            .prior_bytes_in_flight\n            .saturating_sub(congestion_event.bytes_acked)\n            .saturating_sub(congestion_event.bytes_lost);\n\n        if congestion_event.bytes_lost > 0 {\n            self.bytes_lost_in_round += congestion_event.bytes_lost;\n            self.loss_events_in_round += 1;\n        }\n\n        if congestion_event.bytes_acked > 0 &&\n            congestion_event.last_packet_send_state.is_valid &&\n            self.total_bytes_acked() >\n                congestion_event.last_packet_send_state.total_bytes_acked\n        {\n            let bytes_delivered = self.total_bytes_acked() -\n                congestion_event.last_packet_send_state.total_bytes_acked;\n            self.max_bytes_delivered_in_round =\n                self.max_bytes_delivered_in_round.max(bytes_delivered);\n        }\n\n        self.min_bytes_in_flight_in_round = self\n            .min_bytes_in_flight_in_round\n            .min(congestion_event.bytes_in_flight);\n\n        // `bandwidth_latest` and `inflight_latest` only increased within a\n        // round.\n        if sample.sample_max_bandwidth > Some(self.bandwidth_latest) {\n            self.bandwidth_latest = sample.sample_max_bandwidth.unwrap();\n        }\n\n        if sample.sample_max_inflight > self.inflight_latest {\n            self.inflight_latest = sample.sample_max_inflight;\n        }\n\n        // Adapt lower bounds(bandwidth_lo and inflight_lo).\n        self.adapt_lower_bounds(congestion_event, params);\n\n        if !congestion_event.end_of_round_trip {\n            return;\n        }\n\n        if let Some(bandwidth) = sample.sample_max_bandwidth {\n            self.bandwidth_latest = bandwidth;\n        }\n\n        if sample.sample_max_inflight > 0 {\n            self.inflight_latest = sample.sample_max_inflight;\n        }\n    }\n\n    pub(super) fn on_packet_neutered(&mut self, packet_number: u64) {\n        self.bandwidth_sampler.on_packet_neutered(packet_number)\n    }\n\n    fn adapt_lower_bounds(\n        &mut self, congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) {\n        if params.bw_lo_mode == BwLoMode::Default {\n            if !congestion_event.end_of_round_trip ||\n                congestion_event.is_probing_for_bandwidth\n            {\n                return;\n            }\n\n            if self.bytes_lost_in_round > 0 {\n                if self.bandwidth_lo.is_none() {\n                    self.bandwidth_lo = Some(self.max_bandwidth());\n                }\n\n                self.bandwidth_lo = Some(\n                    self.bandwidth_latest\n                        .max(self.bandwidth_lo.unwrap() * (1.0 - params.beta)),\n                );\n\n                if self.inflight_lo == usize::MAX {\n                    self.inflight_lo = congestion_event.prior_cwnd;\n                }\n\n                let inflight_lo_new =\n                    (self.inflight_lo as f32 * (1.0 - params.beta)) as usize;\n                self.inflight_lo = self.inflight_latest.max(inflight_lo_new);\n            }\n            return;\n        }\n\n        if congestion_event.bytes_lost == 0 {\n            return;\n        }\n\n        // Ignore losses from packets sent when probing for more bandwidth in\n        // STARTUP or PROBE_UP when they're lost in DRAIN or PROBE_DOWN.\n        if self.pacing_gain() < 1. {\n            return;\n        }\n\n        // Decrease bandwidth_lo whenever there is loss.\n        // Set `bandwidth_lo`if it is not yet set.\n        if self.bandwidth_lo.is_none() {\n            self.bandwidth_lo = Some(self.max_bandwidth());\n        }\n\n        // Save `bandwidth_lo` if it hasn't already been saved.\n        if self.prior_bandwidth_lo.is_none() {\n            self.prior_bandwidth_lo = self.bandwidth_lo;\n        }\n\n        match params.bw_lo_mode {\n            BwLoMode::Default => unreachable!(\"Handled above\"),\n            BwLoMode::MinRttReduction => {\n                let reduction = Bandwidth::from_bytes_and_time_delta(\n                    congestion_event.bytes_lost,\n                    self.min_rtt(),\n                );\n\n                self.bandwidth_lo = self\n                    .bandwidth_lo\n                    .map(|b| (b - reduction).unwrap_or(Bandwidth::zero()));\n            },\n            BwLoMode::InflightReduction => {\n                // Use a max of BDP and inflight to avoid starving app-limited\n                // flows.\n                let effective_inflight =\n                    self.bdp0().max(congestion_event.prior_bytes_in_flight);\n                // This could use bytes_lost_in_round if the bandwidth_lo_ was\n                // saved when entering 'recovery', but this BBRv2\n                // implementation doesn't have recovery defined.\n                self.bandwidth_lo = self.bandwidth_lo.map(|b| {\n                    b * ((effective_inflight as f64 -\n                        congestion_event.bytes_lost as f64) /\n                        effective_inflight as f64)\n                });\n            },\n            BwLoMode::CwndReduction => {\n                self.bandwidth_lo = self.bandwidth_lo.map(|b| {\n                    b * ((congestion_event.prior_cwnd as f64 -\n                        congestion_event.bytes_lost as f64) /\n                        congestion_event.prior_cwnd as f64)\n                });\n            },\n        }\n\n        let mut last_bandwidth = self.bandwidth_latest;\n        // sample_max_bandwidth will be None if the loss is triggered by a timer\n        // expiring. Ideally we'd use the most recent bandwidth sample,\n        // but bandwidth_latest is safer than None.\n        if let Some(sample_max_bandwidth) = congestion_event.sample_max_bandwidth\n        {\n            // bandwidth_latest is the max bandwidth for the round, but to allow\n            // fast, conservation style response to loss, use the last sample.\n            last_bandwidth = sample_max_bandwidth;\n        }\n        if self.pacing_gain > params.full_bw_threshold {\n            // In STARTUP, `pacing_gain` is applied to `bandwidth_lo` in\n            // update_pacing_rate, so this backs that multiplication out to allow\n            // the pacing rate to decrease, but not below\n            // last_bandwidth * full_bw_threshold.\n            self.bandwidth_lo = self.bandwidth_lo.max(Some(\n                last_bandwidth * (params.full_bw_threshold / self.pacing_gain),\n            ));\n        } else {\n            // Ensure bandwidth_lo isn't lower than last_bandwidth.\n            self.bandwidth_lo = self.bandwidth_lo.max(Some(last_bandwidth))\n        }\n        // If it's the end of the round, ensure bandwidth_lo doesn't decrease more\n        // than beta.\n        if congestion_event.end_of_round_trip {\n            self.bandwidth_lo = self.bandwidth_lo.max(\n                self.prior_bandwidth_lo\n                    .take()\n                    .map(|b| b * (1.0 - params.beta)),\n            )\n        }\n        // These modes ignore inflight_lo as well.\n    }\n\n    pub(super) fn on_congestion_event_finish(\n        &mut self, least_unacked_packet: u64,\n        congestion_event: &BBRv2CongestionEvent,\n    ) {\n        if congestion_event.end_of_round_trip {\n            self.on_new_round();\n        }\n\n        self.bandwidth_sampler\n            .remove_obsolete_packets(least_unacked_packet);\n    }\n\n    pub(super) fn maybe_expire_min_rtt(\n        &mut self, congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) -> bool {\n        if congestion_event.sample_min_rtt.is_none() {\n            return false;\n        }\n\n        if congestion_event.event_time <\n            self.min_rtt_filter.min_rtt_timestamp + params.probe_rtt_period\n        {\n            return false;\n        }\n\n        self.min_rtt_filter.force_update(\n            congestion_event.sample_min_rtt.unwrap(),\n            congestion_event.event_time,\n        );\n\n        true\n    }\n\n    pub(super) fn is_inflight_too_high(\n        &self, congestion_event: &BBRv2CongestionEvent, max_loss_events: usize,\n        params: &Params,\n    ) -> bool {\n        let send_state = &congestion_event.last_packet_send_state;\n\n        if !send_state.is_valid {\n            // Not enough information.\n            return false;\n        }\n\n        if self.loss_events_in_round < max_loss_events {\n            return false;\n        }\n\n        // TODO(vlad): BytesInFlight(send_state);\n        let inflight_at_send = send_state.bytes_in_flight;\n\n        let bytes_lost_in_round = self.bytes_lost_in_round;\n\n        if inflight_at_send > 0 && bytes_lost_in_round > 0 {\n            let lost_in_round_threshold =\n                (inflight_at_send as f32 * params.loss_threshold) as usize;\n            if bytes_lost_in_round > lost_in_round_threshold {\n                return true;\n            }\n        }\n\n        false\n    }\n\n    pub(super) fn restart_round_early(&mut self) {\n        self.on_new_round();\n        self.round_trip_counter.restart_round();\n        self.rounds_with_queueing = 0;\n    }\n\n    fn on_new_round(&mut self) {\n        self.bytes_lost_in_round = 0;\n        self.loss_events_in_round = 0;\n        self.max_bytes_delivered_in_round = 0;\n        self.min_bytes_in_flight_in_round = usize::MAX;\n        self.inflight_hi_limited_in_round = false;\n    }\n\n    pub(super) fn has_bandwidth_growth(\n        &mut self, congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) -> bool {\n        let threshold = self.full_bandwidth_baseline * params.full_bw_threshold;\n\n        if self.max_bandwidth() >= threshold {\n            self.full_bandwidth_baseline = self.max_bandwidth();\n            self.rounds_without_bandwidth_growth = 0;\n            return true;\n        }\n\n        if !congestion_event.last_packet_send_state.is_valid {\n            // last_packet_send_state not available because the\n            // congestion event did not contain any non-ACK frames.\n            return false;\n        }\n\n        let ignore_round = self.ignore_app_limited_for_no_bandwidth_growth &&\n            congestion_event.last_packet_send_state.is_app_limited;\n\n        if !ignore_round {\n            self.rounds_without_bandwidth_growth += 1;\n        }\n\n        // full_bandwidth_reached is only set to true when not app-limited\n        if self.rounds_without_bandwidth_growth >= params.startup_full_bw_rounds &&\n            !congestion_event.last_packet_send_state.is_app_limited\n        {\n            self.full_bandwidth_reached = true;\n        }\n\n        false\n    }\n\n    pub(super) fn queueing_threshold_extra_bytes(&self) -> usize {\n        // TODO(vlad): 2 * mss\n        2 * DEFAULT_MSS\n    }\n\n    pub(super) fn check_persistent_queue(\n        &mut self, target_gain: f32, params: &Params,\n    ) {\n        let target = self\n            .bdp(self.max_bandwidth(), target_gain)\n            .max(self.bdp0() + self.queueing_threshold_extra_bytes());\n\n        if self.min_bytes_in_flight_in_round < target {\n            self.rounds_with_queueing = 0;\n            return;\n        }\n\n        self.rounds_with_queueing += 1;\n        #[allow(clippy::absurd_extreme_comparisons)]\n        if self.rounds_with_queueing >= params.max_startup_queue_rounds {\n            self.full_bandwidth_reached = true;\n        }\n    }\n\n    pub(super) fn max_bytes_delivered_in_round(&self) -> usize {\n        self.max_bytes_delivered_in_round\n    }\n\n    pub(super) fn total_bytes_acked(&self) -> usize {\n        self.bandwidth_sampler.total_bytes_acked()\n    }\n\n    pub(super) fn total_bytes_lost(&self) -> usize {\n        self.bandwidth_sampler.total_bytes_lost()\n    }\n\n    fn round_trip_count(&self) -> usize {\n        self.round_trip_counter.round_trip_count\n    }\n\n    pub(super) fn full_bandwidth_reached(&self) -> bool {\n        self.full_bandwidth_reached\n    }\n\n    pub(super) fn set_full_bandwidth_reached(&mut self) {\n        self.full_bandwidth_reached = true\n    }\n\n    pub(super) fn pacing_gain(&self) -> f32 {\n        self.pacing_gain\n    }\n\n    pub(super) fn set_pacing_gain(&mut self, pacing_gain: f32) {\n        self.pacing_gain = pacing_gain\n    }\n\n    pub(super) fn cwnd_gain(&self) -> f32 {\n        self.cwnd_gain\n    }\n\n    pub(super) fn set_cwnd_gain(&mut self, cwnd_gain: f32) {\n        self.cwnd_gain = cwnd_gain\n    }\n\n    pub(super) fn inflight_hi(&self) -> usize {\n        self.inflight_hi\n    }\n\n    pub(super) fn inflight_hi_with_headroom(&self, params: &Params) -> usize {\n        let headroom =\n            (self.inflight_hi as f32 * params.inflight_hi_headroom) as usize;\n        self.inflight_hi.saturating_sub(headroom)\n    }\n\n    pub(super) fn set_inflight_hi(&mut self, new_inflight_hi: usize) {\n        self.inflight_hi = new_inflight_hi\n    }\n\n    pub(super) fn inflight_hi_default(&self) -> usize {\n        usize::MAX\n    }\n\n    pub(super) fn inflight_lo(&self) -> usize {\n        self.inflight_lo\n    }\n\n    pub(super) fn clear_inflight_lo(&mut self) {\n        self.inflight_lo = usize::MAX\n    }\n\n    pub(super) fn cap_inflight_lo(&mut self, cap: usize) {\n        if self.inflight_lo != usize::MAX {\n            self.inflight_lo = cap.min(self.inflight_lo)\n        }\n    }\n\n    pub(super) fn clear_bandwidth_lo(&mut self) {\n        self.bandwidth_lo = None\n    }\n\n    pub(super) fn advance_max_bandwidth_filter(&mut self) {\n        self.max_bandwidth_filter.advance()\n    }\n\n    pub(super) fn postpone_min_rtt_timestamp(&mut self, duration: Duration) {\n        self.min_rtt_filter\n            .force_update(self.min_rtt(), self.min_rtt_timestamp().add(duration));\n    }\n\n    pub(super) fn on_app_limited(&mut self) {\n        self.bandwidth_sampler.on_app_limited()\n    }\n\n    pub(super) fn loss_events_in_round(&self) -> usize {\n        self.loss_events_in_round\n    }\n\n    pub(super) fn rounds_with_queueing(&self) -> usize {\n        self.rounds_with_queueing\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr2/probe_bw.rs",
    "content": "// Copyright (c) 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::ops::Add;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse crate::recovery::gcongestion::bbr2::Params;\nuse crate::recovery::gcongestion::Acked;\nuse crate::recovery::gcongestion::Lost;\nuse crate::recovery::RecoveryStats;\n\nuse super::mode::Cycle;\nuse super::mode::CyclePhase;\nuse super::mode::Mode;\nuse super::mode::ModeImpl;\nuse super::network_model::BBRv2NetworkModel;\nuse super::network_model::DEFAULT_MSS;\nuse super::BBRv2CongestionEvent;\nuse super::BwLoMode;\nuse super::Limits;\n\n#[derive(Debug)]\npub(super) struct ProbeBW {\n    pub(super) model: BBRv2NetworkModel,\n    pub(super) cycle: Cycle,\n}\n\n#[derive(PartialEq, PartialOrd)]\nenum AdaptUpperBoundsResult {\n    AdaptedOk,\n    AdaptedProbedTooHigh,\n    NotAdaptedInflightHighNotSet,\n    NotAdaptedInvalidSample,\n}\n\nimpl ModeImpl for ProbeBW {\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self) -> &'static str {\n        match self.cycle.phase {\n            CyclePhase::NotStarted => unreachable!(),\n            CyclePhase::Up => \"bbr_probe_bw_up\",\n            CyclePhase::Down => \"bbr_probe_bw_down\",\n            CyclePhase::Cruise => \"bbr_probe_bw_cruise\",\n            CyclePhase::Refill => \"bbr_probe_bw_refill\",\n        }\n    }\n\n    fn enter(\n        &mut self, now: Instant,\n        _congestion_event: Option<&BBRv2CongestionEvent>, params: &Params,\n    ) {\n        self.cycle.start_time = now;\n\n        match self.cycle.phase {\n            CyclePhase::NotStarted => {\n                // First time entering PROBE_BW. Start a new probing cycle.\n                self.enter_probe_down(false, false, now, params)\n            },\n            CyclePhase::Cruise => self.enter_probe_cruise(now),\n            CyclePhase::Refill =>\n                self.enter_probe_refill(self.cycle.probe_up_rounds, now),\n            CyclePhase::Up | CyclePhase::Down => {},\n        }\n    }\n\n    fn on_congestion_event(\n        mut self, prior_in_flight: usize, event_time: Instant, _: &[Acked],\n        _: &[Lost], congestion_event: &mut BBRv2CongestionEvent,\n        target_bytes_inflight: usize, params: &Params,\n        _recovery_stats: &mut RecoveryStats, _cwnd: usize,\n    ) -> Mode {\n        if congestion_event.end_of_round_trip {\n            if self.cycle.start_time != event_time {\n                self.cycle.rounds_since_probe += 1;\n            }\n\n            if self.cycle.phase_start_time != event_time {\n                self.cycle.rounds_in_phase += 1;\n            }\n        }\n\n        let mut switch_to_probe_rtt = false;\n\n        match self.cycle.phase {\n            CyclePhase::NotStarted => unreachable!(),\n            CyclePhase::Up => self.update_probe_up(\n                prior_in_flight,\n                target_bytes_inflight,\n                congestion_event,\n                params,\n            ),\n            CyclePhase::Down => {\n                self.update_probe_down(\n                    target_bytes_inflight,\n                    congestion_event,\n                    params,\n                );\n                if self.cycle.phase != CyclePhase::Down &&\n                    self.model.maybe_expire_min_rtt(congestion_event, params)\n                {\n                    switch_to_probe_rtt = true;\n                }\n            },\n            CyclePhase::Cruise => self.update_probe_cruise(\n                target_bytes_inflight,\n                congestion_event,\n                params,\n            ),\n            CyclePhase::Refill => self.update_probe_refill(\n                target_bytes_inflight,\n                congestion_event,\n                params,\n            ),\n        }\n\n        // Do not need to set the gains if switching to PROBE_RTT, they will be\n        // set when `ProbeRTT::enter` is called.\n        if !switch_to_probe_rtt {\n            self.model\n                .set_pacing_gain(self.cycle.phase.pacing_gain(params));\n            self.model.set_cwnd_gain(self.cycle.phase.cwnd_gain(params));\n        }\n\n        if switch_to_probe_rtt {\n            self.into_probe_rtt(event_time, Some(congestion_event), params)\n        } else {\n            Mode::ProbeBW(self)\n        }\n    }\n\n    fn get_cwnd_limits(&self, params: &Params) -> Limits<usize> {\n        if self.cycle.phase == CyclePhase::Cruise {\n            let limit = self\n                .model\n                .inflight_lo()\n                .min(self.model.inflight_hi_with_headroom(params));\n            return Limits::no_greater_than(limit);\n        }\n\n        if self.cycle.phase == CyclePhase::Up &&\n            params.probe_up_ignore_inflight_hi\n        {\n            // Similar to STARTUP.\n            return Limits::no_greater_than(self.model.inflight_lo());\n        }\n\n        Limits::no_greater_than(\n            self.model.inflight_lo().min(self.model.inflight_hi()),\n        )\n    }\n\n    fn is_probing_for_bandwidth(&self) -> bool {\n        self.cycle.phase == CyclePhase::Refill ||\n            self.cycle.phase == CyclePhase::Up\n    }\n\n    fn on_exit_quiescence(\n        mut self, now: Instant, quiescence_start_time: Instant, _params: &Params,\n    ) -> Mode {\n        self.model\n            .postpone_min_rtt_timestamp(now - quiescence_start_time);\n        Mode::ProbeBW(self)\n    }\n\n    fn leave(\n        &mut self, _now: Instant,\n        _congestion_event: Option<&BBRv2CongestionEvent>,\n    ) {\n    }\n}\n\nimpl ProbeBW {\n    fn enter_probe_down(\n        &mut self, probed_too_high: bool, stopped_risky_probe: bool,\n        now: Instant, params: &Params,\n    ) {\n        let cycle = &mut self.cycle;\n        cycle.last_cycle_probed_too_high = probed_too_high;\n        cycle.last_cycle_stopped_risky_probe = stopped_risky_probe;\n\n        cycle.phase = CyclePhase::Down;\n        cycle.start_time = now;\n        cycle.phase_start_time = now;\n        cycle.rounds_in_phase = 0;\n\n        if params.bw_lo_mode != BwLoMode::Default {\n            // Clear bandwidth lo if it was set in PROBE_UP, because losses in\n            // PROBE_UP should not permanently change bandwidth_lo.\n            // It's possible for bandwidth_lo to be set during REFILL, but if that\n            // was a valid value, it'll quickly be rediscovered.\n            self.model.clear_bandwidth_lo();\n        }\n\n        // Pick probe wait time.\n        // TODO(vlad): actually pick time\n        cycle.rounds_since_probe = 0;\n        cycle.probe_wait_time = Some(\n            params.probe_bw_probe_base_duration + Duration::from_micros(500),\n        );\n\n        cycle.probe_up_bytes = None;\n        cycle.probe_up_app_limited_since_inflight_hi_limited = false;\n        cycle.has_advanced_max_bw = false;\n        self.model.restart_round_early();\n    }\n\n    fn enter_probe_cruise(&mut self, now: Instant) {\n        if self.cycle.phase == CyclePhase::Down {\n            self.exit_probe_down();\n        }\n\n        let cycle = &mut self.cycle;\n\n        self.model.cap_inflight_lo(self.model.inflight_hi());\n        cycle.phase = CyclePhase::Cruise;\n        cycle.phase_start_time = now;\n        cycle.rounds_in_phase = 0;\n        cycle.is_sample_from_probing = false;\n    }\n\n    fn enter_probe_refill(&mut self, probe_up_rounds: usize, now: Instant) {\n        if self.cycle.phase == CyclePhase::Down {\n            self.exit_probe_down();\n        }\n\n        let cycle = &mut self.cycle;\n\n        cycle.phase = CyclePhase::Refill;\n        cycle.phase_start_time = now;\n        cycle.rounds_in_phase = 0;\n\n        cycle.is_sample_from_probing = false;\n        cycle.last_cycle_stopped_risky_probe = false;\n\n        self.model.clear_bandwidth_lo();\n        self.model.clear_inflight_lo();\n        cycle.probe_up_rounds = probe_up_rounds;\n        cycle.probe_up_acked = 0;\n        self.model.restart_round_early();\n    }\n\n    fn enter_probe_up(&mut self, now: Instant, cwnd: usize) {\n        let cycle = &mut self.cycle;\n\n        cycle.phase = CyclePhase::Up;\n        cycle.phase_start_time = now;\n        cycle.rounds_in_phase = 0;\n        cycle.is_sample_from_probing = true;\n        self.raise_inflight_high_slope(cwnd);\n        self.model.restart_round_early();\n    }\n\n    fn exit_probe_down(&mut self) {\n        if !self.cycle.has_advanced_max_bw {\n            self.model.advance_max_bandwidth_filter();\n            self.cycle.has_advanced_max_bw = true;\n        }\n    }\n\n    fn update_probe_down(\n        &mut self, target_bytes_inflight: usize,\n        congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) {\n        if self.cycle.rounds_in_phase == 1 && congestion_event.end_of_round_trip {\n            self.cycle.is_sample_from_probing = false;\n\n            if !congestion_event.last_packet_send_state.is_app_limited {\n                self.model.advance_max_bandwidth_filter();\n                self.cycle.has_advanced_max_bw = true;\n            }\n\n            if self.cycle.last_cycle_stopped_risky_probe &&\n                !self.cycle.last_cycle_probed_too_high\n            {\n                self.enter_probe_refill(0, congestion_event.event_time);\n                return;\n            }\n        }\n\n        self.maybe_adapt_upper_bounds(\n            target_bytes_inflight,\n            congestion_event,\n            params,\n        );\n\n        if self.is_time_to_probe_bandwidth(\n            target_bytes_inflight,\n            congestion_event,\n            params,\n        ) {\n            self.enter_probe_refill(0, congestion_event.event_time);\n            return;\n        }\n\n        // This exit condition is experimental code from Google quiche which\n        // diverges from the RFC. Use `disable_probe_down_early_exit` to override\n        // the behavior.\n        if self.has_stayed_long_enough_in_probe_down(congestion_event, params) {\n            self.enter_probe_cruise(congestion_event.event_time);\n            return;\n        }\n\n        let inflight_with_headroom = self.model.inflight_hi_with_headroom(params);\n        let bytes_in_flight = congestion_event.bytes_in_flight;\n\n        if bytes_in_flight > inflight_with_headroom {\n            // Stay in PROBE_DOWN.\n            return;\n        }\n\n        // Transition to PROBE_CRUISE iff we've drained to target.\n        let bdp = self.model.bdp0();\n\n        if bytes_in_flight < bdp {\n            self.enter_probe_cruise(congestion_event.event_time);\n        }\n    }\n\n    fn update_probe_cruise(\n        &mut self, target_bytes_inflight: usize,\n        congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) {\n        self.maybe_adapt_upper_bounds(\n            target_bytes_inflight,\n            congestion_event,\n            params,\n        );\n\n        if self.is_time_to_probe_bandwidth(\n            target_bytes_inflight,\n            congestion_event,\n            params,\n        ) {\n            self.enter_probe_refill(0, congestion_event.event_time);\n        }\n    }\n\n    fn update_probe_refill(\n        &mut self, target_bytes_inflight: usize,\n        congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) {\n        self.maybe_adapt_upper_bounds(\n            target_bytes_inflight,\n            congestion_event,\n            params,\n        );\n\n        if self.cycle.rounds_in_phase > 0 && congestion_event.end_of_round_trip {\n            self.enter_probe_up(\n                congestion_event.event_time,\n                congestion_event.prior_cwnd,\n            );\n        }\n    }\n\n    fn update_probe_up(\n        &mut self, prior_in_flight: usize, target_bytes_inflight: usize,\n        congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) {\n        if self.maybe_adapt_upper_bounds(\n            target_bytes_inflight,\n            congestion_event,\n            params,\n        ) == AdaptUpperBoundsResult::AdaptedProbedTooHigh\n        {\n            self.enter_probe_down(\n                true,\n                false,\n                congestion_event.event_time,\n                params,\n            );\n            return;\n        }\n\n        self.probe_inflight_high_upward(congestion_event, params);\n\n        let mut is_risky = false;\n        let mut is_queuing = false;\n        if self.cycle.last_cycle_probed_too_high &&\n            prior_in_flight >= self.model.inflight_hi()\n        {\n            is_risky = true;\n        } else if self.cycle.rounds_in_phase > 0 {\n            if params.max_probe_up_queue_rounds > 0 {\n                if congestion_event.end_of_round_trip {\n                    self.model\n                        .check_persistent_queue(params.full_bw_threshold, params);\n                    if self.model.rounds_with_queueing() >=\n                        params.max_probe_up_queue_rounds\n                    {\n                        is_queuing = true;\n                    }\n                }\n            } else {\n                let mut queuing_threshold_extra_bytes =\n                    self.model.queueing_threshold_extra_bytes();\n                if params.add_ack_height_to_queueing_threshold {\n                    queuing_threshold_extra_bytes += self.model.max_ack_height();\n                }\n                let queuing_threshold = (params.full_bw_threshold *\n                    self.model.bdp0() as f32)\n                    as usize +\n                    queuing_threshold_extra_bytes;\n\n                is_queuing =\n                    congestion_event.bytes_in_flight >= queuing_threshold;\n            }\n        }\n\n        if is_risky || is_queuing {\n            self.enter_probe_down(\n                false,\n                is_risky,\n                congestion_event.event_time,\n                params,\n            );\n        }\n    }\n\n    fn is_time_to_probe_bandwidth(\n        &self, target_bytes_inflight: usize,\n        congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) -> bool {\n        if self.has_cycle_lasted(\n            self.cycle.probe_wait_time.unwrap(),\n            congestion_event,\n        ) {\n            return true;\n        }\n\n        if self.is_time_to_probe_for_reno_coexistence(\n            target_bytes_inflight,\n            1.0,\n            congestion_event,\n            params,\n        ) {\n            return true;\n        }\n\n        false\n    }\n\n    fn maybe_adapt_upper_bounds(\n        &mut self, target_bytes_inflight: usize,\n        congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) -> AdaptUpperBoundsResult {\n        let send_state = congestion_event.last_packet_send_state;\n\n        if !send_state.is_valid {\n            return AdaptUpperBoundsResult::NotAdaptedInvalidSample;\n        }\n\n        // TODO(vlad): use BytesInFlight?\n        let mut inflight_at_send = send_state.bytes_in_flight;\n        if params.use_bytes_delivered_for_inflight_hi {\n            inflight_at_send = self.model.total_bytes_acked() -\n                congestion_event.last_packet_send_state.total_bytes_acked;\n        }\n\n        if self.cycle.is_sample_from_probing {\n            if self.model.is_inflight_too_high(\n                congestion_event,\n                params.probe_bw_full_loss_count,\n                params,\n            ) {\n                self.cycle.is_sample_from_probing = false;\n                if !send_state.is_app_limited ||\n                    params.max_probe_up_queue_rounds > 0\n                {\n                    let inflight_target = (target_bytes_inflight as f32 *\n                        (1.0 - params.beta))\n                        as usize;\n\n                    let mut new_inflight_hi =\n                        inflight_at_send.max(inflight_target);\n\n                    if params.limit_inflight_hi_by_max_delivered {\n                        new_inflight_hi = self\n                            .model\n                            .max_bytes_delivered_in_round()\n                            .max(new_inflight_hi);\n                    }\n\n                    self.model.set_inflight_hi(new_inflight_hi);\n                }\n                return AdaptUpperBoundsResult::AdaptedProbedTooHigh;\n            }\n            return AdaptUpperBoundsResult::AdaptedOk;\n        }\n\n        if self.model.inflight_hi() == self.model.inflight_hi_default() {\n            return AdaptUpperBoundsResult::NotAdaptedInflightHighNotSet;\n        }\n\n        // Raise the upper bound for inflight.\n        if inflight_at_send > self.model.inflight_hi() {\n            self.model.set_inflight_hi(inflight_at_send);\n        }\n\n        AdaptUpperBoundsResult::AdaptedOk\n    }\n\n    fn has_cycle_lasted(\n        &self, duration: Duration, congestion_event: &BBRv2CongestionEvent,\n    ) -> bool {\n        (congestion_event.event_time - self.cycle.start_time) > duration\n    }\n\n    fn has_phase_lasted(\n        &self, duration: Duration, congestion_event: &BBRv2CongestionEvent,\n    ) -> bool {\n        (congestion_event.event_time - self.cycle.phase_start_time) > duration\n    }\n\n    fn is_time_to_probe_for_reno_coexistence(\n        &self, target_bytes_inflight: usize, probe_wait_fraction: f64,\n        _congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) -> bool {\n        if !params.enable_reno_coexistence {\n            return false;\n        }\n\n        let mut rounds = params.probe_bw_probe_max_rounds;\n        if params.probe_bw_probe_reno_gain > 0.0 {\n            let reno_rounds = (params.probe_bw_probe_reno_gain *\n                target_bytes_inflight as f32 /\n                DEFAULT_MSS as f32) as usize;\n            rounds = reno_rounds.min(rounds);\n        }\n\n        self.cycle.rounds_since_probe >=\n            (rounds as f64 * probe_wait_fraction) as usize\n    }\n\n    // Used to prevent a BBR2 flow from staying in PROBE_DOWN for too\n    // long, as seen in some multi-sender simulator tests.\n    //\n    // This is experimental code from Google quiche and diverges from the RFC. Use\n    // `disable_probe_down_early_exit` to override the behavior.\n    // - RFC: https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-02.html#name-probebw_down\n    // - Google quiche: https://github.com/google/quiche/blob/b370e7a/quiche/quic/core/congestion_control/bbr2_probe_bw.cc#L142\n    fn has_stayed_long_enough_in_probe_down(\n        &self, congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) -> bool {\n        if params.disable_probe_down_early_exit {\n            return false;\n        }\n\n        // Stay in PROBE_DOWN for at most the time of a min rtt, as it is done\n        // in BBRv1.\n        self.has_phase_lasted(self.model.min_rtt(), congestion_event)\n    }\n\n    fn raise_inflight_high_slope(&mut self, cwnd: usize) {\n        let growth_this_round = 1usize << self.cycle.probe_up_rounds;\n        // The number 30 below means `growth_this_round` is capped at 1G and the\n        // lower bound of `probe_up_bytes` is (practically) 1 mss, at this\n        // speed `inflight_hi`` grows by approximately 1 packet per packet acked.\n        self.cycle.probe_up_rounds = self.cycle.probe_up_rounds.add(1).min(30);\n        let probe_up_bytes = cwnd / growth_this_round;\n        self.cycle.probe_up_bytes = Some(probe_up_bytes.max(DEFAULT_MSS));\n    }\n\n    fn probe_inflight_high_upward(\n        &mut self, congestion_event: &BBRv2CongestionEvent, params: &Params,\n    ) {\n        if params.probe_up_ignore_inflight_hi {\n            // When inflight_hi is disabled in PROBE_UP, it increases when\n            // the number of bytes delivered in a round is larger inflight_hi.\n            return;\n        } else {\n            // TODO(vlad): probe_up_simplify_inflight_hi?\n            if congestion_event.prior_bytes_in_flight <\n                congestion_event.prior_cwnd\n            {\n                // Not fully utilizing cwnd, so can't safely grow.\n                return;\n            }\n\n            if congestion_event.prior_cwnd < self.model.inflight_hi() {\n                // Not fully using inflight_hi, so don't grow it.\n                return;\n            }\n\n            self.cycle.probe_up_acked += congestion_event.bytes_acked;\n        }\n\n        if let Some(probe_up_bytes) = self.cycle.probe_up_bytes {\n            if self.cycle.probe_up_acked >= probe_up_bytes {\n                let delta = self.cycle.probe_up_acked / probe_up_bytes;\n                // probe_up_acked is set to the remainder mod probe_up_bytes;\n                // probe_up_acked >= delta * probe_up_bytes so this\n                // can't underflow.\n                self.cycle.probe_up_acked -= delta * probe_up_bytes;\n                let new_inflight_hi =\n                    self.model.inflight_hi() + delta * DEFAULT_MSS;\n                if new_inflight_hi > self.model.inflight_hi() {\n                    self.model.set_inflight_hi(new_inflight_hi);\n                }\n            }\n        }\n\n        if congestion_event.end_of_round_trip {\n            self.raise_inflight_high_slope(congestion_event.prior_cwnd);\n        }\n    }\n\n    fn into_probe_rtt(\n        mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,\n        params: &Params,\n    ) -> Mode {\n        self.leave(now, congestion_event);\n        let mut next_mode = Mode::probe_rtt(self.model, self.cycle);\n        next_mode.enter(now, congestion_event, params);\n        next_mode\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use rstest::rstest;\n\n    use super::*;\n    use crate::recovery::gcongestion::bbr2::SendTimeState;\n    use crate::recovery::gcongestion::bbr2::DEFAULT_PARAMS;\n\n    #[rstest]\n    fn probe_upward(#[values(100, 10_000, 65_536, 300_000)] step: usize) {\n        let test_event =\n            |probe_bw: &ProbeBW, bytes_acked: usize, end_of_round_trip: bool| {\n                BBRv2CongestionEvent {\n                    event_time: probe_bw.cycle.start_time,\n                    prior_cwnd: probe_bw.model.inflight_hi(),\n                    prior_bytes_in_flight: probe_bw.model.inflight_hi(),\n                    bytes_in_flight: 0,\n                    bytes_acked,\n                    bytes_lost: 0,\n                    end_of_round_trip,\n                    is_probing_for_bandwidth: true,\n                    sample_max_bandwidth: None,\n                    sample_min_rtt: None,\n                    last_packet_send_state: SendTimeState::default(),\n                }\n            };\n\n        let do_probe_up = |probe_bw: &mut ProbeBW,\n                           params: &Params,\n                           total_bytes: usize| {\n            let mut remaining = total_bytes;\n            loop {\n                let congestion_event =\n                    test_event(probe_bw, step.min(remaining), false);\n\n                probe_bw.probe_inflight_high_upward(&congestion_event, params);\n\n                if remaining > step {\n                    remaining -= step;\n                } else {\n                    break;\n                }\n            }\n        };\n\n        let params = &DEFAULT_PARAMS;\n        let model = BBRv2NetworkModel::new(params, Duration::from_millis(333));\n        let cycle = Cycle::default();\n        let mut probe_bw = ProbeBW { model, cycle };\n        probe_bw.model.set_inflight_hi(100_000);\n        probe_bw.raise_inflight_high_slope(100_000);\n        assert_eq!(probe_bw.cycle.probe_up_rounds, 1);\n        assert_eq!(probe_bw.cycle.probe_up_bytes, Some(100_000));\n\n        assert_eq!(probe_bw.model.inflight_hi(), 100_000);\n        do_probe_up(&mut probe_bw, params, 1_000_000);\n        // End inflight_hi should be independent of step size.\n        assert_eq!(probe_bw.model.inflight_hi(), 113_000);\n\n        // Slope is increased at the end of round by decreasing probe_up_bytes.\n        probe_bw.probe_inflight_high_upward(\n            &test_event(&probe_bw, 10_000, true),\n            params,\n        );\n        assert_eq!(probe_bw.cycle.probe_up_rounds, 2);\n        assert_eq!(probe_bw.cycle.probe_up_bytes, Some(56500));\n\n        do_probe_up(&mut probe_bw, params, 1_000_000);\n        // End inflight_hi should be independent of step size.\n        assert_eq!(probe_bw.model.inflight_hi(), 135_100);\n\n        probe_bw.probe_inflight_high_upward(\n            &test_event(&probe_bw, 10_000, true),\n            params,\n        );\n        assert_eq!(probe_bw.cycle.probe_up_rounds, 3);\n        assert_eq!(probe_bw.cycle.probe_up_bytes, Some(33775));\n\n        do_probe_up(&mut probe_bw, params, 1_000_000);\n        // End inflight_hi should be independent of step size.\n        assert_eq!(probe_bw.model.inflight_hi(), 174_100);\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr2/probe_rtt.rs",
    "content": "// Copyright (c) 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Instant;\n\nuse crate::recovery::gcongestion::bbr2::Params;\nuse crate::recovery::gcongestion::Acked;\nuse crate::recovery::gcongestion::Lost;\nuse crate::recovery::RecoveryStats;\n\nuse super::mode::Cycle;\nuse super::mode::Mode;\nuse super::mode::ModeImpl;\nuse super::network_model::BBRv2NetworkModel;\nuse super::BBRv2CongestionEvent;\nuse super::Limits;\n\n#[derive(Debug)]\npub(super) struct ProbeRTT {\n    pub(super) model: BBRv2NetworkModel,\n    pub(super) cycle: Cycle,\n    exit_time: Option<Instant>,\n}\n\nimpl ProbeRTT {\n    pub(super) fn new(model: BBRv2NetworkModel, cycle: Cycle) -> Self {\n        ProbeRTT {\n            model,\n            cycle,\n            exit_time: None,\n        }\n    }\n\n    fn into_probe_bw(\n        mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,\n        params: &Params,\n    ) -> Mode {\n        self.leave(now, congestion_event);\n        let mut next_mode = Mode::probe_bw(self.model, self.cycle);\n        next_mode.enter(now, congestion_event, params);\n        next_mode\n    }\n\n    fn inflight_target(&self, params: &Params) -> usize {\n        self.model.bdp(\n            self.model.max_bandwidth(),\n            params.probe_rtt_inflight_target_bdp_fraction,\n        )\n    }\n}\n\nimpl ModeImpl for ProbeRTT {\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self) -> &'static str {\n        \"bbr_probe_rtt\"\n    }\n\n    fn is_probing_for_bandwidth(&self) -> bool {\n        false\n    }\n\n    fn on_congestion_event(\n        mut self, _prior_in_flight: usize, event_time: Instant,\n        _acked_packets: &[Acked], _lost_packets: &[Lost],\n        congestion_event: &mut BBRv2CongestionEvent,\n        _target_bytes_inflight: usize, params: &Params,\n        _recovery_stats: &mut RecoveryStats, _cwnd: usize,\n    ) -> Mode {\n        match self.exit_time {\n            None => {\n                if congestion_event.bytes_in_flight <=\n                    self.inflight_target(params)\n                {\n                    self.exit_time = Some(\n                        congestion_event.event_time + params.probe_rtt_duration,\n                    )\n                }\n                Mode::ProbeRTT(self)\n            },\n            Some(exit_time) =>\n                if congestion_event.event_time > exit_time {\n                    self.into_probe_bw(event_time, Some(congestion_event), params)\n                } else {\n                    Mode::ProbeRTT(self)\n                },\n        }\n    }\n\n    fn get_cwnd_limits(&self, params: &Params) -> Limits<usize> {\n        let inflight_upper_bound = self\n            .model\n            .inflight_lo()\n            .min(self.model.inflight_hi_with_headroom(params));\n        Limits::no_greater_than(\n            inflight_upper_bound.min(self.inflight_target(params)),\n        )\n    }\n\n    fn on_exit_quiescence(\n        self, now: Instant, _quiescence_start_time: Instant, params: &Params,\n    ) -> Mode {\n        match self.exit_time {\n            None => self.into_probe_bw(now, None, params),\n            Some(exit_time) if now > exit_time =>\n                self.into_probe_bw(now, None, params),\n            Some(_) => Mode::ProbeRTT(self),\n        }\n    }\n\n    fn enter(\n        &mut self, _now: Instant,\n        _congestion_event: Option<&BBRv2CongestionEvent>, params: &Params,\n    ) {\n        self.model.set_pacing_gain(params.probe_rtt_pacing_gain);\n        self.model.set_cwnd_gain(params.probe_rtt_cwnd_gain);\n        self.exit_time = None;\n    }\n\n    fn leave(\n        &mut self, _now: Instant,\n        _congestion_event: Option<&BBRv2CongestionEvent>,\n    ) {\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::recovery::gcongestion::bbr2::DEFAULT_PARAMS;\n    use crate::BbrParams;\n    use std::time::Duration;\n\n    #[test]\n    fn probe_rtt_params() {\n        let custom_bbr_settings = BbrParams {\n            probe_rtt_pacing_gain: Some(0.8),\n            probe_rtt_cwnd_gain: Some(0.5),\n            ..Default::default()\n        };\n        let params = &DEFAULT_PARAMS.with_overrides(&custom_bbr_settings);\n\n        let model = BBRv2NetworkModel::new(params, Duration::from_millis(333));\n        let mut probe_rtt = ProbeRTT::new(model, Cycle::default());\n        probe_rtt.enter(Instant::now(), None, params);\n        assert_eq!(probe_rtt.model.pacing_gain(), 0.8);\n        assert_eq!(probe_rtt.model.cwnd_gain(), 0.5);\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr2/startup.rs",
    "content": "// Copyright (c) 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Instant;\n\nuse crate::recovery::gcongestion::bbr2::Params;\nuse crate::recovery::RecoveryStats;\nuse crate::recovery::StartupExit;\nuse crate::recovery::StartupExitReason;\n\nuse super::mode::Mode;\nuse super::mode::ModeImpl;\nuse super::network_model::BBRv2NetworkModel;\nuse super::Acked;\nuse super::BBRv2CongestionEvent;\nuse super::Limits;\nuse super::Lost;\n\n#[derive(Debug)]\npub(super) struct Startup {\n    pub(super) model: BBRv2NetworkModel,\n}\n\nimpl ModeImpl for Startup {\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self) -> &'static str {\n        \"bbr_startup\"\n    }\n\n    fn is_probing_for_bandwidth(&self) -> bool {\n        true\n    }\n\n    fn on_congestion_event(\n        mut self, _prior_in_flight: usize, event_time: Instant,\n        _acked_packets: &[Acked], _lost_packets: &[Lost],\n        congestion_event: &mut BBRv2CongestionEvent,\n        _target_bytes_inflight: usize, params: &Params,\n        recovery_stats: &mut RecoveryStats, cwnd: usize,\n    ) -> Mode {\n        if self.model.full_bandwidth_reached() {\n            return self.into_drain(event_time, Some(congestion_event), params);\n        }\n\n        if !congestion_event.end_of_round_trip {\n            return Mode::Startup(self);\n        }\n\n        let has_bandwidth_growth =\n            self.model.has_bandwidth_growth(congestion_event, params);\n        if self.model.full_bandwidth_reached() {\n            recovery_stats.set_startup_exit(StartupExit::new(\n                cwnd,\n                Some(self.model.max_bandwidth()),\n                StartupExitReason::BandwidthPlateau,\n            ));\n        }\n\n        let check_persisten_queue =\n            params.max_startup_queue_rounds > 0 && !has_bandwidth_growth;\n        if check_persisten_queue {\n            // https://github.com/google/quiche/blob/27eca0257490df89d2bd2c2a8bcea15565e7831c/quiche/quic/core/congestion_control/bbr2_startup.cc#L60-L62\n            // 1.75 is less than the 2x CWND gain, but substantially more than\n            // 1.25x, the minimum bandwidth increase expected during\n            // STARTUP.\n            self.model.check_persistent_queue(1.75, params);\n            if self.model.full_bandwidth_reached() {\n                recovery_stats.set_startup_exit(StartupExit::new(\n                    cwnd,\n                    Some(self.model.max_bandwidth()),\n                    StartupExitReason::PersistentQueue,\n                ));\n            }\n        }\n\n        // TCP BBR always exits upon excessive losses. QUIC BBRv1 does not exit\n        // upon excessive losses, if enough bandwidth growth is observed or if the\n        // sample was app limited.\n        let check_for_excessive_loss = !congestion_event.last_packet_send_state.is_app_limited &&\n                !has_bandwidth_growth &&\n                // check for excessive loss only if not exiting for other reasons\n                !self.model.full_bandwidth_reached();\n\n        if check_for_excessive_loss {\n            self.check_excessive_losses(congestion_event, params);\n\n            if self.model.full_bandwidth_reached() {\n                recovery_stats.set_startup_exit(StartupExit::new(\n                    cwnd,\n                    Some(self.model.max_bandwidth()),\n                    StartupExitReason::Loss,\n                ));\n            }\n        }\n\n        if self.model.full_bandwidth_reached() {\n            self.into_drain(event_time, Some(congestion_event), params)\n        } else {\n            Mode::Startup(self)\n        }\n    }\n\n    fn get_cwnd_limits(&self, _params: &Params) -> Limits<usize> {\n        Limits {\n            lo: 0,\n            hi: self.model.inflight_lo(),\n        }\n    }\n\n    fn on_exit_quiescence(\n        self, _now: Instant, _quiescence_start_time: Instant, _params: &Params,\n    ) -> Mode {\n        Mode::Startup(self)\n    }\n\n    fn enter(\n        &mut self, _now: Instant,\n        _congestion_event: Option<&BBRv2CongestionEvent>, _params: &Params,\n    ) {\n        unreachable!(\"Enter should never be called for startup\")\n    }\n\n    fn leave(\n        &mut self, _now: Instant,\n        _congestion_event: Option<&BBRv2CongestionEvent>,\n    ) {\n        // Clear bandwidth_lo if it's set during STARTUP.\n        self.model.clear_bandwidth_lo();\n    }\n}\n\nimpl Startup {\n    fn into_drain(\n        mut self, now: Instant, congestion_event: Option<&BBRv2CongestionEvent>,\n        params: &Params,\n    ) -> Mode {\n        self.leave(now, congestion_event);\n        let mut next_mode = Mode::drain(self.model);\n        next_mode.enter(now, congestion_event, params);\n        next_mode\n    }\n\n    fn check_excessive_losses(\n        &mut self, congestion_event: &mut BBRv2CongestionEvent, params: &Params,\n    ) {\n        // At the end of a round trip. Check if loss is too high in this round.\n        if self.model.is_inflight_too_high(\n            congestion_event,\n            params.startup_full_loss_count,\n            params,\n        ) {\n            let mut new_inflight_hi = self.model.bdp0();\n\n            if params.startup_loss_exit_use_max_delivered_for_inflight_hi {\n                new_inflight_hi = new_inflight_hi\n                    .max(self.model.max_bytes_delivered_in_round());\n            }\n\n            self.model.set_inflight_hi(new_inflight_hi);\n            self.model.set_full_bandwidth_reached();\n        }\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/bbr2.rs",
    "content": "// Copyright (c) 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod drain;\nmod mode;\nmod network_model;\nmod probe_bw;\nmod probe_rtt;\nmod startup;\n\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse network_model::BBRv2NetworkModel;\n\nuse crate::recovery::gcongestion::Bandwidth;\nuse crate::recovery::RecoveryStats;\n\nuse self::mode::Mode;\nuse self::mode::ModeImpl;\n\nuse super::bbr::SendTimeState;\nuse super::Acked;\nuse super::BbrBwLoReductionStrategy;\nuse super::BbrParams;\nuse super::CongestionControl;\nuse super::Lost;\nuse super::RttStats;\n\nconst MAX_MODE_CHANGES_PER_CONGESTION_EVENT: usize = 4;\n\n#[derive(Debug)]\nstruct Params {\n    // STARTUP parameters.\n    /// The gain for CWND in startup.\n    startup_cwnd_gain: f32,\n\n    startup_pacing_gain: f32,\n\n    /// STARTUP or PROBE_UP are exited if the total bandwidth growth is less\n    /// than `full_bw_threshold` in the last `startup_full_bw_rounds`` round\n    /// trips.\n    full_bw_threshold: f32,\n\n    /// The number of rounds to stay in  STARTUP before exiting due to\n    /// bandwidth plateau.\n    startup_full_bw_rounds: usize,\n\n    /// Number of rounds to stay in STARTUP when there's a sufficient queue that\n    /// bytes_in_flight never drops below the target (1.75 * BDP).  0 indicates\n    /// the feature is disabled and we never exit due to queueing.\n    max_startup_queue_rounds: usize,\n\n    /// The minimum number of loss marking events to exit STARTUP.\n    startup_full_loss_count: usize,\n\n    /// DRAIN parameters.\n    drain_cwnd_gain: f32,\n\n    drain_pacing_gain: f32,\n\n    // PROBE_BW parameters.\n    /// Max number of rounds before probing for Reno-coexistence.\n    probe_bw_probe_max_rounds: usize,\n\n    enable_reno_coexistence: bool,\n\n    /// Multiplier to get Reno-style probe epoch duration as: k * BDP round\n    /// trips. If zero, disables Reno-style BDP-scaled coexistence mechanism.\n    probe_bw_probe_reno_gain: f32,\n\n    /// Minimum duration for BBR-native probes.\n    probe_bw_probe_base_duration: Duration,\n\n    /// The minimum number of loss marking events to exit the PROBE_UP phase.\n    probe_bw_full_loss_count: usize,\n\n    /// Pacing gains.\n    probe_bw_probe_up_pacing_gain: f32,\n    probe_bw_probe_down_pacing_gain: f32,\n    probe_bw_default_pacing_gain: f32,\n\n    /// cwnd_gain for probe bw phases other than ProbeBW_UP\n    probe_bw_cwnd_gain: f32,\n\n    /// cwnd_gain for ProbeBW_UP\n    probe_bw_up_cwnd_gain: f32,\n\n    // PROBE_UP parameters.\n    probe_up_ignore_inflight_hi: bool,\n\n    /// Number of rounds to stay in PROBE_UP when there's a sufficient queue\n    /// that bytes_in_flight never drops below the target.  0 indicates the\n    /// feature is disabled and we never exit due to queueing.\n    // TODO(vlad):\n    max_probe_up_queue_rounds: usize,\n\n    // PROBE_RTT parameters.\n    probe_rtt_inflight_target_bdp_fraction: f32,\n\n    /// The default period for entering PROBE_RTT\n    probe_rtt_period: Duration,\n\n    probe_rtt_duration: Duration,\n\n    probe_rtt_pacing_gain: f32,\n    probe_rtt_cwnd_gain: f32,\n\n    // Parameters used by multiple modes.\n    /// The initial value of the max ack height filter's window length.\n    initial_max_ack_height_filter_window: usize,\n\n    /// The default fraction of unutilized headroom to try to leave in path\n    /// upon high loss.\n    inflight_hi_headroom: f32,\n\n    /// Estimate startup/bw probing has gone too far if loss rate exceeds this.\n    loss_threshold: f32,\n\n    /// A common factor for multiplicative decreases. Used for adjusting\n    /// `bandwidth_lo``, `inflight_lo`` and `inflight_hi`` upon losses.\n    beta: f32,\n\n    // Experimental flags.\n    add_ack_height_to_queueing_threshold: bool,\n\n    /// Don't run PROBE_RTT on the regular schedule\n    avoid_unnecessary_probe_rtt: bool,\n\n    /// When exiting STARTUP due to loss, set `inflight_hi`` to the max of bdp\n    /// and max bytes delivered in round.\n    limit_inflight_hi_by_max_delivered: bool,\n\n    startup_loss_exit_use_max_delivered_for_inflight_hi: bool,\n\n    /// Increase `inflight_hi`` based on delievered, not inflight.\n    use_bytes_delivered_for_inflight_hi: bool,\n\n    /// Set the pacing gain to 25% larger than the recent BW increase in\n    /// STARTUP.\n    decrease_startup_pacing_at_end_of_round: bool,\n\n    /// Avoid Overestimation in Bandwidth Sampler with ack aggregation.\n    /// This is an old experiment that we have found to under-perform the\n    /// algorithm described in the spec.  Use is not recommended.\n    enable_overestimate_avoidance: bool,\n\n    /// If true, apply the fix to A0 point selection logic so the\n    /// implementation is consistent with the behavior of the\n    /// google/quiche implementation.\n    choose_a0_point_fix: bool,\n\n    /// Controls the behavior of BBRAdaptLowerBoundsFromCongestion().\n    bw_lo_mode: BwLoMode,\n\n    /// Determines whether app limited rounds with no bandwidth growth count\n    /// towards the rounds threshold to exit startup.\n    ignore_app_limited_for_no_bandwidth_growth: bool,\n\n    /// Initial pacing rate for a new connection before an RTT\n    /// estimate is available.  This rate serves as an upper bound on\n    /// the initial pacing rate, which is calculated by dividing the\n    /// initial cwnd by the first RTT estimate.\n    initial_pacing_rate_bytes_per_second: Option<u64>,\n\n    /// If true, scale the pacing rate when updating mss when doing pmtud.\n    scale_pacing_rate_by_mss: bool,\n\n    /// Disable `has_stayed_long_enough_in_probe_down` which can cause ProbeDown\n    /// to exit early.\n    disable_probe_down_early_exit: bool,\n\n    /// Set the expected send time for packets when using BBR to `now`\n    /// instead of `get_next_release_time()`.  Setting the time based\n    /// on `get_next_release_time()` can result in artificially low\n    /// RTT measurements due to the pacer's use of burst_tokens to\n    /// make up for lost time.  BBR has significant problems when\n    /// minRTT is under estimated, so it is better to have the RTT be\n    /// slightly over estimated.  The pacer can only schedule packets\n    /// 1/8th of an RTT into the future, so the error introduced by\n    /// setting `time_sent` to `now` is bounded.\n    time_sent_set_to_now: bool,\n}\n\nimpl Params {\n    fn with_overrides(mut self, custom_bbr_settings: &BbrParams) -> Self {\n        macro_rules! apply_override {\n            ($field:ident) => {\n                if let Some(custom_value) = custom_bbr_settings.$field {\n                    self.$field = custom_value;\n                }\n            };\n        }\n\n        macro_rules! apply_optional_override {\n            ($field:ident) => {\n                if let Some(custom_value) = custom_bbr_settings.$field {\n                    self.$field = Some(custom_value);\n                }\n            };\n        }\n\n        apply_override!(startup_cwnd_gain);\n        apply_override!(startup_pacing_gain);\n        apply_override!(full_bw_threshold);\n        apply_override!(startup_full_bw_rounds);\n        apply_override!(startup_full_loss_count);\n        apply_override!(drain_cwnd_gain);\n        apply_override!(drain_pacing_gain);\n        apply_override!(enable_reno_coexistence);\n        apply_override!(enable_overestimate_avoidance);\n        apply_override!(choose_a0_point_fix);\n        apply_override!(probe_bw_probe_up_pacing_gain);\n        apply_override!(probe_bw_probe_down_pacing_gain);\n        apply_override!(probe_bw_cwnd_gain);\n        apply_override!(probe_bw_up_cwnd_gain);\n        apply_override!(probe_rtt_pacing_gain);\n        apply_override!(probe_rtt_cwnd_gain);\n        apply_override!(max_probe_up_queue_rounds);\n        apply_override!(loss_threshold);\n        apply_override!(use_bytes_delivered_for_inflight_hi);\n        apply_override!(decrease_startup_pacing_at_end_of_round);\n        apply_override!(ignore_app_limited_for_no_bandwidth_growth);\n        apply_override!(scale_pacing_rate_by_mss);\n        apply_override!(disable_probe_down_early_exit);\n        apply_override!(time_sent_set_to_now);\n        apply_optional_override!(initial_pacing_rate_bytes_per_second);\n\n        if let Some(custom_value) = custom_bbr_settings.bw_lo_reduction_strategy {\n            self.bw_lo_mode = custom_value.into();\n        }\n\n        self\n    }\n}\n\nconst DEFAULT_PARAMS: Params = Params {\n    startup_cwnd_gain: 2.0,\n\n    startup_pacing_gain: 2.773,\n\n    full_bw_threshold: 1.25,\n\n    startup_full_bw_rounds: 3,\n\n    max_startup_queue_rounds: 0,\n\n    startup_full_loss_count: 8,\n\n    drain_cwnd_gain: 2.0,\n\n    drain_pacing_gain: 1.0 / 2.885,\n\n    probe_bw_probe_max_rounds: 63,\n\n    enable_reno_coexistence: true,\n\n    probe_bw_probe_reno_gain: 1.0,\n\n    probe_bw_probe_base_duration: Duration::from_millis(2000),\n\n    probe_bw_full_loss_count: 2,\n\n    probe_bw_probe_up_pacing_gain: 1.25,\n\n    probe_bw_probe_down_pacing_gain: 0.9, // BBRv3\n\n    probe_bw_default_pacing_gain: 1.0,\n\n    probe_bw_cwnd_gain: 2.0, // BBRv3\n\n    probe_bw_up_cwnd_gain: 2.25, // BBRv3\n\n    probe_up_ignore_inflight_hi: false,\n\n    max_probe_up_queue_rounds: 2,\n\n    probe_rtt_inflight_target_bdp_fraction: 0.5,\n\n    probe_rtt_period: Duration::from_millis(10000),\n\n    probe_rtt_duration: Duration::from_millis(200),\n\n    probe_rtt_pacing_gain: 1.0,\n\n    probe_rtt_cwnd_gain: 1.0,\n\n    initial_max_ack_height_filter_window: 10,\n\n    inflight_hi_headroom: 0.15,\n\n    loss_threshold: 0.015,\n\n    beta: 0.3,\n\n    add_ack_height_to_queueing_threshold: false,\n\n    avoid_unnecessary_probe_rtt: true,\n\n    limit_inflight_hi_by_max_delivered: true,\n\n    startup_loss_exit_use_max_delivered_for_inflight_hi: true,\n\n    use_bytes_delivered_for_inflight_hi: true,\n\n    decrease_startup_pacing_at_end_of_round: true,\n\n    enable_overestimate_avoidance: false,\n\n    choose_a0_point_fix: false,\n\n    bw_lo_mode: BwLoMode::Default,\n\n    ignore_app_limited_for_no_bandwidth_growth: true,\n\n    initial_pacing_rate_bytes_per_second: None,\n\n    scale_pacing_rate_by_mss: false,\n\n    disable_probe_down_early_exit: false,\n\n    time_sent_set_to_now: true,\n};\n\n#[derive(Debug, PartialEq)]\nenum BwLoMode {\n    /// Mode that implements the BBRAdaptLowerBoundsFromCongestion()\n    /// behavior described in the BBR RFC draft.\n    Default,\n\n    /// BBRAdaptLowerBoundsFromCongestion experiment that reduces\n    /// bw_lo by bytes_lost/min_rtt.\n    ///\n    /// Not recommended.\n    MinRttReduction,\n\n    /// BBRAdaptLowerBoundsFromCongestion experiment that reduces\n    /// bw_lo by bw_lo * bytes_lost/inflight.\n    ///\n    /// Not recommended.\n    InflightReduction,\n\n    /// BBRAdaptLowerBoundsFromCongestion experiment that reduces\n    /// bw_lo by bw_lo * bytes_lost/cwnd\n    ///\n    /// Not recommended.\n    CwndReduction,\n}\n\nimpl From<BbrBwLoReductionStrategy> for BwLoMode {\n    fn from(value: BbrBwLoReductionStrategy) -> Self {\n        match value {\n            BbrBwLoReductionStrategy::Default => BwLoMode::Default,\n            BbrBwLoReductionStrategy::MinRttReduction =>\n                BwLoMode::MinRttReduction,\n            BbrBwLoReductionStrategy::InflightReduction =>\n                BwLoMode::InflightReduction,\n            BbrBwLoReductionStrategy::CwndReduction => BwLoMode::CwndReduction,\n        }\n    }\n}\n\n#[derive(Debug)]\nstruct Limits<T: Ord> {\n    lo: T,\n    hi: T,\n}\n\nimpl<T: Ord + Clone + Copy> Limits<T> {\n    fn min(&self) -> T {\n        self.lo\n    }\n\n    fn apply_limits(&self, val: T) -> T {\n        val.max(self.lo).min(self.hi)\n    }\n}\n\nimpl<T: Ord + Clone + Copy + From<u8>> Limits<T> {\n    pub(crate) fn no_greater_than(val: T) -> Self {\n        Self {\n            lo: T::from(0),\n            hi: val,\n        }\n    }\n}\n\nfn initial_pacing_rate(\n    cwnd_in_bytes: usize, smoothed_rtt: Duration, params: &Params,\n) -> Bandwidth {\n    if let Some(pacing_rate) = params.initial_pacing_rate_bytes_per_second {\n        return Bandwidth::from_bytes_per_second(pacing_rate);\n    }\n\n    Bandwidth::from_bytes_and_time_delta(cwnd_in_bytes, smoothed_rtt) * 2.885\n}\n\n#[derive(Debug)]\npub(crate) struct BBRv2 {\n    mode: Mode,\n    cwnd: usize,\n    mss: usize,\n\n    pacing_rate: Bandwidth,\n\n    cwnd_limits: Limits<usize>,\n\n    initial_cwnd: usize,\n\n    last_sample_is_app_limited: bool,\n    has_non_app_limited_sample: bool,\n    last_quiescence_start: Option<Instant>,\n    params: Params,\n}\n\nstruct BBRv2CongestionEvent {\n    event_time: Instant,\n\n    /// The congestion window prior to the processing of the ack/loss events.\n    prior_cwnd: usize,\n    /// Total bytes inflight before the processing of the ack/loss events.\n    prior_bytes_in_flight: usize,\n\n    /// Total bytes inflight after the processing of the ack/loss events.\n    bytes_in_flight: usize,\n    /// Total bytes acked from acks in this event.\n    bytes_acked: usize,\n    /// Total bytes lost from losses in this event.\n    bytes_lost: usize,\n\n    /// Whether acked_packets indicates the end of a round trip.\n    end_of_round_trip: bool,\n    // When the event happened, whether the sender is probing for bandwidth.\n    is_probing_for_bandwidth: bool,\n\n    // Maximum bandwidth of all bandwidth samples from acked_packets.\n    // This sample may be app-limited, and will be None if there are no newly\n    // acknowledged inflight packets.\n    sample_max_bandwidth: Option<Bandwidth>,\n\n    /// Minimum rtt of all bandwidth samples from acked_packets.\n    /// None if acked_packets is empty.\n    sample_min_rtt: Option<Duration>,\n\n    /// The send state of the largest packet in acked_packets, unless it is\n    /// empty. If acked_packets is empty, it's the send state of the largest\n    /// packet in lost_packets.\n    last_packet_send_state: SendTimeState,\n}\n\nimpl BBRv2CongestionEvent {\n    fn new(\n        event_time: Instant, prior_cwnd: usize, prior_bytes_in_flight: usize,\n        is_probing_for_bandwidth: bool,\n    ) -> Self {\n        BBRv2CongestionEvent {\n            event_time,\n            prior_cwnd,\n            prior_bytes_in_flight,\n            is_probing_for_bandwidth,\n            bytes_in_flight: 0,\n            bytes_acked: 0,\n            bytes_lost: 0,\n            end_of_round_trip: false,\n            last_packet_send_state: Default::default(),\n            sample_max_bandwidth: None,\n            sample_min_rtt: None,\n        }\n    }\n}\n\nimpl BBRv2 {\n    pub fn new(\n        initial_congestion_window: usize, max_congestion_window: usize,\n        max_segment_size: usize, smoothed_rtt: Duration,\n        custom_bbr_params: Option<&BbrParams>,\n    ) -> Self {\n        let cwnd = initial_congestion_window * max_segment_size;\n\n        let params = if let Some(custom_bbr_settings) = custom_bbr_params {\n            DEFAULT_PARAMS.with_overrides(custom_bbr_settings)\n        } else {\n            DEFAULT_PARAMS\n        };\n\n        BBRv2 {\n            mode: Mode::startup(BBRv2NetworkModel::new(&params, smoothed_rtt)),\n            cwnd,\n            pacing_rate: initial_pacing_rate(cwnd, smoothed_rtt, &params),\n            cwnd_limits: Limits {\n                lo: initial_congestion_window * max_segment_size,\n                hi: max_congestion_window * max_segment_size,\n            },\n            initial_cwnd: initial_congestion_window * max_segment_size,\n            last_sample_is_app_limited: false,\n            has_non_app_limited_sample: false,\n            last_quiescence_start: None,\n            mss: max_segment_size,\n            params,\n        }\n    }\n\n    pub fn time_sent_set_to_now(&self) -> bool {\n        self.params.time_sent_set_to_now\n    }\n\n    fn on_exit_quiescence(&mut self, now: Instant) {\n        if let Some(last_quiescence_start) = self.last_quiescence_start.take() {\n            self.mode.do_on_exit_quiescence(\n                now,\n                last_quiescence_start,\n                &self.params,\n            )\n        }\n    }\n\n    fn get_target_congestion_window(&self, gain: f32) -> usize {\n        let network_model = self.mode.network_model();\n        network_model\n            .bdp(network_model.bandwidth_estimate(), gain)\n            .max(self.cwnd_limits.min())\n    }\n\n    fn update_pacing_rate(&mut self, bytes_acked: usize) {\n        let network_model = self.mode.network_model();\n        let bandwidth_estimate = match network_model.bandwidth_estimate() {\n            e if e == Bandwidth::zero() => return,\n            e => e,\n        };\n\n        if network_model.total_bytes_acked() == bytes_acked {\n            // After the first ACK, cwnd is still the initial congestion window.\n            self.pacing_rate = Bandwidth::from_bytes_and_time_delta(\n                self.cwnd,\n                network_model.min_rtt(),\n            );\n\n            if let Some(pacing_rate) =\n                self.params.initial_pacing_rate_bytes_per_second\n            {\n                // Do not allow the pacing rate calculated from the first RTT\n                // measurement to be higher than the configured initial pacing\n                // rate.\n                let initial_pacing_rate =\n                    Bandwidth::from_bytes_per_second(pacing_rate);\n                self.pacing_rate = self.pacing_rate.min(initial_pacing_rate);\n            }\n\n            return;\n        }\n\n        let target_rate = bandwidth_estimate * network_model.pacing_gain();\n        if network_model.full_bandwidth_reached() {\n            self.pacing_rate = target_rate;\n            return;\n        }\n\n        if self.params.decrease_startup_pacing_at_end_of_round &&\n            network_model.pacing_gain() < self.params.startup_pacing_gain\n        {\n            self.pacing_rate = target_rate;\n            return;\n        }\n\n        if self.params.bw_lo_mode != BwLoMode::Default &&\n            network_model.loss_events_in_round() > 0\n        {\n            self.pacing_rate = target_rate;\n            return;\n        }\n\n        // By default, the pacing rate never decreases in STARTUP.\n        self.pacing_rate = self.pacing_rate.max(target_rate);\n    }\n\n    fn update_congestion_window(&mut self, bytes_acked: usize) {\n        let network_model = self.mode.network_model();\n        let mut target_cwnd =\n            self.get_target_congestion_window(network_model.cwnd_gain());\n\n        let prior_cwnd = self.cwnd;\n        if network_model.full_bandwidth_reached() {\n            target_cwnd += network_model.max_ack_height();\n            self.cwnd = target_cwnd.min(prior_cwnd + bytes_acked);\n        } else if prior_cwnd < target_cwnd || prior_cwnd < 2 * self.initial_cwnd {\n            self.cwnd = prior_cwnd + bytes_acked;\n        }\n\n        self.cwnd = self\n            .mode\n            .get_cwnd_limits(&self.params)\n            .apply_limits(self.cwnd);\n        self.cwnd = self.cwnd_limits.apply_limits(self.cwnd);\n    }\n\n    fn on_enter_quiescence(&mut self, time: Instant) {\n        self.last_quiescence_start = Some(time);\n    }\n\n    fn target_bytes_inflight(&self) -> usize {\n        let network_model = &self.mode.network_model();\n        let bdp = network_model.bdp1(network_model.bandwidth_estimate());\n        bdp.min(self.get_congestion_window())\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub(crate) fn send_rate(&self) -> Option<Bandwidth> {\n        self.mode.network_model().send_rate()\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub(crate) fn ack_rate(&self) -> Option<Bandwidth> {\n        self.mode.network_model().ack_rate()\n    }\n}\n\nimpl CongestionControl for BBRv2 {\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self) -> &'static str {\n        self.mode.state_str()\n    }\n\n    fn get_congestion_window(&self) -> usize {\n        self.cwnd\n    }\n\n    fn get_congestion_window_in_packets(&self) -> usize {\n        self.cwnd / self.mss\n    }\n\n    fn can_send(&self, bytes_in_flight: usize) -> bool {\n        bytes_in_flight < self.get_congestion_window()\n    }\n\n    fn on_packet_sent(\n        &mut self, sent_time: Instant, bytes_in_flight: usize,\n        packet_number: u64, bytes: usize, is_retransmissible: bool,\n    ) {\n        if bytes_in_flight == 0 && self.params.avoid_unnecessary_probe_rtt {\n            self.on_exit_quiescence(sent_time);\n        }\n\n        let network_model = self.mode.network_model_mut();\n        network_model.on_packet_sent(\n            sent_time,\n            bytes_in_flight,\n            packet_number,\n            bytes,\n            is_retransmissible,\n        );\n    }\n\n    fn on_congestion_event(\n        &mut self, _rtt_updated: bool, prior_in_flight: usize,\n        _bytes_in_flight: usize, event_time: Instant, acked_packets: &[Acked],\n        lost_packets: &[Lost], least_unacked: u64, _rtt_stats: &RttStats,\n        recovery_stats: &mut RecoveryStats,\n    ) {\n        let mut congestion_event = BBRv2CongestionEvent::new(\n            event_time,\n            self.cwnd,\n            prior_in_flight,\n            self.mode.is_probing_for_bandwidth(),\n        );\n\n        let network_model = self.mode.network_model_mut();\n        network_model.on_congestion_event_start(\n            acked_packets,\n            lost_packets,\n            &mut congestion_event,\n            &self.params,\n        );\n\n        // Number of mode changes allowed for this congestion event.\n        let mut mode_changes_allowed = MAX_MODE_CHANGES_PER_CONGESTION_EVENT;\n        while mode_changes_allowed > 0 &&\n            self.mode.do_on_congestion_event(\n                prior_in_flight,\n                event_time,\n                acked_packets,\n                lost_packets,\n                &mut congestion_event,\n                self.target_bytes_inflight(),\n                &self.params,\n                recovery_stats,\n                self.get_congestion_window(),\n            )\n        {\n            mode_changes_allowed -= 1;\n        }\n\n        self.update_pacing_rate(congestion_event.bytes_acked);\n\n        self.update_congestion_window(congestion_event.bytes_acked);\n\n        let network_model = self.mode.network_model_mut();\n        network_model\n            .on_congestion_event_finish(least_unacked, &congestion_event);\n        self.last_sample_is_app_limited =\n            congestion_event.last_packet_send_state.is_app_limited;\n        if !self.last_sample_is_app_limited {\n            self.has_non_app_limited_sample = true;\n        }\n        if congestion_event.bytes_in_flight == 0 &&\n            self.params.avoid_unnecessary_probe_rtt\n        {\n            self.on_enter_quiescence(event_time);\n        }\n    }\n\n    fn on_packet_neutered(&mut self, packet_number: u64) {\n        let network_model = self.mode.network_model_mut();\n        network_model.on_packet_neutered(packet_number);\n    }\n\n    fn on_retransmission_timeout(&mut self, _packets_retransmitted: bool) {}\n\n    fn on_connection_migration(&mut self) {}\n\n    fn is_in_recovery(&self) -> bool {\n        // TODO(vlad): is this true?\n        self.last_quiescence_start.is_none()\n    }\n\n    fn is_cwnd_limited(&self, bytes_in_flight: usize) -> bool {\n        bytes_in_flight >= self.get_congestion_window()\n    }\n\n    fn pacing_rate(\n        &self, _bytes_in_flight: usize, _rtt_stats: &RttStats,\n    ) -> Bandwidth {\n        self.pacing_rate\n    }\n\n    fn bandwidth_estimate(&self, _rtt_stats: &RttStats) -> Bandwidth {\n        let network_model = self.mode.network_model();\n        network_model.bandwidth_estimate()\n    }\n\n    fn max_bandwidth(&self) -> Bandwidth {\n        self.mode.network_model().max_bandwidth()\n    }\n\n    fn update_mss(&mut self, new_mss: usize) {\n        self.cwnd_limits.hi = (self.cwnd_limits.hi as u64 * new_mss as u64 /\n            self.mss as u64) as usize;\n        self.cwnd_limits.lo = (self.cwnd_limits.lo as u64 * new_mss as u64 /\n            self.mss as u64) as usize;\n        self.cwnd =\n            (self.cwnd as u64 * new_mss as u64 / self.mss as u64) as usize;\n        self.initial_cwnd = (self.initial_cwnd as u64 * new_mss as u64 /\n            self.mss as u64) as usize;\n        if self.params.scale_pacing_rate_by_mss {\n            self.pacing_rate =\n                self.pacing_rate * (new_mss as f64 / self.mss as f64);\n        }\n        self.mss = new_mss;\n    }\n\n    fn on_app_limited(&mut self, bytes_in_flight: usize) {\n        if bytes_in_flight >= self.get_congestion_window() {\n            return;\n        }\n\n        let network_model = self.mode.network_model_mut();\n        network_model.on_app_limited()\n    }\n\n    fn limit_cwnd(&mut self, max_cwnd: usize) {\n        self.cwnd_limits.hi = max_cwnd\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use rstest::rstest;\n\n    use super::*;\n\n    #[rstest]\n    fn update_mss(#[values(false, true)] scale_pacing_rate_by_mss: bool) {\n        const INIT_PACKET_SIZE: usize = 1200;\n        const INIT_WINDOW_PACKETS: usize = 10;\n        const MAX_WINDOW_PACKETS: usize = 10000;\n        const INIT_CWND: usize = INIT_WINDOW_PACKETS * INIT_PACKET_SIZE;\n        const MAX_CWND: usize = MAX_WINDOW_PACKETS * INIT_PACKET_SIZE;\n        let initial_rtt = Duration::from_millis(333);\n        let bbr_params = &BbrParams {\n            scale_pacing_rate_by_mss: Some(scale_pacing_rate_by_mss),\n            ..Default::default()\n        };\n\n        const NEW_PACKET_SIZE: usize = 1450;\n        const NEW_CWND: usize = INIT_WINDOW_PACKETS * NEW_PACKET_SIZE;\n        const NEW_MAX_CWND: usize = MAX_WINDOW_PACKETS * NEW_PACKET_SIZE;\n\n        let mut bbr2 = BBRv2::new(\n            INIT_WINDOW_PACKETS,\n            MAX_WINDOW_PACKETS,\n            INIT_PACKET_SIZE,\n            initial_rtt,\n            Some(bbr_params),\n        );\n\n        assert_eq!(bbr2.cwnd_limits.lo, INIT_CWND);\n        assert_eq!(bbr2.cwnd_limits.hi, MAX_CWND);\n        assert_eq!(bbr2.cwnd, INIT_CWND);\n        assert_eq!(\n            bbr2.pacing_rate.to_bytes_per_period(initial_rtt),\n            (2.88499 * INIT_CWND as f64) as u64\n        );\n\n        bbr2.update_mss(NEW_PACKET_SIZE);\n\n        assert_eq!(bbr2.cwnd_limits.lo, NEW_CWND);\n        assert_eq!(bbr2.cwnd_limits.hi, NEW_MAX_CWND);\n        assert_eq!(bbr2.cwnd, NEW_CWND);\n        let pacing_cwnd = if scale_pacing_rate_by_mss {\n            NEW_CWND\n        } else {\n            INIT_CWND\n        };\n        assert_eq!(\n            bbr2.pacing_rate.to_bytes_per_period(initial_rtt),\n            (2.88499 * pacing_cwnd as f64) as u64\n        );\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/mod.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod bbr;\nmod bbr2;\npub mod pacer;\nmod recovery;\n\nuse std::fmt::Debug;\nuse std::str::FromStr;\nuse std::time::Instant;\n\npub use self::recovery::GRecovery;\nuse crate::recovery::bandwidth::Bandwidth;\n\nuse crate::recovery::rtt::RttStats;\nuse crate::recovery::RecoveryStats;\n\n#[derive(Debug)]\npub struct Lost {\n    pub(super) packet_number: u64,\n    pub(super) bytes_lost: usize,\n}\n\n#[derive(Debug)]\npub struct Acked {\n    pub(super) pkt_num: u64,\n    pub(super) time_sent: Instant,\n}\n\npub(super) trait CongestionControl: Debug {\n    /// Returns the name of the current state of the congestion control state\n    /// machine. Used to annotate qlogs after state transitions.\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self) -> &'static str;\n\n    /// Returns the size of the current congestion window in bytes. Note, this\n    /// is not the *available* window. Some send algorithms may not use a\n    /// congestion window and will return 0.\n    fn get_congestion_window(&self) -> usize;\n\n    /// Returns the size of the current congestion window in packets. Note, this\n    /// is not the *available* window. Some send algorithms may not use a\n    /// congestion window and will return 0.\n    fn get_congestion_window_in_packets(&self) -> usize;\n\n    /// Make decision on whether the sender can send right now.  Note that even\n    /// when this method returns true, the sending can be delayed due to pacing.\n    fn can_send(&self, bytes_in_flight: usize) -> bool;\n\n    /// Inform that we sent `bytes` to the wire, and if the packet is\n    /// retransmittable. `bytes_in_flight` is the number of bytes in flight\n    /// before the packet was sent. Note: this function must be called for\n    /// every packet sent to the wire.\n    fn on_packet_sent(\n        &mut self, sent_time: Instant, bytes_in_flight: usize,\n        packet_number: u64, bytes: usize, is_retransmissible: bool,\n    );\n\n    /// Inform that `packet_number` has been neutered.\n    fn on_packet_neutered(&mut self, _packet_number: u64) {}\n\n    /// Indicates an update to the congestion state, caused either by an\n    /// incoming ack or loss event timeout. `rtt_updated` indicates whether a\n    /// new `latest_rtt` sample has been taken, `prior_in_flight` the bytes in\n    /// flight prior to the congestion event. `acked_packets` and `lost_packets`\n    /// are any packets considered acked or lost as a result of the\n    /// congestion event.\n    #[allow(clippy::too_many_arguments)]\n    fn on_congestion_event(\n        &mut self, rtt_updated: bool, prior_in_flight: usize,\n        bytes_in_flight: usize, event_time: Instant, acked_packets: &[Acked],\n        lost_packets: &[Lost], least_unacked: u64, rtt_stats: &RttStats,\n        recovery_stats: &mut RecoveryStats,\n    );\n\n    /// Called when an RTO fires.  Resets the retransmission alarm if there are\n    /// remaining unacked packets.\n    fn on_retransmission_timeout(&mut self, packets_retransmitted: bool);\n\n    /// Called when connection migrates and cwnd needs to be reset.\n    #[allow(dead_code)]\n    fn on_connection_migration(&mut self);\n\n    /// Adjust the current cwnd to a new maximal size\n    fn limit_cwnd(&mut self, _max_cwnd: usize) {}\n\n    fn is_in_recovery(&self) -> bool;\n\n    #[allow(dead_code)]\n    fn is_cwnd_limited(&self, bytes_in_flight: usize) -> bool;\n\n    fn pacing_rate(\n        &self, bytes_in_flight: usize, rtt_stats: &RttStats,\n    ) -> Bandwidth;\n\n    fn bandwidth_estimate(&self, rtt_stats: &RttStats) -> Bandwidth;\n\n    fn max_bandwidth(&self) -> Bandwidth;\n\n    fn update_mss(&mut self, new_mss: usize);\n\n    fn on_app_limited(&mut self, _bytes_in_flight: usize) {}\n\n    #[cfg(feature = \"qlog\")]\n    fn ssthresh(&self) -> Option<u64> {\n        None\n    }\n}\n\n/// BBR settings used to customize the algorithm's behavior.\n///\n/// This functionality is experimental and will be removed in the future.\n///\n/// A congestion control algorithm has dual-responsibility of effective network\n/// utilization and avoiding congestion. Custom values should be choosen\n/// carefully since incorrect values can lead to network degradation for all\n/// connections on the shared network.\n#[derive(Debug, Default, Copy, Clone, PartialEq)]\n#[repr(C)]\n#[doc(hidden)]\npub struct BbrParams {\n    /// Controls the BBR startup gain.\n    pub startup_cwnd_gain: Option<f32>,\n\n    /// Controls the BBR startup pacing gain.\n    pub startup_pacing_gain: Option<f32>,\n\n    /// Controls the BBR full bandwidth threshold.\n    pub full_bw_threshold: Option<f32>,\n\n    /// Controls the number of rounds to stay in STARTUP before\n    /// exiting due to bandwidth plateau.\n    pub startup_full_bw_rounds: Option<usize>,\n\n    /// Controls the BBR startup loss count necessary to exit startup.\n    pub startup_full_loss_count: Option<usize>,\n\n    /// Controls the BBR drain cwnd gain.\n    pub drain_cwnd_gain: Option<f32>,\n\n    /// Controls the BBR drain pacing gain.\n    pub drain_pacing_gain: Option<f32>,\n\n    /// Controls if BBR should respect Reno coexistence.\n    pub enable_reno_coexistence: Option<bool>,\n\n    /// Controls if BBR should enable code in Bandwidth Sampler that\n    /// attempts to avoid overestimating bandwidth on ack compression.\n    pub enable_overestimate_avoidance: Option<bool>,\n\n    /// Controls if BBR should enable a possible fix in Bandwidth\n    /// Sampler that attempts to bandwidth over estimation avoidance.\n    pub choose_a0_point_fix: Option<bool>,\n\n    /// Controls the BBR bandwidth probe up pacing gain.\n    pub probe_bw_probe_up_pacing_gain: Option<f32>,\n\n    /// Controls the BBR bandwidth probe down pacing gain.\n    pub probe_bw_probe_down_pacing_gain: Option<f32>,\n\n    /// Controls the BBR probe bandwidth DOWN/CRUISE/REFILL cwnd gain.\n    pub probe_bw_cwnd_gain: Option<f32>,\n\n    /// Controls the BBR probe bandwidth UP cwnd gain.\n    pub probe_bw_up_cwnd_gain: Option<f32>,\n\n    /// Controls the BBR probe RTT pacing gain.\n    pub probe_rtt_pacing_gain: Option<f32>,\n\n    /// Controls the BBR probe RTT cwnd gain.\n    pub probe_rtt_cwnd_gain: Option<f32>,\n\n    /// Controls the number of rounds BBR should stay in probe up if\n    /// bytes_in_flight doesn't drop below target.\n    pub max_probe_up_queue_rounds: Option<usize>,\n\n    /// Controls the BBR loss threshold.\n    pub loss_threshold: Option<f32>,\n\n    /// Controls if BBR should use bytes delievered as an estimate for\n    /// inflight_hi.\n    pub use_bytes_delivered_for_inflight_hi: Option<bool>,\n\n    /// Controls if BBR should adjust startup pacing at round end.\n    pub decrease_startup_pacing_at_end_of_round: Option<bool>,\n\n    /// Controls the BBR bandwidth lo reduction strategy.\n    pub bw_lo_reduction_strategy: Option<BbrBwLoReductionStrategy>,\n\n    /// Determines whether app limited rounds with no bandwidth growth count\n    /// towards the rounds threshold to exit startup.\n    pub ignore_app_limited_for_no_bandwidth_growth: Option<bool>,\n\n    /// Initial pacing rate for a new connection before an RTT\n    /// estimate is available.  This rate serves as an upper bound on\n    /// the initial pacing rate, which is calculated by dividing the\n    /// initial cwnd by the first RTT estimate.\n    pub initial_pacing_rate_bytes_per_second: Option<u64>,\n\n    /// If true, scale the pacing rate when updating mss when doing pmtud.\n    pub scale_pacing_rate_by_mss: Option<bool>,\n\n    /// Disable `has_stayed_long_enough_in_probe_down` which can cause ProbeDown\n    /// to exit early.\n    pub disable_probe_down_early_exit: Option<bool>,\n\n    /// Set the expected send time for packets when using BBR to `now`\n    /// instead of `get_next_release_time()`.  Setting the time based\n    /// on `get_next_release_time()` can result in artificially low\n    /// minRTT measurements which will make BBR misbehave.\n    pub time_sent_set_to_now: Option<bool>,\n}\n\n/// Controls BBR's bandwidth reduction strategy on congestion event.\n///\n/// This functionality is experimental and will be removed in the future.\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n#[repr(C)]\n#[doc(hidden)]\npub enum BbrBwLoReductionStrategy {\n    /// Uses the default strategy based on `BBRBeta`.\n    Default           = 0,\n\n    /// Considers min-rtt to estimate bandwidth reduction.\n    MinRttReduction   = 1,\n\n    /// Considers inflight data to estimate bandwidth reduction.\n    InflightReduction = 2,\n\n    /// Considers cwnd to estimate bandwidth reduction.\n    CwndReduction     = 3,\n}\n\n#[doc(hidden)]\nimpl FromStr for BbrBwLoReductionStrategy {\n    type Err = crate::Error;\n\n    /// Converts a string to `BbrBwLoReductionStrategy`.\n    ///\n    /// If `name` is not valid, `Error::CongestionControl` is returned.\n    fn from_str(name: &str) -> Result<Self, Self::Err> {\n        match name {\n            \"default\" => Ok(BbrBwLoReductionStrategy::Default),\n            \"minrtt\" => Ok(BbrBwLoReductionStrategy::MinRttReduction),\n            \"inflight\" => Ok(BbrBwLoReductionStrategy::InflightReduction),\n            \"cwnd\" => Ok(BbrBwLoReductionStrategy::CwndReduction),\n\n            _ => Err(crate::Error::CongestionControl),\n        }\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/pacer.rs",
    "content": "// Copyright (c) 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Instant;\n\nuse crate::recovery::gcongestion::bbr2::BBRv2;\nuse crate::recovery::gcongestion::Bandwidth;\nuse crate::recovery::gcongestion::CongestionControl;\nuse crate::recovery::rtt::RttStats;\nuse crate::recovery::RecoveryStats;\nuse crate::recovery::ReleaseDecision;\nuse crate::recovery::ReleaseTime;\n\nuse super::Acked;\nuse super::Lost;\n\n/// Congestion window fraction that the pacing sender allows in bursts during\n/// pacing.\nconst LUMPY_PACING_CWND_FRACTION: f64 = 0.25;\n\n/// Number of packets that the pacing sender allows in bursts during pacing.\n/// This is ignored if a flow's estimated bandwidth is lower than 1200 kbps.\nconst LUMPY_PACING_SIZE: usize = 2;\n\n/// The minimum estimated client bandwidth below which the pacing sender will\n/// not allow bursts.\nconst LUMPY_PACING_MIN_BANDWIDTH_KBPS: Bandwidth =\n    Bandwidth::from_kbits_per_second(1_200);\n\n/// Configured maximum size of the burst coming out of quiescence.  The burst is\n/// never larger than the current CWND in packets.\nconst INITIAL_UNPACED_BURST: usize = 10;\n\n#[derive(Debug)]\npub struct Pacer {\n    /// Should this [`Pacer`] be making any release decisions?\n    enabled: bool,\n    /// Underlying sender\n    sender: BBRv2,\n    /// The maximum rate the [`Pacer`] will use.\n    max_pacing_rate: Option<Bandwidth>,\n    /// Number of unpaced packets to be sent before packets are delayed.\n    burst_tokens: usize,\n    /// When can the next packet be sent.\n    ideal_next_packet_send_time: ReleaseTime,\n    initial_burst_size: usize,\n    /// Number of unpaced packets to be sent before packets are delayed. This\n    /// token is consumed after [`Self::burst_tokens`] ran out.\n    lumpy_tokens: usize,\n    /// Indicates whether pacing throttles the sending. If true, make up for\n    /// lost time.\n    pacing_limited: bool,\n}\n\nimpl Pacer {\n    /// Create a new [`Pacer`] with and underlying [`Congestion`]\n    /// implementation, and an optional throttling as specified by\n    /// `max_pacing_rate`.\n    pub(crate) fn new(\n        enabled: bool, congestion: BBRv2, max_pacing_rate: Option<Bandwidth>,\n    ) -> Self {\n        Pacer {\n            enabled,\n            sender: congestion,\n            max_pacing_rate,\n            burst_tokens: INITIAL_UNPACED_BURST,\n            ideal_next_packet_send_time: ReleaseTime::Immediate,\n            initial_burst_size: INITIAL_UNPACED_BURST,\n            lumpy_tokens: 0,\n            pacing_limited: false,\n        }\n    }\n\n    pub fn get_next_release_time(&self) -> ReleaseDecision {\n        if !self.enabled {\n            return ReleaseDecision {\n                time: ReleaseTime::Immediate,\n                allow_burst: true,\n            };\n        }\n\n        let allow_burst = self.burst_tokens > 0 || self.lumpy_tokens > 0;\n        ReleaseDecision {\n            time: self.ideal_next_packet_send_time,\n            allow_burst,\n        }\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub fn state_str(&self) -> &'static str {\n        self.sender.state_str()\n    }\n\n    pub fn get_congestion_window(&self) -> usize {\n        self.sender.get_congestion_window()\n    }\n\n    pub fn on_packet_sent(\n        &mut self, sent_time: Instant, bytes_in_flight: usize,\n        packet_number: u64, bytes: usize, is_retransmissible: bool,\n        rtt_stats: &RttStats,\n    ) {\n        self.sender.on_packet_sent(\n            sent_time,\n            bytes_in_flight,\n            packet_number,\n            bytes,\n            is_retransmissible,\n        );\n\n        if !self.enabled || !is_retransmissible {\n            return;\n        }\n\n        // If in recovery, the connection is not coming out of quiescence.\n        if bytes_in_flight == 0 && !self.sender.is_in_recovery() {\n            // Add more burst tokens anytime the connection is leaving quiescence,\n            // but limit it to the equivalent of a single bulk write,\n            // not exceeding the current CWND in packets.\n            self.burst_tokens = self\n                .initial_burst_size\n                .min(self.sender.get_congestion_window_in_packets());\n        }\n\n        if self.burst_tokens > 0 {\n            self.burst_tokens -= 1;\n            self.ideal_next_packet_send_time = ReleaseTime::Immediate;\n            self.pacing_limited = false;\n            return;\n        }\n\n        // The next packet should be sent as soon as the current packet has been\n        // transferred. PacingRate is based on bytes in flight including this\n        // packet.\n        let delay = self\n            .pacing_rate(bytes_in_flight + bytes, rtt_stats)\n            .transfer_time(bytes);\n\n        if !self.pacing_limited || self.lumpy_tokens == 0 {\n            // Reset lumpy_tokens_ if either application or cwnd throttles sending\n            // or token runs out.\n            self.lumpy_tokens = 1.max(LUMPY_PACING_SIZE.min(\n                (self.sender.get_congestion_window_in_packets() as f64 *\n                    LUMPY_PACING_CWND_FRACTION) as usize,\n            ));\n\n            if self.sender.bandwidth_estimate(rtt_stats) <\n                LUMPY_PACING_MIN_BANDWIDTH_KBPS\n            {\n                // Below 1.2Mbps, send 1 packet at once, because one full-sized\n                // packet is about 10ms of queueing.\n                self.lumpy_tokens = 1;\n            }\n\n            if bytes_in_flight + bytes >= self.sender.get_congestion_window() {\n                // Don't add lumpy_tokens if the congestion controller is CWND\n                // limited.\n                self.lumpy_tokens = 1;\n            }\n        }\n\n        self.lumpy_tokens -= 1;\n        self.ideal_next_packet_send_time.set_max(sent_time);\n        self.ideal_next_packet_send_time.inc(delay);\n        // Stop making up for lost time if underlying sender prevents sending.\n        self.pacing_limited = self.sender.can_send(bytes_in_flight + bytes);\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    #[inline]\n    pub fn on_congestion_event(\n        &mut self, rtt_updated: bool, prior_in_flight: usize,\n        bytes_in_flight: usize, event_time: Instant, acked_packets: &[Acked],\n        lost_packets: &[Lost], least_unacked: u64, rtt_stats: &RttStats,\n        recovery_stats: &mut RecoveryStats,\n    ) {\n        self.sender.on_congestion_event(\n            rtt_updated,\n            prior_in_flight,\n            bytes_in_flight,\n            event_time,\n            acked_packets,\n            lost_packets,\n            least_unacked,\n            rtt_stats,\n            recovery_stats,\n        );\n\n        if !self.enabled {\n            return;\n        }\n\n        if !lost_packets.is_empty() {\n            // Clear any burst tokens when entering recovery.\n            self.burst_tokens = 0;\n        }\n\n        if let Some(max_pacing_rate) = self.max_pacing_rate {\n            if rtt_updated {\n                let max_rate = max_pacing_rate * 1.25f32;\n                let max_cwnd =\n                    max_rate.to_bytes_per_period(rtt_stats.smoothed_rtt);\n                self.sender.limit_cwnd(max_cwnd as usize);\n            }\n        }\n    }\n\n    pub fn on_packet_neutered(&mut self, packet_number: u64) {\n        self.sender.on_packet_neutered(packet_number);\n    }\n\n    pub fn on_retransmission_timeout(&mut self, packets_retransmitted: bool) {\n        self.sender.on_retransmission_timeout(packets_retransmitted)\n    }\n\n    pub fn pacing_rate(\n        &self, bytes_in_flight: usize, rtt_stats: &RttStats,\n    ) -> Bandwidth {\n        let sender_rate = self.sender.pacing_rate(bytes_in_flight, rtt_stats);\n        match self.max_pacing_rate {\n            Some(rate) if self.enabled => rate.min(sender_rate),\n            _ => sender_rate,\n        }\n    }\n\n    pub fn bandwidth_estimate(&self, rtt_stats: &RttStats) -> Bandwidth {\n        self.sender.bandwidth_estimate(rtt_stats)\n    }\n\n    pub fn max_bandwidth(&self) -> Bandwidth {\n        self.sender.max_bandwidth()\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub fn send_rate(&self) -> Option<Bandwidth> {\n        self.sender.send_rate()\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub fn ack_rate(&self) -> Option<Bandwidth> {\n        self.sender.ack_rate()\n    }\n\n    pub fn on_app_limited(&mut self, bytes_in_flight: usize) {\n        self.pacing_limited = false;\n        self.sender.on_app_limited(bytes_in_flight);\n    }\n\n    pub fn update_mss(&mut self, new_mss: usize) {\n        self.sender.update_mss(new_mss)\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub fn ssthresh(&self) -> Option<u64> {\n        self.sender.ssthresh()\n    }\n\n    #[cfg(test)]\n    pub fn is_app_limited(&self, bytes_in_flight: usize) -> bool {\n        !self.is_cwnd_limited(bytes_in_flight)\n    }\n\n    #[cfg(test)]\n    fn is_cwnd_limited(&self, bytes_in_flight: usize) -> bool {\n        !self.pacing_limited && self.sender.is_cwnd_limited(bytes_in_flight)\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/gcongestion/recovery.rs",
    "content": "use crate::packet;\nuse crate::recovery::OnLossDetectionTimeoutOutcome;\nuse crate::recovery::INITIAL_TIME_THRESHOLD_OVERHEAD;\nuse crate::recovery::TIME_THRESHOLD_OVERHEAD_MULTIPLIER;\nuse crate::Error;\nuse crate::Result;\n\nuse std::collections::VecDeque;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse smallvec::SmallVec;\n\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventData;\n\n#[cfg(feature = \"qlog\")]\nuse crate::recovery::QlogMetrics;\n\nuse crate::frame;\n\nuse crate::recovery::bytes_in_flight::BytesInFlight;\nuse crate::recovery::gcongestion::Bandwidth;\nuse crate::recovery::rtt::RttStats;\nuse crate::recovery::CongestionControlAlgorithm;\nuse crate::recovery::HandshakeStatus;\nuse crate::recovery::LossDetectionTimer;\nuse crate::recovery::OnAckReceivedOutcome;\nuse crate::recovery::RangeSet;\nuse crate::recovery::RecoveryConfig;\nuse crate::recovery::RecoveryOps;\nuse crate::recovery::RecoveryStats;\nuse crate::recovery::ReleaseDecision;\nuse crate::recovery::Sent;\nuse crate::recovery::StartupExit;\nuse crate::recovery::GRANULARITY;\nuse crate::recovery::INITIAL_PACKET_THRESHOLD;\nuse crate::recovery::INITIAL_TIME_THRESHOLD;\nuse crate::recovery::MAX_OUTSTANDING_NON_ACK_ELICITING;\nuse crate::recovery::MAX_PACKET_THRESHOLD;\nuse crate::recovery::MAX_PTO_PROBES_COUNT;\nuse crate::recovery::PACKET_REORDER_TIME_THRESHOLD;\n\nuse super::bbr2::BBRv2;\nuse super::pacer::Pacer;\nuse super::Acked;\nuse super::Lost;\n\n// Congestion Control\nconst MAX_WINDOW_PACKETS: usize = 20_000;\n\n#[derive(Debug)]\nstruct SentPacket {\n    pkt_num: u64,\n    status: SentStatus,\n}\n\n#[derive(Debug)]\nenum SentStatus {\n    Sent {\n        time_sent: Instant,\n        ack_eliciting: bool,\n        in_flight: bool,\n        has_data: bool,\n        is_pmtud_probe: bool,\n        sent_bytes: usize,\n        frames: SmallVec<[frame::Frame; 1]>,\n    },\n    Acked,\n    Lost,\n}\n\nimpl SentStatus {\n    fn ack(&mut self) -> Self {\n        std::mem::replace(self, SentStatus::Acked)\n    }\n\n    fn lose(&mut self) -> Self {\n        if !matches!(self, SentStatus::Acked) {\n            std::mem::replace(self, SentStatus::Lost)\n        } else {\n            SentStatus::Acked\n        }\n    }\n}\n\n#[derive(Default)]\nstruct RecoveryEpoch {\n    /// The time the most recent ack-eliciting packet was sent.\n    time_of_last_ack_eliciting_packet: Option<Instant>,\n\n    /// The largest packet number acknowledged in the packet number space so\n    /// far.\n    largest_acked_packet: Option<u64>,\n\n    /// The time at which the next packet in that packet number space can be\n    /// considered lost based on exceeding the reordering window in time.\n    loss_time: Option<Instant>,\n\n    /// An association of packet numbers in a packet number space to information\n    /// about them.\n    sent_packets: VecDeque<SentPacket>,\n\n    loss_probes: usize,\n    pkts_in_flight: usize,\n\n    acked_frames: VecDeque<frame::Frame>,\n    lost_frames: VecDeque<frame::Frame>,\n\n    /// The largest packet number sent in the packet number space so far.\n    #[allow(dead_code)]\n    test_largest_sent_pkt_num_on_path: Option<u64>,\n}\n\nstruct AckedDetectionResult {\n    acked_bytes: usize,\n    spurious_losses: usize,\n    spurious_pkt_thresh: Option<u64>,\n    has_ack_eliciting: bool,\n}\n\nstruct LossDetectionResult {\n    lost_bytes: usize,\n    lost_packets: usize,\n\n    pmtud_lost_bytes: usize,\n    pmtud_lost_packets: SmallVec<[u64; 1]>,\n}\n\nimpl RecoveryEpoch {\n    /// Discard the Epoch state and return the total size of unacked packets\n    /// that were discarded\n    fn discard(&mut self, cc: &mut Pacer) -> usize {\n        let unacked_bytes = self\n            .sent_packets\n            .drain(..)\n            .map(|p| {\n                if let SentPacket {\n                    status:\n                        SentStatus::Sent {\n                            in_flight,\n                            sent_bytes,\n                            ..\n                        },\n                    pkt_num,\n                } = p\n                {\n                    cc.on_packet_neutered(pkt_num);\n                    if in_flight {\n                        return sent_bytes;\n                    }\n                }\n                0\n            })\n            .sum();\n\n        std::mem::take(&mut self.sent_packets);\n        self.time_of_last_ack_eliciting_packet = None;\n        self.loss_time = None;\n        self.loss_probes = 0;\n        self.pkts_in_flight = 0;\n\n        unacked_bytes\n    }\n\n    // `peer_sent_ack_ranges` should not be used without validation.\n    fn detect_and_remove_acked_packets(\n        &mut self, peer_sent_ack_ranges: &RangeSet, newly_acked: &mut Vec<Acked>,\n        skip_pn: Option<u64>, trace_id: &str,\n    ) -> Result<AckedDetectionResult> {\n        newly_acked.clear();\n\n        let mut acked_bytes = 0;\n        let mut spurious_losses = 0;\n        let mut spurious_pkt_thresh = None;\n        let mut has_ack_eliciting = false;\n\n        let largest_ack_received = peer_sent_ack_ranges.last().unwrap();\n        let largest_acked = self\n            .largest_acked_packet\n            .unwrap_or(0)\n            .max(largest_ack_received);\n\n        for peer_sent_range in peer_sent_ack_ranges.iter() {\n            if skip_pn.is_some_and(|skip_pn| peer_sent_range.contains(&skip_pn)) {\n                // https://www.rfc-editor.org/rfc/rfc9000#section-13.1\n                // An endpoint SHOULD treat receipt of an acknowledgment\n                // for a packet it did not send as\n                // a connection error of type PROTOCOL_VIOLATION\n                return Err(Error::OptimisticAckDetected);\n            }\n\n            // Because packets always have incrementing numbers, they are always\n            // in sorted order.\n            let start = if self\n                .sent_packets\n                .front()\n                .filter(|e| e.pkt_num >= peer_sent_range.start)\n                .is_some()\n            {\n                // Usually it will be the first packet.\n                0\n            } else {\n                self.sent_packets\n                    .binary_search_by_key(&peer_sent_range.start, |p| p.pkt_num)\n                    .unwrap_or_else(|e| e)\n            };\n\n            for SentPacket { pkt_num, status } in\n                self.sent_packets.range_mut(start..)\n            {\n                if *pkt_num < peer_sent_range.end {\n                    match status.ack() {\n                        SentStatus::Sent {\n                            time_sent,\n                            in_flight,\n                            sent_bytes,\n                            frames,\n                            ack_eliciting,\n                            ..\n                        } => {\n                            if in_flight {\n                                self.pkts_in_flight -= 1;\n                                acked_bytes += sent_bytes;\n                            }\n                            newly_acked.push(Acked {\n                                pkt_num: *pkt_num,\n                                time_sent,\n                            });\n\n                            self.acked_frames.extend(frames);\n\n                            has_ack_eliciting |= ack_eliciting;\n\n                            trace!(\"{trace_id} packet newly acked {pkt_num}\");\n                        },\n\n                        SentStatus::Acked => {},\n                        SentStatus::Lost => {\n                            // An acked packet was already declared lost\n                            spurious_losses += 1;\n                            spurious_pkt_thresh\n                                .get_or_insert(largest_acked - *pkt_num + 1);\n                        },\n                    }\n                } else {\n                    break;\n                }\n            }\n        }\n\n        self.drain_acked_and_lost_packets();\n\n        Ok(AckedDetectionResult {\n            acked_bytes,\n            spurious_losses,\n            spurious_pkt_thresh,\n            has_ack_eliciting,\n        })\n    }\n\n    fn detect_and_remove_lost_packets(\n        &mut self, loss_delay: Duration, pkt_thresh: Option<u64>, now: Instant,\n        newly_lost: &mut Vec<Lost>,\n    ) -> LossDetectionResult {\n        newly_lost.clear();\n        let mut lost_bytes = 0;\n        self.loss_time = None;\n\n        let lost_send_time = now.checked_sub(loss_delay).unwrap();\n        let largest_acked = self.largest_acked_packet.unwrap_or(0);\n        let mut pmtud_lost_bytes = 0;\n        let mut pmtud_lost_packets = SmallVec::new();\n\n        for SentPacket { pkt_num, status } in &mut self.sent_packets {\n            if *pkt_num > largest_acked {\n                break;\n            }\n\n            if let SentStatus::Sent { time_sent, .. } = status {\n                let loss_by_time = *time_sent <= lost_send_time;\n                let loss_by_pkt = match pkt_thresh {\n                    Some(pkt_thresh) => largest_acked >= *pkt_num + pkt_thresh,\n                    None => false,\n                };\n\n                if loss_by_time || loss_by_pkt {\n                    if let SentStatus::Sent {\n                        in_flight,\n                        sent_bytes,\n                        frames,\n                        is_pmtud_probe,\n                        ..\n                    } = status.lose()\n                    {\n                        self.lost_frames.extend(frames);\n\n                        if in_flight {\n                            self.pkts_in_flight -= 1;\n\n                            if is_pmtud_probe {\n                                pmtud_lost_bytes += sent_bytes;\n                                pmtud_lost_packets.push(*pkt_num);\n                                // Do not track PMTUD probes losses\n                                continue;\n                            }\n\n                            lost_bytes += sent_bytes;\n                        }\n\n                        newly_lost.push(Lost {\n                            packet_number: *pkt_num,\n                            bytes_lost: sent_bytes,\n                        });\n                    }\n                } else {\n                    self.loss_time = Some(*time_sent + loss_delay);\n                    break;\n                }\n            }\n        }\n\n        LossDetectionResult {\n            lost_bytes,\n            lost_packets: newly_lost.len(),\n\n            pmtud_lost_bytes,\n            pmtud_lost_packets,\n        }\n    }\n\n    /// Remove packets that were already handled from the front of the queue,\n    /// but avoid removing packets from the middle of the queue to avoid\n    /// compaction\n    fn drain_acked_and_lost_packets(&mut self) {\n        while let Some(SentPacket {\n            status: SentStatus::Acked | SentStatus::Lost,\n            ..\n        }) = self.sent_packets.front()\n        {\n            self.sent_packets.pop_front();\n        }\n    }\n\n    fn least_unacked(&self) -> u64 {\n        for pkt in self.sent_packets.iter() {\n            if let SentPacket {\n                pkt_num,\n                status: SentStatus::Sent { .. },\n            } = pkt\n            {\n                return *pkt_num;\n            }\n        }\n\n        self.largest_acked_packet.unwrap_or(0) + 1\n    }\n}\n\nstruct LossThreshold {\n    pkt_thresh: Option<u64>,\n    time_thresh: f64,\n\n    // # Experiment: enable_relaxed_loss_threshold\n    //\n    // If `Some` this will disable pkt_thresh on the first loss and then double\n    // time_thresh on subsequent loss.\n    //\n    // The actual threshold is calcualted as `1.0 +\n    // INITIAL_TIME_THRESHOLD_OVERHEAD` and equivalent to the initial value\n    // of INITIAL_TIME_THRESHOLD.\n    time_thresh_overhead: Option<f64>,\n}\n\nimpl LossThreshold {\n    fn new(recovery_config: &RecoveryConfig) -> Self {\n        let time_thresh_overhead =\n            if recovery_config.enable_relaxed_loss_threshold {\n                Some(INITIAL_TIME_THRESHOLD_OVERHEAD)\n            } else {\n                None\n            };\n        LossThreshold {\n            pkt_thresh: Some(INITIAL_PACKET_THRESHOLD),\n            time_thresh: INITIAL_TIME_THRESHOLD,\n            time_thresh_overhead,\n        }\n    }\n\n    fn pkt_thresh(&self) -> Option<u64> {\n        self.pkt_thresh\n    }\n\n    fn time_thresh(&self) -> f64 {\n        self.time_thresh\n    }\n\n    fn on_spurious_loss(&mut self, new_pkt_thresh: u64) {\n        match &mut self.time_thresh_overhead {\n            Some(time_thresh_overhead) => {\n                if self.pkt_thresh.is_some() {\n                    // Disable packet threshold on first spurious loss.\n                    self.pkt_thresh = None;\n                } else {\n                    // Double time threshold but cap it at `1.0`, which ends up\n                    // being 2x the RTT.\n                    *time_thresh_overhead *= TIME_THRESHOLD_OVERHEAD_MULTIPLIER;\n                    *time_thresh_overhead = time_thresh_overhead.min(1.0);\n\n                    self.time_thresh = 1.0 + *time_thresh_overhead;\n                }\n            },\n            None => {\n                let new_packet_threshold = self\n                    .pkt_thresh\n                    .expect(\"packet threshold should always be Some when `enable_relaxed_loss_threshold` is false\")\n                    .max(new_pkt_thresh.min(MAX_PACKET_THRESHOLD));\n                self.pkt_thresh = Some(new_packet_threshold);\n\n                self.time_thresh = PACKET_REORDER_TIME_THRESHOLD;\n            },\n        }\n    }\n}\n\npub struct GRecovery {\n    epochs: [RecoveryEpoch; packet::Epoch::count()],\n\n    loss_timer: LossDetectionTimer,\n\n    pto_count: u32,\n\n    rtt_stats: RttStats,\n\n    recovery_stats: RecoveryStats,\n\n    pub lost_count: usize,\n\n    pub lost_spurious_count: usize,\n\n    loss_thresh: LossThreshold,\n\n    bytes_in_flight: BytesInFlight,\n\n    bytes_sent: usize,\n\n    pub bytes_lost: u64,\n\n    max_datagram_size: usize,\n    time_sent_set_to_now: bool,\n\n    #[cfg(feature = \"qlog\")]\n    qlog_metrics: QlogMetrics,\n\n    #[cfg(feature = \"qlog\")]\n    qlog_prev_cc_state: &'static str,\n\n    /// How many non-ack-eliciting packets have been sent.\n    outstanding_non_ack_eliciting: usize,\n\n    /// A resusable list of acks.\n    newly_acked: Vec<Acked>,\n\n    /// A [`Vec`] that can be reused for calls of\n    /// [`Self::detect_and_remove_lost_packets`] to avoid allocations\n    lost_reuse: Vec<Lost>,\n\n    pacer: Pacer,\n}\n\nimpl GRecovery {\n    #[cfg(feature = \"qlog\")]\n    fn send_rate(&self) -> Bandwidth {\n        self.pacer.send_rate().unwrap_or(Bandwidth::zero())\n    }\n\n    #[cfg(feature = \"qlog\")]\n    fn ack_rate(&self) -> Bandwidth {\n        self.pacer.ack_rate().unwrap_or(Bandwidth::zero())\n    }\n\n    pub fn new(recovery_config: &RecoveryConfig) -> Option<Self> {\n        let cc = match recovery_config.cc_algorithm {\n            CongestionControlAlgorithm::Bbr2Gcongestion => BBRv2::new(\n                recovery_config.initial_congestion_window_packets,\n                MAX_WINDOW_PACKETS,\n                recovery_config.max_send_udp_payload_size,\n                recovery_config.initial_rtt,\n                recovery_config.custom_bbr_params.as_ref(),\n            ),\n            _ => return None,\n        };\n\n        Some(Self {\n            epochs: Default::default(),\n            rtt_stats: RttStats::new(\n                recovery_config.initial_rtt,\n                recovery_config.max_ack_delay,\n            ),\n            recovery_stats: RecoveryStats::default(),\n            loss_timer: Default::default(),\n            pto_count: 0,\n\n            lost_count: 0,\n            lost_spurious_count: 0,\n\n            loss_thresh: LossThreshold::new(recovery_config),\n            bytes_in_flight: Default::default(),\n            bytes_sent: 0,\n            bytes_lost: 0,\n\n            max_datagram_size: recovery_config.max_send_udp_payload_size,\n            time_sent_set_to_now: cc.time_sent_set_to_now(),\n\n            #[cfg(feature = \"qlog\")]\n            qlog_metrics: QlogMetrics::default(),\n\n            #[cfg(feature = \"qlog\")]\n            qlog_prev_cc_state: \"\",\n\n            outstanding_non_ack_eliciting: 0,\n\n            pacer: Pacer::new(\n                recovery_config.pacing,\n                cc,\n                recovery_config\n                    .max_pacing_rate\n                    .map(Bandwidth::from_mbits_per_second),\n            ),\n\n            newly_acked: Vec::new(),\n            lost_reuse: Vec::new(),\n        })\n    }\n\n    fn detect_and_remove_lost_packets(\n        &mut self, epoch: packet::Epoch, now: Instant,\n    ) -> (usize, usize) {\n        let loss_delay =\n            self.rtt_stats.loss_delay(self.loss_thresh.time_thresh());\n        let lost = &mut self.lost_reuse;\n\n        let LossDetectionResult {\n            lost_bytes,\n            lost_packets,\n            pmtud_lost_bytes,\n            pmtud_lost_packets,\n        } = self.epochs[epoch].detect_and_remove_lost_packets(\n            loss_delay,\n            self.loss_thresh.pkt_thresh(),\n            now,\n            lost,\n        );\n\n        self.bytes_in_flight\n            .saturating_subtract(lost_bytes + pmtud_lost_bytes, now);\n\n        for pkt in pmtud_lost_packets {\n            self.pacer.on_packet_neutered(pkt);\n        }\n\n        (lost_bytes, lost_packets)\n    }\n\n    fn loss_time_and_space(&self) -> (Option<Instant>, packet::Epoch) {\n        let mut epoch = packet::Epoch::Initial;\n        let mut time = self.epochs[epoch].loss_time;\n\n        // Iterate over all packet number spaces starting from Handshake.\n        for e in [packet::Epoch::Handshake, packet::Epoch::Application] {\n            let new_time = self.epochs[e].loss_time;\n            if time.is_none() || new_time < time {\n                time = new_time;\n                epoch = e;\n            }\n        }\n\n        (time, epoch)\n    }\n\n    fn pto_time_and_space(\n        &self, handshake_status: HandshakeStatus, now: Instant,\n    ) -> (Option<Instant>, packet::Epoch) {\n        let mut duration = self.pto() * (1 << self.pto_count);\n\n        // Arm PTO from now when there are no inflight packets.\n        if self.bytes_in_flight.is_zero() {\n            if handshake_status.has_handshake_keys {\n                return (Some(now + duration), packet::Epoch::Handshake);\n            } else {\n                return (Some(now + duration), packet::Epoch::Initial);\n            }\n        }\n\n        let mut pto_timeout = None;\n        let mut pto_space = packet::Epoch::Initial;\n\n        // Iterate over all packet number spaces.\n        for &e in packet::Epoch::epochs(\n            packet::Epoch::Initial..=packet::Epoch::Application,\n        ) {\n            if self.epochs[e].pkts_in_flight == 0 {\n                continue;\n            }\n\n            if e == packet::Epoch::Application {\n                // Skip Application Data until handshake completes.\n                if !handshake_status.completed {\n                    return (pto_timeout, pto_space);\n                }\n\n                // Include max_ack_delay and backoff for Application Data.\n                duration +=\n                    self.rtt_stats.max_ack_delay * 2_u32.pow(self.pto_count);\n            }\n\n            let new_time = self.epochs[e]\n                .time_of_last_ack_eliciting_packet\n                .map(|t| t + duration);\n\n            if pto_timeout.is_none() || new_time < pto_timeout {\n                pto_timeout = new_time;\n                pto_space = e;\n            }\n        }\n\n        (pto_timeout, pto_space)\n    }\n\n    fn set_loss_detection_timer(\n        &mut self, handshake_status: HandshakeStatus, now: Instant,\n    ) {\n        if let (Some(earliest_loss_time), _) = self.loss_time_and_space() {\n            // Time threshold loss detection.\n            self.loss_timer.update(earliest_loss_time);\n            return;\n        }\n\n        if self.bytes_in_flight.is_zero() &&\n            handshake_status.peer_verified_address\n        {\n            self.loss_timer.clear();\n            return;\n        }\n\n        // PTO timer.\n        if let (Some(timeout), _) = self.pto_time_and_space(handshake_status, now)\n        {\n            self.loss_timer.update(timeout);\n        }\n    }\n}\n\nimpl RecoveryOps for GRecovery {\n    fn lost_count(&self) -> usize {\n        self.lost_count\n    }\n\n    fn bytes_lost(&self) -> u64 {\n        self.bytes_lost\n    }\n\n    fn should_elicit_ack(&self, epoch: packet::Epoch) -> bool {\n        self.epochs[epoch].loss_probes > 0 ||\n            self.outstanding_non_ack_eliciting >=\n                MAX_OUTSTANDING_NON_ACK_ELICITING\n    }\n\n    fn next_acked_frame(&mut self, epoch: packet::Epoch) -> Option<frame::Frame> {\n        self.epochs[epoch].acked_frames.pop_front()\n    }\n\n    fn next_lost_frame(&mut self, epoch: packet::Epoch) -> Option<frame::Frame> {\n        self.epochs[epoch].lost_frames.pop_front()\n    }\n\n    fn get_largest_acked_on_epoch(&self, epoch: packet::Epoch) -> Option<u64> {\n        self.epochs[epoch].largest_acked_packet\n    }\n\n    fn has_lost_frames(&self, epoch: packet::Epoch) -> bool {\n        !self.epochs[epoch].lost_frames.is_empty()\n    }\n\n    fn loss_probes(&self, epoch: packet::Epoch) -> usize {\n        self.epochs[epoch].loss_probes\n    }\n\n    #[cfg(test)]\n    fn inc_loss_probes(&mut self, epoch: packet::Epoch) {\n        self.epochs[epoch].loss_probes += 1;\n    }\n\n    fn ping_sent(&mut self, epoch: packet::Epoch) {\n        self.epochs[epoch].loss_probes =\n            self.epochs[epoch].loss_probes.saturating_sub(1);\n    }\n\n    fn on_packet_sent(\n        &mut self, pkt: Sent, epoch: packet::Epoch,\n        handshake_status: HandshakeStatus, now: Instant, trace_id: &str,\n    ) {\n        let time_sent = if self.time_sent_set_to_now {\n            now\n        } else {\n            self.get_next_release_time().time(now).unwrap_or(now)\n        };\n\n        let epoch = &mut self.epochs[epoch];\n\n        let ack_eliciting = pkt.ack_eliciting;\n        let in_flight = pkt.in_flight;\n        let is_pmtud_probe = pkt.is_pmtud_probe;\n        let pkt_num = pkt.pkt_num;\n        let sent_bytes = pkt.size;\n\n        if let Some(SentPacket { pkt_num, .. }) = epoch.sent_packets.back() {\n            assert!(*pkt_num < pkt.pkt_num, \"Packet numbers must increase\");\n        }\n\n        let status = SentStatus::Sent {\n            time_sent,\n            ack_eliciting,\n            in_flight,\n            is_pmtud_probe,\n            has_data: pkt.has_data,\n            sent_bytes,\n            frames: pkt.frames,\n        };\n\n        #[cfg(test)]\n        {\n            epoch.test_largest_sent_pkt_num_on_path = epoch\n                .test_largest_sent_pkt_num_on_path\n                .max(Some(pkt.pkt_num));\n        }\n\n        epoch.sent_packets.push_back(SentPacket { pkt_num, status });\n\n        if ack_eliciting {\n            epoch.time_of_last_ack_eliciting_packet = Some(time_sent);\n            self.outstanding_non_ack_eliciting = 0;\n        } else {\n            self.outstanding_non_ack_eliciting += 1;\n        }\n\n        if in_flight {\n            self.pacer.on_packet_sent(\n                time_sent,\n                self.bytes_in_flight.get(),\n                pkt_num,\n                sent_bytes,\n                pkt.has_data,\n                &self.rtt_stats,\n            );\n\n            self.bytes_in_flight.add(sent_bytes, now);\n            epoch.pkts_in_flight += 1;\n            self.set_loss_detection_timer(handshake_status, time_sent);\n        }\n\n        self.bytes_sent += sent_bytes;\n\n        trace!(\"{trace_id} {self:?}\");\n    }\n\n    fn get_packet_send_time(&self, now: Instant) -> Instant {\n        self.pacer.get_next_release_time().time(now).unwrap_or(now)\n    }\n\n    // `peer_sent_ack_ranges` should not be used without validation.\n    fn on_ack_received(\n        &mut self, peer_sent_ack_ranges: &RangeSet, ack_delay: u64,\n        epoch: packet::Epoch, handshake_status: HandshakeStatus, now: Instant,\n        skip_pn: Option<u64>, trace_id: &str,\n    ) -> Result<OnAckReceivedOutcome> {\n        let prior_in_flight = self.bytes_in_flight.get();\n\n        let AckedDetectionResult {\n            acked_bytes,\n            spurious_losses,\n            spurious_pkt_thresh,\n            has_ack_eliciting,\n        } = self.epochs[epoch].detect_and_remove_acked_packets(\n            peer_sent_ack_ranges,\n            &mut self.newly_acked,\n            skip_pn,\n            trace_id,\n        )?;\n\n        self.lost_spurious_count += spurious_losses;\n        if let Some(thresh) = spurious_pkt_thresh {\n            self.loss_thresh.on_spurious_loss(thresh);\n        }\n\n        if self.newly_acked.is_empty() {\n            return Ok(OnAckReceivedOutcome {\n                acked_bytes,\n                spurious_losses,\n                ..Default::default()\n            });\n        }\n\n        self.bytes_in_flight.saturating_subtract(acked_bytes, now);\n\n        let largest_newly_acked = self.newly_acked.last().unwrap();\n\n        // Update `largest_acked_packet` based on the validated `newly_acked`\n        // value.\n        let largest_acked_pkt_num = self.epochs[epoch]\n            .largest_acked_packet\n            .unwrap_or(0)\n            .max(largest_newly_acked.pkt_num);\n        self.epochs[epoch].largest_acked_packet = Some(largest_acked_pkt_num);\n\n        // Check if largest packet is newly acked.\n        let update_rtt = largest_newly_acked.pkt_num == largest_acked_pkt_num &&\n            has_ack_eliciting;\n        if update_rtt {\n            let latest_rtt = now - largest_newly_acked.time_sent;\n            self.rtt_stats.update_rtt(\n                latest_rtt,\n                Duration::from_micros(ack_delay),\n                now,\n                handshake_status.completed,\n            );\n        }\n\n        let (lost_bytes, lost_packets) =\n            self.detect_and_remove_lost_packets(epoch, now);\n\n        self.pacer.on_congestion_event(\n            update_rtt,\n            prior_in_flight,\n            self.bytes_in_flight.get(),\n            now,\n            &self.newly_acked,\n            &self.lost_reuse,\n            self.epochs[epoch].least_unacked(),\n            &self.rtt_stats,\n            &mut self.recovery_stats,\n        );\n\n        self.pto_count = 0;\n        self.lost_count += lost_packets;\n\n        self.set_loss_detection_timer(handshake_status, now);\n\n        trace!(\"{trace_id} {self:?}\");\n\n        Ok(OnAckReceivedOutcome {\n            lost_packets,\n            lost_bytes,\n            acked_bytes,\n            spurious_losses,\n        })\n    }\n\n    fn on_loss_detection_timeout(\n        &mut self, handshake_status: HandshakeStatus, now: Instant,\n        trace_id: &str,\n    ) -> OnLossDetectionTimeoutOutcome {\n        let (earliest_loss_time, epoch) = self.loss_time_and_space();\n\n        if earliest_loss_time.is_some() {\n            let prior_in_flight = self.bytes_in_flight.get();\n\n            let (lost_bytes, lost_packets) =\n                self.detect_and_remove_lost_packets(epoch, now);\n\n            self.pacer.on_congestion_event(\n                false,\n                prior_in_flight,\n                self.bytes_in_flight.get(),\n                now,\n                &[],\n                &self.lost_reuse,\n                self.epochs[epoch].least_unacked(),\n                &self.rtt_stats,\n                &mut self.recovery_stats,\n            );\n\n            self.lost_count += lost_packets;\n\n            self.set_loss_detection_timer(handshake_status, now);\n\n            trace!(\"{trace_id} {self:?}\");\n            return OnLossDetectionTimeoutOutcome {\n                lost_packets,\n                lost_bytes,\n            };\n        }\n\n        let epoch = if self.bytes_in_flight.get() > 0 {\n            // Send new data if available, else retransmit old data. If neither\n            // is available, send a single PING frame.\n            let (_, e) = self.pto_time_and_space(handshake_status, now);\n\n            e\n        } else {\n            // Client sends an anti-deadlock packet: Initial is padded to earn\n            // more anti-amplification credit, a Handshake packet proves address\n            // ownership.\n            if handshake_status.has_handshake_keys {\n                packet::Epoch::Handshake\n            } else {\n                packet::Epoch::Initial\n            }\n        };\n\n        self.pto_count += 1;\n\n        let epoch = &mut self.epochs[epoch];\n\n        epoch.loss_probes = MAX_PTO_PROBES_COUNT.min(self.pto_count as usize);\n\n        // Skip packets that have already been acked or lost, and packets\n        // that don't contain either CRYPTO or STREAM frames and only return as\n        // many packets as the number of probe packets that will be sent.\n        let unacked_frames = epoch\n            .sent_packets\n            .iter_mut()\n            .filter_map(|p| {\n                if let SentStatus::Sent {\n                    has_data: true,\n                    frames,\n                    ..\n                } = &p.status\n                {\n                    Some(frames)\n                } else {\n                    None\n                }\n            })\n            .take(epoch.loss_probes)\n            .flatten()\n            .filter(|f| !matches!(f, frame::Frame::DatagramHeader { .. }));\n\n        // Retransmit the frames from the oldest sent packets on PTO. However\n        // the packets are not actually declared lost (so there is no effect to\n        // congestion control), we just reschedule the data they carried.\n        //\n        // This will also trigger sending an ACK and retransmitting frames like\n        // HANDSHAKE_DONE and MAX_DATA / MAX_STREAM_DATA as well, in addition\n        // to CRYPTO and STREAM, if the original packet carried them.\n        epoch.lost_frames.extend(unacked_frames.cloned());\n\n        self.pacer\n            .on_retransmission_timeout(!epoch.lost_frames.is_empty());\n\n        self.set_loss_detection_timer(handshake_status, now);\n\n        trace!(\"{trace_id} {self:?}\");\n        OnLossDetectionTimeoutOutcome {\n            lost_packets: 0,\n            lost_bytes: 0,\n        }\n    }\n\n    fn on_pkt_num_space_discarded(\n        &mut self, epoch: packet::Epoch, handshake_status: HandshakeStatus,\n        now: Instant,\n    ) {\n        let epoch = &mut self.epochs[epoch];\n        self.bytes_in_flight\n            .saturating_subtract(epoch.discard(&mut self.pacer), now);\n        self.set_loss_detection_timer(handshake_status, now);\n    }\n\n    fn on_path_change(\n        &mut self, epoch: packet::Epoch, now: Instant, _trace_id: &str,\n    ) -> (usize, usize) {\n        let (lost_bytes, lost_packets) =\n            self.detect_and_remove_lost_packets(epoch, now);\n\n        (lost_packets, lost_bytes)\n    }\n\n    fn loss_detection_timer(&self) -> Option<Instant> {\n        self.loss_timer.time\n    }\n\n    fn cwnd(&self) -> usize {\n        self.pacer.get_congestion_window()\n    }\n\n    fn cwnd_available(&self) -> usize {\n        // Ignore cwnd when sending probe packets.\n        if self.epochs.iter().any(|e| e.loss_probes > 0) {\n            return usize::MAX;\n        }\n\n        self.cwnd().saturating_sub(self.bytes_in_flight.get())\n    }\n\n    fn rtt(&self) -> Duration {\n        self.rtt_stats.rtt()\n    }\n\n    fn min_rtt(&self) -> Option<Duration> {\n        self.rtt_stats.min_rtt()\n    }\n\n    fn max_rtt(&self) -> Option<Duration> {\n        self.rtt_stats.max_rtt()\n    }\n\n    fn rttvar(&self) -> Duration {\n        self.rtt_stats.rttvar()\n    }\n\n    fn pto(&self) -> Duration {\n        let r = &self.rtt_stats;\n        r.rtt() + (r.rttvar() * 4).max(GRANULARITY)\n    }\n\n    /// The most recent data delivery rate estimate.\n    fn delivery_rate(&self) -> Bandwidth {\n        self.pacer.bandwidth_estimate(&self.rtt_stats)\n    }\n\n    fn max_bandwidth(&self) -> Option<Bandwidth> {\n        Some(self.pacer.max_bandwidth())\n    }\n\n    /// Statistics from when a CCA first exited the startup phase.\n    fn startup_exit(&self) -> Option<StartupExit> {\n        self.recovery_stats.startup_exit\n    }\n\n    fn max_datagram_size(&self) -> usize {\n        self.max_datagram_size\n    }\n\n    fn pmtud_update_max_datagram_size(&mut self, new_max_datagram_size: usize) {\n        self.max_datagram_size = new_max_datagram_size;\n        self.pacer.update_mss(self.max_datagram_size);\n    }\n\n    fn update_max_datagram_size(&mut self, new_max_datagram_size: usize) {\n        self.pmtud_update_max_datagram_size(\n            self.max_datagram_size.min(new_max_datagram_size),\n        )\n    }\n\n    // FIXME only used by gcongestion\n    fn on_app_limited(&mut self) {\n        self.pacer.on_app_limited(self.bytes_in_flight.get())\n    }\n\n    #[cfg(test)]\n    fn sent_packets_len(&self, epoch: packet::Epoch) -> usize {\n        self.epochs[epoch].sent_packets.len()\n    }\n\n    #[cfg(test)]\n    fn in_flight_count(&self, epoch: packet::Epoch) -> usize {\n        self.epochs[epoch].pkts_in_flight\n    }\n\n    fn bytes_in_flight(&self) -> usize {\n        self.bytes_in_flight.get()\n    }\n\n    fn bytes_in_flight_duration(&self) -> Duration {\n        self.bytes_in_flight.get_duration()\n    }\n\n    #[cfg(test)]\n    fn pacing_rate(&self) -> u64 {\n        self.pacer\n            .pacing_rate(self.bytes_in_flight.get(), &self.rtt_stats)\n            .to_bytes_per_period(Duration::from_secs(1))\n    }\n\n    #[cfg(test)]\n    fn pto_count(&self) -> u32 {\n        self.pto_count\n    }\n\n    #[cfg(test)]\n    fn pkt_thresh(&self) -> Option<u64> {\n        self.loss_thresh.pkt_thresh()\n    }\n\n    #[cfg(test)]\n    fn time_thresh(&self) -> f64 {\n        self.loss_thresh.time_thresh()\n    }\n\n    #[cfg(test)]\n    fn lost_spurious_count(&self) -> usize {\n        self.lost_spurious_count\n    }\n\n    #[cfg(test)]\n    fn detect_lost_packets_for_test(\n        &mut self, epoch: packet::Epoch, now: Instant,\n    ) -> (usize, usize) {\n        let ret = self.detect_and_remove_lost_packets(epoch, now);\n        self.epochs[epoch].drain_acked_and_lost_packets();\n        ret\n    }\n\n    #[cfg(test)]\n    fn largest_sent_pkt_num_on_path(&self, epoch: packet::Epoch) -> Option<u64> {\n        self.epochs[epoch].test_largest_sent_pkt_num_on_path\n    }\n\n    #[cfg(test)]\n    fn app_limited(&self) -> bool {\n        self.pacer.is_app_limited(self.bytes_in_flight.get())\n    }\n\n    // FIXME only used by congestion\n    fn update_app_limited(&mut self, _v: bool) {\n        // TODO\n    }\n\n    // FIXME only used by congestion\n    fn delivery_rate_update_app_limited(&mut self, _v: bool) {\n        // TODO\n    }\n\n    fn update_max_ack_delay(&mut self, max_ack_delay: Duration) {\n        self.rtt_stats.max_ack_delay = max_ack_delay;\n    }\n\n    fn get_next_release_time(&self) -> ReleaseDecision {\n        self.pacer.get_next_release_time()\n    }\n\n    fn gcongestion_enabled(&self) -> bool {\n        true\n    }\n\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self, _now: Instant) -> &'static str {\n        self.pacer.state_str()\n    }\n\n    #[cfg(feature = \"qlog\")]\n    fn get_updated_qlog_event_data(&mut self) -> Option<EventData> {\n        let qlog_metrics = QlogMetrics {\n            min_rtt: *self.rtt_stats.min_rtt,\n            smoothed_rtt: self.rtt(),\n            latest_rtt: self.rtt_stats.latest_rtt(),\n            rttvar: self.rtt_stats.rttvar(),\n            cwnd: self.cwnd() as u64,\n            bytes_in_flight: self.bytes_in_flight.get() as u64,\n            ssthresh: self.pacer.ssthresh(),\n\n            pacing_rate: Some(\n                self.pacer\n                    .pacing_rate(self.bytes_in_flight.get(), &self.rtt_stats)\n                    .to_bytes_per_second(),\n            ),\n            delivery_rate: Some(self.delivery_rate().to_bytes_per_second()),\n            send_rate: Some(self.send_rate().to_bytes_per_second()),\n            ack_rate: Some(self.ack_rate().to_bytes_per_second()),\n            lost_packets: Some(self.lost_count as u64),\n            lost_bytes: Some(self.bytes_lost),\n            pto_count: Some(self.pto_count),\n        };\n\n        self.qlog_metrics.maybe_update(qlog_metrics)\n    }\n\n    #[cfg(feature = \"qlog\")]\n    fn get_updated_qlog_cc_state(\n        &mut self, now: Instant,\n    ) -> Option<&'static str> {\n        let cc_state = self.state_str(now);\n        if cc_state != self.qlog_prev_cc_state {\n            self.qlog_prev_cc_state = cc_state;\n            Some(cc_state)\n        } else {\n            None\n        }\n    }\n\n    fn send_quantum(&self) -> usize {\n        let pacing_rate = self\n            .pacer\n            .pacing_rate(self.bytes_in_flight.get(), &self.rtt_stats);\n\n        let floor = if pacing_rate < Bandwidth::from_kbits_per_second(1200) {\n            self.max_datagram_size\n        } else {\n            2 * self.max_datagram_size\n        };\n\n        pacing_rate\n            .to_bytes_per_period(ReleaseDecision::EQUAL_THRESHOLD)\n            .min(64 * 1024)\n            .max(floor as u64) as usize\n    }\n}\n\nimpl std::fmt::Debug for GRecovery {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"timer={:?} \", self.loss_detection_timer())?;\n        write!(f, \"rtt_stats={:?} \", self.rtt_stats)?;\n        write!(f, \"bytes_in_flight={} \", self.bytes_in_flight.get())?;\n        write!(f, \"{:?} \", self.pacer)?;\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::Config;\n\n    #[test]\n    fn loss_threshold() {\n        let config = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        let recovery_config = RecoveryConfig::from_config(&config);\n        assert!(!recovery_config.enable_relaxed_loss_threshold);\n\n        let mut loss_thresh = LossThreshold::new(&recovery_config);\n        assert_eq!(loss_thresh.time_thresh_overhead, None);\n        assert_eq!(loss_thresh.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);\n        assert_eq!(loss_thresh.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // First spurious loss.\n        loss_thresh.on_spurious_loss(INITIAL_PACKET_THRESHOLD);\n        assert_eq!(loss_thresh.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);\n        assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);\n\n        // Packet gaps < INITIAL_PACKET_THRESHOLD will NOT change packet\n        // threshold.\n        for packet_gap in 0..INITIAL_PACKET_THRESHOLD {\n            loss_thresh.on_spurious_loss(packet_gap);\n\n            // Packet threshold only increases once the packet gap increases.\n            assert_eq!(\n                loss_thresh.pkt_thresh().unwrap(),\n                INITIAL_PACKET_THRESHOLD\n            );\n            assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);\n        }\n\n        // Subsequent spurious loss with packet_gaps > INITIAL_PACKET_THRESHOLD.\n        // Test values much larger than MAX_PACKET_THRESHOLD, i.e.\n        // `MAX_PACKET_THRESHOLD * 2`\n        for packet_gap in INITIAL_PACKET_THRESHOLD + 1..MAX_PACKET_THRESHOLD * 2 {\n            loss_thresh.on_spurious_loss(packet_gap);\n\n            // Packet threshold is equal to packet gap beyond\n            // INITIAL_PACKET_THRESHOLD, but capped\n            // at MAX_PACKET_THRESHOLD.\n            let new_packet_threshold = if packet_gap < MAX_PACKET_THRESHOLD {\n                packet_gap\n            } else {\n                MAX_PACKET_THRESHOLD\n            };\n            assert_eq!(loss_thresh.pkt_thresh().unwrap(), new_packet_threshold);\n            assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);\n        }\n        // Packet threshold is capped at MAX_PACKET_THRESHOLD\n        assert_eq!(loss_thresh.pkt_thresh().unwrap(), MAX_PACKET_THRESHOLD);\n        assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);\n\n        // Packet threshold is monotonically increasing\n        loss_thresh.on_spurious_loss(INITIAL_PACKET_THRESHOLD);\n        assert_eq!(loss_thresh.pkt_thresh().unwrap(), MAX_PACKET_THRESHOLD);\n        assert_eq!(loss_thresh.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);\n    }\n\n    #[test]\n    fn relaxed_loss_threshold() {\n        // The max time threshold when operating in relaxed loss mode.\n        const MAX_TIME_THRESHOLD: f64 = 2.0;\n\n        let mut config = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        config.set_enable_relaxed_loss_threshold(true);\n        let recovery_config = RecoveryConfig::from_config(&config);\n        assert!(recovery_config.enable_relaxed_loss_threshold);\n\n        let mut loss_thresh = LossThreshold::new(&recovery_config);\n        assert_eq!(\n            loss_thresh.time_thresh_overhead,\n            Some(INITIAL_TIME_THRESHOLD_OVERHEAD)\n        );\n        assert_eq!(loss_thresh.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);\n        assert_eq!(loss_thresh.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // First spurious loss.\n        loss_thresh.on_spurious_loss(INITIAL_PACKET_THRESHOLD);\n        assert_eq!(loss_thresh.pkt_thresh(), None);\n        assert_eq!(loss_thresh.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // Subsequent spurious loss.\n        for subsequent_loss_count in 1..100 {\n            // Double the overhead until it caps at `2.0`.\n            //\n            // It takes `3` rounds of doubling for INITIAL_TIME_THRESHOLD_OVERHEAD\n            // to equal `1.0`.\n            let new_time_threshold = if subsequent_loss_count <= 3 {\n                1.0 + INITIAL_TIME_THRESHOLD_OVERHEAD *\n                    2_f64.powi(subsequent_loss_count as i32)\n            } else {\n                2.0\n            };\n\n            loss_thresh.on_spurious_loss(subsequent_loss_count);\n            assert_eq!(loss_thresh.pkt_thresh(), None);\n            assert_eq!(loss_thresh.time_thresh(), new_time_threshold);\n        }\n        // Time threshold is capped at 2.0.\n        assert_eq!(loss_thresh.pkt_thresh(), None);\n        assert_eq!(loss_thresh.time_thresh(), MAX_TIME_THRESHOLD);\n    }\n}\n"
  },
  {
    "path": "quiche/src/recovery/mod.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::str::FromStr;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse crate::frame;\nuse crate::packet;\nuse crate::ranges::RangeSet;\npub(crate) use crate::recovery::bandwidth::Bandwidth;\nuse crate::Config;\nuse crate::Result;\n\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventData;\n#[cfg(feature = \"qlog\")]\nuse serde::Serialize;\n\nuse smallvec::SmallVec;\n\nuse self::congestion::recovery::LegacyRecovery;\nuse self::gcongestion::GRecovery;\npub use gcongestion::BbrBwLoReductionStrategy;\npub use gcongestion::BbrParams;\n\n// Loss Recovery\nconst INITIAL_PACKET_THRESHOLD: u64 = 3;\n\nconst MAX_PACKET_THRESHOLD: u64 = 20;\n\n// Time threshold used to calculate the loss time.\n//\n// https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.2\nconst INITIAL_TIME_THRESHOLD: f64 = 9.0 / 8.0;\n\n// Reduce the sensitivity to packet reordering after the first reordering event.\n//\n// Packet reorder is not a real loss event so quickly reduce the sensitivity to\n// avoid penializing subsequent packet reordering.\n//\n// https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.2\n//\n// Implementations MAY experiment with absolute thresholds, thresholds from\n// previous connections, adaptive thresholds, or the including of RTT variation.\n// Smaller thresholds reduce reordering resilience and increase spurious\n// retransmissions, and larger thresholds increase loss detection delay.\nconst PACKET_REORDER_TIME_THRESHOLD: f64 = 5.0 / 4.0;\n\n// # Experiment: enable_relaxed_loss_threshold\n//\n// Time threshold overhead used to calculate the loss time.\n//\n// The actual threshold is calcualted as 1 + INITIAL_TIME_THRESHOLD_OVERHEAD and\n// equivalent to INITIAL_TIME_THRESHOLD.\nconst INITIAL_TIME_THRESHOLD_OVERHEAD: f64 = 1.0 / 8.0;\n// # Experiment: enable_relaxed_loss_threshold\n//\n// The factor by which to increase the time threshold on spurious loss.\nconst TIME_THRESHOLD_OVERHEAD_MULTIPLIER: f64 = 2.0;\n\nconst GRANULARITY: Duration = Duration::from_millis(1);\n\nconst MAX_PTO_PROBES_COUNT: usize = 2;\n\nconst MINIMUM_WINDOW_PACKETS: usize = 2;\n\nconst LOSS_REDUCTION_FACTOR: f64 = 0.5;\n\n// How many non ACK eliciting packets we send before including a PING to solicit\n// an ACK.\npub(super) const MAX_OUTSTANDING_NON_ACK_ELICITING: usize = 24;\n\n#[derive(Default)]\nstruct LossDetectionTimer {\n    time: Option<Instant>,\n}\n\nimpl LossDetectionTimer {\n    fn update(&mut self, timeout: Instant) {\n        self.time = Some(timeout);\n    }\n\n    fn clear(&mut self) {\n        self.time = None;\n    }\n}\n\nimpl std::fmt::Debug for LossDetectionTimer {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self.time {\n            Some(v) => {\n                let now = Instant::now();\n                if v > now {\n                    let d = v.duration_since(now);\n                    write!(f, \"{d:?}\")\n                } else {\n                    write!(f, \"exp\")\n                }\n            },\n            None => write!(f, \"none\"),\n        }\n    }\n}\n\n#[derive(Clone, Copy, PartialEq)]\npub struct RecoveryConfig {\n    pub initial_rtt: Duration,\n    pub max_send_udp_payload_size: usize,\n    pub max_ack_delay: Duration,\n    pub cc_algorithm: CongestionControlAlgorithm,\n    pub custom_bbr_params: Option<BbrParams>,\n    pub hystart: bool,\n    pub pacing: bool,\n    pub max_pacing_rate: Option<u64>,\n    pub initial_congestion_window_packets: usize,\n    pub enable_relaxed_loss_threshold: bool,\n    pub enable_cubic_idle_restart_fix: bool,\n}\n\nimpl RecoveryConfig {\n    pub fn from_config(config: &Config) -> Self {\n        Self {\n            initial_rtt: config.initial_rtt,\n            max_send_udp_payload_size: config.max_send_udp_payload_size,\n            max_ack_delay: Duration::ZERO,\n            cc_algorithm: config.cc_algorithm,\n            custom_bbr_params: config.custom_bbr_params,\n            hystart: config.hystart,\n            pacing: config.pacing,\n            max_pacing_rate: config.max_pacing_rate,\n            initial_congestion_window_packets: config\n                .initial_congestion_window_packets,\n            enable_relaxed_loss_threshold: config.enable_relaxed_loss_threshold,\n            enable_cubic_idle_restart_fix: config.enable_cubic_idle_restart_fix,\n        }\n    }\n}\n\n#[enum_dispatch::enum_dispatch(RecoveryOps)]\n#[allow(clippy::large_enum_variant)]\n#[derive(Debug)]\npub(crate) enum Recovery {\n    Legacy(LegacyRecovery),\n    GCongestion(GRecovery),\n}\n\n#[derive(Debug, Default, PartialEq)]\npub struct OnAckReceivedOutcome {\n    pub lost_packets: usize,\n    pub lost_bytes: usize,\n    pub acked_bytes: usize,\n    pub spurious_losses: usize,\n}\n\n#[derive(Debug, Default)]\npub struct OnLossDetectionTimeoutOutcome {\n    pub lost_packets: usize,\n    pub lost_bytes: usize,\n}\n\n#[enum_dispatch::enum_dispatch]\n/// Api for the Recovery implementation\npub trait RecoveryOps {\n    fn lost_count(&self) -> usize;\n    fn bytes_lost(&self) -> u64;\n\n    /// Returns whether or not we should elicit an ACK even if we wouldn't\n    /// otherwise have constructed an ACK eliciting packet.\n    fn should_elicit_ack(&self, epoch: packet::Epoch) -> bool;\n\n    fn next_acked_frame(&mut self, epoch: packet::Epoch) -> Option<frame::Frame>;\n\n    fn next_lost_frame(&mut self, epoch: packet::Epoch) -> Option<frame::Frame>;\n\n    fn get_largest_acked_on_epoch(&self, epoch: packet::Epoch) -> Option<u64>;\n    fn has_lost_frames(&self, epoch: packet::Epoch) -> bool;\n    fn loss_probes(&self, epoch: packet::Epoch) -> usize;\n    #[cfg(test)]\n    fn inc_loss_probes(&mut self, epoch: packet::Epoch);\n\n    fn ping_sent(&mut self, epoch: packet::Epoch);\n\n    fn on_packet_sent(\n        &mut self, pkt: Sent, epoch: packet::Epoch,\n        handshake_status: HandshakeStatus, now: Instant, trace_id: &str,\n    );\n    fn get_packet_send_time(&self, now: Instant) -> Instant;\n\n    #[allow(clippy::too_many_arguments)]\n    fn on_ack_received(\n        &mut self, ranges: &RangeSet, ack_delay: u64, epoch: packet::Epoch,\n        handshake_status: HandshakeStatus, now: Instant, skip_pn: Option<u64>,\n        trace_id: &str,\n    ) -> Result<OnAckReceivedOutcome>;\n\n    fn on_loss_detection_timeout(\n        &mut self, handshake_status: HandshakeStatus, now: Instant,\n        trace_id: &str,\n    ) -> OnLossDetectionTimeoutOutcome;\n    fn on_pkt_num_space_discarded(\n        &mut self, epoch: packet::Epoch, handshake_status: HandshakeStatus,\n        now: Instant,\n    );\n    fn on_path_change(\n        &mut self, epoch: packet::Epoch, now: Instant, _trace_id: &str,\n    ) -> (usize, usize);\n    fn loss_detection_timer(&self) -> Option<Instant>;\n    fn cwnd(&self) -> usize;\n    fn cwnd_available(&self) -> usize;\n    fn rtt(&self) -> Duration;\n\n    fn min_rtt(&self) -> Option<Duration>;\n\n    fn max_rtt(&self) -> Option<Duration>;\n\n    fn rttvar(&self) -> Duration;\n\n    fn pto(&self) -> Duration;\n\n    /// The most recent data delivery rate estimate.\n    fn delivery_rate(&self) -> Bandwidth;\n\n    /// Maximum bandwidth estimate, if one is available.\n    fn max_bandwidth(&self) -> Option<Bandwidth>;\n\n    /// Statistics from when a CCA first exited the startup phase.\n    fn startup_exit(&self) -> Option<StartupExit>;\n\n    fn max_datagram_size(&self) -> usize;\n\n    fn pmtud_update_max_datagram_size(&mut self, new_max_datagram_size: usize);\n\n    fn update_max_datagram_size(&mut self, new_max_datagram_size: usize);\n\n    fn on_app_limited(&mut self);\n\n    // Since a recovery module is path specific, this tracks the largest packet\n    // sent per path.\n    #[cfg(test)]\n    fn largest_sent_pkt_num_on_path(&self, epoch: packet::Epoch) -> Option<u64>;\n\n    #[cfg(test)]\n    fn app_limited(&self) -> bool;\n\n    #[cfg(test)]\n    fn sent_packets_len(&self, epoch: packet::Epoch) -> usize;\n\n    fn bytes_in_flight(&self) -> usize;\n\n    fn bytes_in_flight_duration(&self) -> Duration;\n\n    #[cfg(test)]\n    fn in_flight_count(&self, epoch: packet::Epoch) -> usize;\n\n    #[cfg(test)]\n    fn pacing_rate(&self) -> u64;\n\n    #[cfg(test)]\n    fn pto_count(&self) -> u32;\n\n    // This value might be `None` when experiment `enable_relaxed_loss_threshold`\n    // is enabled for gcongestion\n    #[cfg(test)]\n    fn pkt_thresh(&self) -> Option<u64>;\n\n    #[cfg(test)]\n    fn time_thresh(&self) -> f64;\n\n    #[cfg(test)]\n    fn lost_spurious_count(&self) -> usize;\n\n    #[cfg(test)]\n    fn detect_lost_packets_for_test(\n        &mut self, epoch: packet::Epoch, now: Instant,\n    ) -> (usize, usize);\n\n    fn update_app_limited(&mut self, v: bool);\n\n    fn delivery_rate_update_app_limited(&mut self, v: bool);\n\n    fn update_max_ack_delay(&mut self, max_ack_delay: Duration);\n\n    #[cfg(feature = \"qlog\")]\n    fn state_str(&self, now: Instant) -> &'static str;\n\n    #[cfg(feature = \"qlog\")]\n    fn get_updated_qlog_event_data(&mut self) -> Option<EventData>;\n\n    #[cfg(feature = \"qlog\")]\n    fn get_updated_qlog_cc_state(&mut self, now: Instant)\n        -> Option<&'static str>;\n\n    fn send_quantum(&self) -> usize;\n\n    fn get_next_release_time(&self) -> ReleaseDecision;\n\n    fn gcongestion_enabled(&self) -> bool;\n}\n\nimpl Recovery {\n    pub fn new_with_config(recovery_config: &RecoveryConfig) -> Self {\n        let grecovery = GRecovery::new(recovery_config);\n        if let Some(grecovery) = grecovery {\n            Recovery::from(grecovery)\n        } else {\n            Recovery::from(LegacyRecovery::new_with_config(recovery_config))\n        }\n    }\n\n    #[cfg(feature = \"qlog\")]\n    pub fn maybe_qlog(\n        &mut self, qlog: &mut qlog::streamer::QlogStreamer, now: Instant,\n    ) {\n        if let Some(ev_data) = self.get_updated_qlog_event_data() {\n            qlog.add_event_data_with_instant(ev_data, now).ok();\n        }\n\n        if let Some(cc_state) = self.get_updated_qlog_cc_state(now) {\n            let ev_data = EventData::QuicCongestionStateUpdated(\n                qlog::events::quic::CongestionStateUpdated {\n                    old: None,\n                    new: cc_state.to_string(),\n                    trigger: None,\n                },\n            );\n\n            qlog.add_event_data_with_instant(ev_data, now).ok();\n        }\n    }\n\n    #[cfg(test)]\n    pub fn new(config: &Config) -> Self {\n        Self::new_with_config(&RecoveryConfig::from_config(config))\n    }\n}\n\n/// Available congestion control algorithms.\n///\n/// This enum provides currently available list of congestion control\n/// algorithms.\n#[derive(Debug, Copy, Clone, PartialEq, Eq)]\n#[repr(C)]\npub enum CongestionControlAlgorithm {\n    /// Reno congestion control algorithm. `reno` in a string form.\n    Reno            = 0,\n    /// CUBIC congestion control algorithm (default). `cubic` in a string form.\n    CUBIC           = 1,\n    /// BBRv2 congestion control algorithm implementation from gcongestion\n    /// branch. `bbr2_gcongestion` in a string form.\n    Bbr2Gcongestion = 4,\n}\n\nimpl FromStr for CongestionControlAlgorithm {\n    type Err = crate::Error;\n\n    /// Converts a string to `CongestionControlAlgorithm`.\n    ///\n    /// If `name` is not valid, `Error::CongestionControl` is returned.\n    fn from_str(name: &str) -> std::result::Result<Self, Self::Err> {\n        match name {\n            \"reno\" => Ok(CongestionControlAlgorithm::Reno),\n            \"cubic\" => Ok(CongestionControlAlgorithm::CUBIC),\n            \"bbr\" => Ok(CongestionControlAlgorithm::Bbr2Gcongestion),\n            \"bbr2\" => Ok(CongestionControlAlgorithm::Bbr2Gcongestion),\n            \"bbr2_gcongestion\" => Ok(CongestionControlAlgorithm::Bbr2Gcongestion),\n            _ => Err(crate::Error::CongestionControl),\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct Sent {\n    pub pkt_num: u64,\n\n    pub frames: SmallVec<[frame::Frame; 1]>,\n\n    pub time_sent: Instant,\n\n    pub time_acked: Option<Instant>,\n\n    pub time_lost: Option<Instant>,\n\n    pub size: usize,\n\n    pub ack_eliciting: bool,\n\n    pub in_flight: bool,\n\n    pub delivered: usize,\n\n    pub delivered_time: Instant,\n\n    pub first_sent_time: Instant,\n\n    pub is_app_limited: bool,\n\n    pub tx_in_flight: usize,\n\n    pub lost: u64,\n\n    pub has_data: bool,\n\n    pub is_pmtud_probe: bool,\n}\n\nimpl std::fmt::Debug for Sent {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"pkt_num={:?} \", self.pkt_num)?;\n        write!(f, \"pkt_sent_time={:?} \", self.time_sent)?;\n        write!(f, \"pkt_size={:?} \", self.size)?;\n        write!(f, \"delivered={:?} \", self.delivered)?;\n        write!(f, \"delivered_time={:?} \", self.delivered_time)?;\n        write!(f, \"first_sent_time={:?} \", self.first_sent_time)?;\n        write!(f, \"is_app_limited={} \", self.is_app_limited)?;\n        write!(f, \"tx_in_flight={} \", self.tx_in_flight)?;\n        write!(f, \"lost={} \", self.lost)?;\n        write!(f, \"has_data={} \", self.has_data)?;\n        write!(f, \"is_pmtud_probe={}\", self.is_pmtud_probe)?;\n\n        Ok(())\n    }\n}\n\n#[derive(Clone, Copy, Debug)]\npub struct HandshakeStatus {\n    pub has_handshake_keys: bool,\n\n    pub peer_verified_address: bool,\n\n    pub completed: bool,\n}\n\n#[cfg(test)]\nimpl Default for HandshakeStatus {\n    fn default() -> HandshakeStatus {\n        HandshakeStatus {\n            has_handshake_keys: true,\n\n            peer_verified_address: true,\n\n            completed: true,\n        }\n    }\n}\n\n// We don't need to log all qlog metrics every time there is a recovery event.\n// Instead, we can log only the MetricsUpdated event data fields that we care\n// about, only when they change. To support this, the QLogMetrics structure\n// keeps a running picture of the fields.\n#[derive(Default)]\n#[cfg(feature = \"qlog\")]\nstruct QlogMetrics {\n    min_rtt: Duration,\n    smoothed_rtt: Duration,\n    latest_rtt: Duration,\n    rttvar: Duration,\n    cwnd: u64,\n    bytes_in_flight: u64,\n    ssthresh: Option<u64>,\n    pacing_rate: Option<u64>,\n    delivery_rate: Option<u64>,\n    send_rate: Option<u64>,\n    ack_rate: Option<u64>,\n    lost_packets: Option<u64>,\n    lost_bytes: Option<u64>,\n    pto_count: Option<u32>,\n}\n\n#[cfg(feature = \"qlog\")]\ntrait CustomCfQlogField {\n    fn name(&self) -> &'static str;\n    fn as_json_value(&self) -> serde_json::Value;\n}\n\n#[cfg(feature = \"qlog\")]\n#[serde_with::skip_serializing_none]\n#[derive(Serialize)]\nstruct TotalAndDelta {\n    total: Option<u64>,\n    delta: Option<u64>,\n}\n\n#[cfg(feature = \"qlog\")]\nstruct CustomQlogField<T> {\n    name: &'static str,\n    value: T,\n}\n\n#[cfg(feature = \"qlog\")]\nimpl<T> CustomQlogField<T> {\n    fn new(name: &'static str, value: T) -> Self {\n        Self { name, value }\n    }\n}\n\n#[cfg(feature = \"qlog\")]\nimpl<T: Serialize> CustomCfQlogField for CustomQlogField<T> {\n    fn name(&self) -> &'static str {\n        self.name\n    }\n\n    fn as_json_value(&self) -> serde_json::Value {\n        serde_json::json!(&self.value)\n    }\n}\n\n#[cfg(feature = \"qlog\")]\nstruct CfExData(qlog::events::ExData);\n\n#[cfg(feature = \"qlog\")]\nimpl CfExData {\n    fn new() -> Self {\n        Self(qlog::events::ExData::new())\n    }\n\n    fn insert<T: Serialize>(&mut self, name: &'static str, value: T) {\n        let field = CustomQlogField::new(name, value);\n        self.0\n            .insert(field.name().to_string(), field.as_json_value());\n    }\n\n    fn into_inner(self) -> qlog::events::ExData {\n        self.0\n    }\n}\n\n#[cfg(feature = \"qlog\")]\nimpl QlogMetrics {\n    // Make a qlog event if the latest instance of QlogMetrics is different.\n    //\n    // This function diffs each of the fields. A qlog MetricsUpdated event is\n    // only generated if at least one field is different. Where fields are\n    // different, the qlog event contains the latest value.\n    fn maybe_update(&mut self, latest: Self) -> Option<EventData> {\n        let mut emit_event = false;\n\n        let new_min_rtt = if self.min_rtt != latest.min_rtt {\n            self.min_rtt = latest.min_rtt;\n            emit_event = true;\n            Some(latest.min_rtt.as_secs_f32() * 1000.0)\n        } else {\n            None\n        };\n\n        let new_smoothed_rtt = if self.smoothed_rtt != latest.smoothed_rtt {\n            self.smoothed_rtt = latest.smoothed_rtt;\n            emit_event = true;\n            Some(latest.smoothed_rtt.as_secs_f32() * 1000.0)\n        } else {\n            None\n        };\n\n        let new_latest_rtt = if self.latest_rtt != latest.latest_rtt {\n            self.latest_rtt = latest.latest_rtt;\n            emit_event = true;\n            Some(latest.latest_rtt.as_secs_f32() * 1000.0)\n        } else {\n            None\n        };\n\n        let new_rttvar = if self.rttvar != latest.rttvar {\n            self.rttvar = latest.rttvar;\n            emit_event = true;\n            Some(latest.rttvar.as_secs_f32() * 1000.0)\n        } else {\n            None\n        };\n\n        let new_cwnd = if self.cwnd != latest.cwnd {\n            self.cwnd = latest.cwnd;\n            emit_event = true;\n            Some(latest.cwnd)\n        } else {\n            None\n        };\n\n        let new_bytes_in_flight =\n            if self.bytes_in_flight != latest.bytes_in_flight {\n                self.bytes_in_flight = latest.bytes_in_flight;\n                emit_event = true;\n                Some(latest.bytes_in_flight)\n            } else {\n                None\n            };\n\n        let new_ssthresh = if self.ssthresh != latest.ssthresh {\n            self.ssthresh = latest.ssthresh;\n            emit_event = true;\n            latest.ssthresh\n        } else {\n            None\n        };\n\n        let new_pacing_rate = if self.pacing_rate != latest.pacing_rate {\n            self.pacing_rate = latest.pacing_rate;\n            emit_event = true;\n            latest.pacing_rate\n        } else {\n            None\n        };\n\n        let new_pto_count =\n            if latest.pto_count.is_some() && self.pto_count != latest.pto_count {\n                self.pto_count = latest.pto_count;\n                emit_event = true;\n                latest.pto_count.map(|v| v as u16)\n            } else {\n                None\n            };\n\n        // Build ex_data for rate metrics\n        let mut ex_data = CfExData::new();\n        if self.delivery_rate != latest.delivery_rate {\n            if let Some(rate) = latest.delivery_rate {\n                self.delivery_rate = latest.delivery_rate;\n                emit_event = true;\n                ex_data.insert(\"cf_delivery_rate\", rate);\n            }\n        }\n        if self.send_rate != latest.send_rate {\n            if let Some(rate) = latest.send_rate {\n                self.send_rate = latest.send_rate;\n                emit_event = true;\n                ex_data.insert(\"cf_send_rate\", rate);\n            }\n        }\n        if self.ack_rate != latest.ack_rate {\n            if let Some(rate) = latest.ack_rate {\n                self.ack_rate = latest.ack_rate;\n                emit_event = true;\n                ex_data.insert(\"cf_ack_rate\", rate);\n            }\n        }\n\n        if self.lost_packets != latest.lost_packets {\n            if let Some(val) = latest.lost_packets {\n                emit_event = true;\n                ex_data.insert(\"cf_lost_packets\", TotalAndDelta {\n                    total: latest.lost_packets,\n                    delta: Some(val - self.lost_packets.unwrap_or(0)),\n                });\n                self.lost_packets = latest.lost_packets;\n            }\n        }\n        if self.lost_bytes != latest.lost_bytes {\n            if let Some(val) = latest.lost_bytes {\n                emit_event = true;\n                ex_data.insert(\"cf_lost_bytes\", TotalAndDelta {\n                    total: latest.lost_bytes,\n                    delta: Some(val - self.lost_bytes.unwrap_or(0)),\n                });\n                self.lost_bytes = latest.lost_bytes;\n            }\n        }\n\n        if emit_event {\n            return Some(EventData::QuicMetricsUpdated(\n                qlog::events::quic::RecoveryMetricsUpdated {\n                    min_rtt: new_min_rtt,\n                    smoothed_rtt: new_smoothed_rtt,\n                    latest_rtt: new_latest_rtt,\n                    rtt_variance: new_rttvar,\n                    congestion_window: new_cwnd,\n                    bytes_in_flight: new_bytes_in_flight,\n                    ssthresh: new_ssthresh,\n                    pacing_rate: new_pacing_rate,\n                    pto_count: new_pto_count,\n                    ex_data: ex_data.into_inner(),\n                    ..Default::default()\n                },\n            ));\n        }\n\n        None\n    }\n}\n\n/// When the pacer thinks is a good time to release the next packet\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum ReleaseTime {\n    Immediate,\n    At(Instant),\n}\n\n/// When the next packet should be release and if it can be part of a burst\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct ReleaseDecision {\n    time: ReleaseTime,\n    allow_burst: bool,\n}\n\nimpl ReleaseTime {\n    /// Add the specific delay to the current time\n    fn inc(&mut self, delay: Duration) {\n        match self {\n            ReleaseTime::Immediate => {},\n            ReleaseTime::At(time) => *time += delay,\n        }\n    }\n\n    /// Set the time to the later of two times\n    fn set_max(&mut self, other: Instant) {\n        match self {\n            ReleaseTime::Immediate => *self = ReleaseTime::At(other),\n            ReleaseTime::At(time) => *self = ReleaseTime::At(other.max(*time)),\n        }\n    }\n}\n\nimpl ReleaseDecision {\n    pub(crate) const EQUAL_THRESHOLD: Duration = Duration::from_micros(50);\n\n    /// Get the [`Instant`] the next packet should be released. It will never be\n    /// in the past.\n    #[inline]\n    pub fn time(&self, now: Instant) -> Option<Instant> {\n        match self.time {\n            ReleaseTime::Immediate => None,\n            ReleaseTime::At(other) => other.gt(&now).then_some(other),\n        }\n    }\n\n    /// Can this packet be appended to a previous burst\n    #[inline]\n    pub fn can_burst(&self) -> bool {\n        self.allow_burst\n    }\n\n    /// Check if the two packets can be released at the same time\n    #[inline]\n    pub fn time_eq(&self, other: &Self, now: Instant) -> bool {\n        let delta = match (self.time(now), other.time(now)) {\n            (None, None) => Duration::ZERO,\n            (Some(t), None) | (None, Some(t)) => t.duration_since(now),\n            (Some(t1), Some(t2)) if t1 < t2 => t2.duration_since(t1),\n            (Some(t1), Some(t2)) => t1.duration_since(t2),\n        };\n\n        delta <= Self::EQUAL_THRESHOLD\n    }\n}\n\n/// Recovery statistics\n#[derive(Default, Debug)]\npub struct RecoveryStats {\n    startup_exit: Option<StartupExit>,\n}\n\nimpl RecoveryStats {\n    // Record statistics when a CCA first exits startup.\n    pub fn set_startup_exit(&mut self, startup_exit: StartupExit) {\n        if self.startup_exit.is_none() {\n            self.startup_exit = Some(startup_exit);\n        }\n    }\n}\n\n/// Statistics from when a CCA first exited the startup phase.\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct StartupExit {\n    /// The congestion_window recorded at Startup exit.\n    pub cwnd: usize,\n\n    /// The bandwidth estimate recorded at Startup exit.\n    pub bandwidth: Option<u64>,\n\n    /// The reason a CCA exited the startup phase.\n    pub reason: StartupExitReason,\n}\n\nimpl StartupExit {\n    fn new(\n        cwnd: usize, bandwidth: Option<Bandwidth>, reason: StartupExitReason,\n    ) -> Self {\n        let bandwidth = bandwidth.map(Bandwidth::to_bytes_per_second);\n        Self {\n            cwnd,\n            bandwidth,\n            reason,\n        }\n    }\n}\n\n/// The reason a CCA exited the startup phase.\n#[derive(Debug, Clone, Copy, PartialEq)]\npub enum StartupExitReason {\n    /// Exit slow start or BBR startup due to excessive loss\n    Loss,\n\n    /// Exit BBR startup due to bandwidth plateau.\n    BandwidthPlateau,\n\n    /// Exit BBR startup due to persistent queue.\n    PersistentQueue,\n\n    /// Exit HyStart++ conservative slow start after the max rounds allowed.\n    ConservativeSlowStartRounds,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::packet;\n    use crate::test_utils;\n    use crate::CongestionControlAlgorithm;\n    use crate::DEFAULT_INITIAL_RTT;\n    use rstest::rstest;\n    use smallvec::smallvec;\n    use std::str::FromStr;\n\n    fn recovery_for_alg(algo: CongestionControlAlgorithm) -> Recovery {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        cfg.set_cc_algorithm(algo);\n        Recovery::new(&cfg)\n    }\n\n    #[test]\n    fn lookup_cc_algo_ok() {\n        let algo = CongestionControlAlgorithm::from_str(\"reno\").unwrap();\n        assert_eq!(algo, CongestionControlAlgorithm::Reno);\n        assert!(!recovery_for_alg(algo).gcongestion_enabled());\n\n        let algo = CongestionControlAlgorithm::from_str(\"cubic\").unwrap();\n        assert_eq!(algo, CongestionControlAlgorithm::CUBIC);\n        assert!(!recovery_for_alg(algo).gcongestion_enabled());\n\n        let algo = CongestionControlAlgorithm::from_str(\"bbr\").unwrap();\n        assert_eq!(algo, CongestionControlAlgorithm::Bbr2Gcongestion);\n        assert!(recovery_for_alg(algo).gcongestion_enabled());\n\n        let algo = CongestionControlAlgorithm::from_str(\"bbr2\").unwrap();\n        assert_eq!(algo, CongestionControlAlgorithm::Bbr2Gcongestion);\n        assert!(recovery_for_alg(algo).gcongestion_enabled());\n\n        let algo =\n            CongestionControlAlgorithm::from_str(\"bbr2_gcongestion\").unwrap();\n        assert_eq!(algo, CongestionControlAlgorithm::Bbr2Gcongestion);\n        assert!(recovery_for_alg(algo).gcongestion_enabled());\n    }\n\n    #[test]\n    fn lookup_cc_algo_bad() {\n        assert_eq!(\n            CongestionControlAlgorithm::from_str(\"???\"),\n            Err(crate::Error::CongestionControl)\n        );\n    }\n\n    #[rstest]\n    fn loss_on_pto(\n        #[values(\"reno\", \"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    ) {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        let mut r = Recovery::new(&cfg);\n\n        let mut now = Instant::now();\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n\n        // Start by sending a few packets.\n        let p = Sent {\n            pkt_num: 0,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 1);\n        assert_eq!(r.bytes_in_flight(), 1000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        let p = Sent {\n            pkt_num: 1,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 2000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        let p = Sent {\n            pkt_num: 2,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 3);\n        assert_eq!(r.bytes_in_flight(), 3000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        let p = Sent {\n            pkt_num: 3,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 4);\n        assert_eq!(r.bytes_in_flight(), 4000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        // Wait for 10ms.\n        now += Duration::from_millis(10);\n\n        // Only the first 2 packets are acked.\n        let mut acked = RangeSet::default();\n        acked.insert(0..2);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 2 * 1000,\n                spurious_losses: 0,\n            }\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 2000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_millis(10));\n        assert_eq!(r.lost_count(), 0);\n\n        // Wait until loss detection timer expires.\n        now = r.loss_detection_timer().unwrap();\n\n        // PTO.\n        r.on_loss_detection_timeout(HandshakeStatus::default(), now, \"\");\n        assert_eq!(r.loss_probes(packet::Epoch::Application), 1);\n        assert_eq!(r.lost_count(), 0);\n        assert_eq!(r.pto_count(), 1);\n\n        let p = Sent {\n            pkt_num: 4,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 3);\n        assert_eq!(r.bytes_in_flight(), 3000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_millis(30));\n\n        let p = Sent {\n            pkt_num: 5,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 4);\n        assert_eq!(r.bytes_in_flight(), 4000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_millis(30));\n        assert_eq!(r.lost_count(), 0);\n\n        // Wait for 10ms.\n        now += Duration::from_millis(10);\n\n        // PTO packets are acked.\n        let mut acked = RangeSet::default();\n        acked.insert(4..6);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 2,\n                lost_bytes: 2000,\n                acked_bytes: 2 * 1000,\n                spurious_losses: 0,\n            }\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 4);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_millis(40));\n\n        assert_eq!(r.lost_count(), 2);\n\n        // Wait 1 RTT.\n        now += r.rtt();\n\n        assert_eq!(\n            r.detect_lost_packets_for_test(packet::Epoch::Application, now),\n            (0, 0)\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n        if cc_algorithm_name == \"reno\" || cc_algorithm_name == \"cubic\" {\n            assert!(r.startup_exit().is_some());\n            assert_eq!(r.startup_exit().unwrap().reason, StartupExitReason::Loss);\n        } else {\n            assert_eq!(r.startup_exit(), None);\n        }\n    }\n\n    #[rstest]\n    fn loss_on_timer(\n        #[values(\"reno\", \"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    ) {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        let mut r = Recovery::new(&cfg);\n\n        let mut now = Instant::now();\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n\n        // Start by sending a few packets.\n        let p = Sent {\n            pkt_num: 0,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 1);\n        assert_eq!(r.bytes_in_flight(), 1000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        let p = Sent {\n            pkt_num: 1,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 2000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        let p = Sent {\n            pkt_num: 2,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 3);\n        assert_eq!(r.bytes_in_flight(), 3000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        let p = Sent {\n            pkt_num: 3,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 4);\n        assert_eq!(r.bytes_in_flight(), 4000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        // Wait for 10ms.\n        now += Duration::from_millis(10);\n\n        // Only the first 2 packets and the last one are acked.\n        let mut acked = RangeSet::default();\n        acked.insert(0..2);\n        acked.insert(3..4);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 3 * 1000,\n                spurious_losses: 0,\n            }\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 1000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_millis(10));\n        assert_eq!(r.lost_count(), 0);\n\n        // Wait until loss detection timer expires.\n        now = r.loss_detection_timer().unwrap();\n\n        // Packet is declared lost.\n        r.on_loss_detection_timeout(HandshakeStatus::default(), now, \"\");\n        assert_eq!(r.loss_probes(packet::Epoch::Application), 0);\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_micros(11250));\n\n        assert_eq!(r.lost_count(), 1);\n\n        // Wait 1 RTT.\n        now += r.rtt();\n\n        assert_eq!(\n            r.detect_lost_packets_for_test(packet::Epoch::Application, now),\n            (0, 0)\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n        if cc_algorithm_name == \"reno\" || cc_algorithm_name == \"cubic\" {\n            assert!(r.startup_exit().is_some());\n            assert_eq!(r.startup_exit().unwrap().reason, StartupExitReason::Loss);\n        } else {\n            assert_eq!(r.startup_exit(), None);\n        }\n    }\n\n    #[rstest]\n    fn loss_on_reordering(\n        #[values(\"reno\", \"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    ) {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        let mut r = Recovery::new(&cfg);\n\n        let mut now = Instant::now();\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n\n        // Start by sending a few packets.\n        //\n        // pkt number: [0, 1, 2, 3]\n        for i in 0..4 {\n            let p = test_utils::helper_packet_sent(i, now, 1000);\n            r.on_packet_sent(\n                p,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n\n            let pkt_count = (i + 1) as usize;\n            assert_eq!(r.sent_packets_len(packet::Epoch::Application), pkt_count);\n            assert_eq!(r.bytes_in_flight(), pkt_count * 1000);\n            assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n        }\n\n        // Wait for 10ms after sending.\n        now += Duration::from_millis(10);\n\n        // Recieve reordered ACKs, i.e. pkt_num [2, 3]\n        let mut acked = RangeSet::default();\n        acked.insert(2..4);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 1,\n                lost_bytes: 1000,\n                acked_bytes: 1000 * 2,\n                spurious_losses: 0,\n            }\n        );\n        // Since we only remove packets from the back to avoid compaction, the\n        // send length remains the same after receiving reordered ACKs\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 4);\n        assert_eq!(r.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);\n        assert_eq!(r.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // Wait for 10ms after receiving first set of ACKs.\n        now += Duration::from_millis(10);\n\n        // Recieve remaining ACKs, i.e. pkt_num [0, 1]\n        let mut acked = RangeSet::default();\n        acked.insert(0..2);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 1000,\n                spurious_losses: 1,\n            }\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_millis(20));\n\n        // Spurious loss.\n        assert_eq!(r.lost_count(), 1);\n        assert_eq!(r.lost_spurious_count(), 1);\n\n        // Packet threshold was increased.\n        assert_eq!(r.pkt_thresh().unwrap(), 4);\n        assert_eq!(r.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);\n\n        // Wait 1 RTT.\n        now += r.rtt();\n\n        // All packets have been ACKed so dont expect additional lost packets\n        assert_eq!(\n            r.detect_lost_packets_for_test(packet::Epoch::Application, now),\n            (0, 0)\n        );\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n\n        if cc_algorithm_name == \"reno\" || cc_algorithm_name == \"cubic\" {\n            assert!(r.startup_exit().is_some());\n            assert_eq!(r.startup_exit().unwrap().reason, StartupExitReason::Loss);\n        } else {\n            assert_eq!(r.startup_exit(), None);\n        }\n    }\n\n    // TODO: This should run agains both `congestion` and `gcongestion`.\n    // `congestion` and `gcongestion` behave differently. That might be ok\n    // given the different algorithms but it would be ideal to merge and share\n    // the logic.\n    #[rstest]\n    fn time_thresholds_on_reordering(\n        #[values(\"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    ) {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        let mut now = Instant::now();\n        let mut r = Recovery::new(&cfg);\n        assert_eq!(r.rtt(), DEFAULT_INITIAL_RTT);\n\n        // Pick time between and above thresholds for testing threshold increase.\n        //\n        //```\n        //              between_thresh_ms\n        //                         |\n        //    initial_thresh_ms    |     spurious_thresh_ms\n        //      v                  v             v\n        // --------------------------------------------------\n        //      | ................ | ..................... |\n        //            THRESH_GAP         THRESH_GAP\n        // ```\n        // \n        // Threshold gap time.\n        const THRESH_GAP: Duration = Duration::from_millis(30);\n        // Initial time theshold based on inital RTT.\n        let initial_thresh_ms =\n            DEFAULT_INITIAL_RTT.mul_f64(INITIAL_TIME_THRESHOLD);\n        // The time threshold after spurious loss.\n        let spurious_thresh_ms: Duration =\n            DEFAULT_INITIAL_RTT.mul_f64(PACKET_REORDER_TIME_THRESHOLD);\n        // Time between the two thresholds\n        let between_thresh_ms = initial_thresh_ms + THRESH_GAP;\n        assert!(between_thresh_ms > initial_thresh_ms);\n        assert!(between_thresh_ms < spurious_thresh_ms);\n        assert!(between_thresh_ms + THRESH_GAP > spurious_thresh_ms);\n\n        for i in 0..6 {\n            let send_time = now + i * between_thresh_ms;\n\n            let p = test_utils::helper_packet_sent(i.into(), send_time, 1000);\n            r.on_packet_sent(\n                p,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                send_time,\n                \"\",\n            );\n        }\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 6);\n        assert_eq!(r.bytes_in_flight(), 6 * 1000);\n        assert_eq!(r.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);\n        assert_eq!(r.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // Wait for `between_thresh_ms` after sending to trigger loss based on\n        // loss threshold.\n        now += between_thresh_ms;\n\n        // Ack packet: 1\n        //\n        // [0, 1, 2, 3, 4, 5]\n        //     ^\n        let mut acked = RangeSet::default();\n        acked.insert(1..2);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 1,\n                lost_bytes: 1000,\n                acked_bytes: 1000,\n                spurious_losses: 0,\n            }\n        );\n        assert_eq!(r.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);\n        assert_eq!(r.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // Ack packet: 0\n        //\n        // [0, 1, 2, 3, 4, 5]\n        //  ^  x\n        let mut acked = RangeSet::default();\n        acked.insert(0..1);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 0,\n                spurious_losses: 1,\n            }\n        );\n        // The time_thresh after spurious loss\n        assert_eq!(r.time_thresh(), PACKET_REORDER_TIME_THRESHOLD);\n\n        // Wait for `between_thresh_ms` after sending. However, since the\n        // threshold has increased, we do not expect loss.\n        now += between_thresh_ms;\n\n        // Ack packet: 3\n        //\n        // [2, 3, 4, 5]\n        //     ^\n        let mut acked = RangeSet::default();\n        acked.insert(3..4);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 1000,\n                spurious_losses: 0,\n            }\n        );\n\n        // Wait for and additional `plus_overhead` to trigger loss based on the\n        // new time threshold.\n        now += THRESH_GAP;\n\n        // Ack packet: 4\n        //\n        // [2, 3, 4, 5]\n        //     x  ^\n        let mut acked = RangeSet::default();\n        acked.insert(4..5);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 1,\n                lost_bytes: 1000,\n                acked_bytes: 1000,\n                spurious_losses: 0,\n            }\n        );\n    }\n\n    // TODO: Implement enable_relaxed_loss_threshold and enable this test for the\n    // congestion module.\n    #[rstest]\n    fn relaxed_thresholds_on_reordering(\n        #[values(\"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    ) {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        cfg.enable_relaxed_loss_threshold = true;\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        let mut now = Instant::now();\n        let mut r = Recovery::new(&cfg);\n        assert_eq!(r.rtt(), DEFAULT_INITIAL_RTT);\n\n        // Pick time between and above thresholds for testing threshold increase.\n        //\n        //```\n        //              between_thresh_ms\n        //                         |\n        //    initial_thresh_ms    |     spurious_thresh_ms\n        //      v                  v             v\n        // --------------------------------------------------\n        //      | ................ | ..................... |\n        //            THRESH_GAP         THRESH_GAP\n        // ```\n        // Threshold gap time.\n        const THRESH_GAP: Duration = Duration::from_millis(30);\n        // Initial time theshold based on inital RTT.\n        let initial_thresh_ms =\n            DEFAULT_INITIAL_RTT.mul_f64(INITIAL_TIME_THRESHOLD);\n        // The time threshold after spurious loss.\n        let spurious_thresh_ms: Duration =\n            DEFAULT_INITIAL_RTT.mul_f64(PACKET_REORDER_TIME_THRESHOLD);\n        // Time between the two thresholds\n        let between_thresh_ms = initial_thresh_ms + THRESH_GAP;\n        assert!(between_thresh_ms > initial_thresh_ms);\n        assert!(between_thresh_ms < spurious_thresh_ms);\n        assert!(between_thresh_ms + THRESH_GAP > spurious_thresh_ms);\n\n        for i in 0..6 {\n            let send_time = now + i * between_thresh_ms;\n\n            let p = test_utils::helper_packet_sent(i.into(), send_time, 1000);\n            r.on_packet_sent(\n                p,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                send_time,\n                \"\",\n            );\n        }\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 6);\n        assert_eq!(r.bytes_in_flight(), 6 * 1000);\n        // Intitial thresholds\n        assert_eq!(r.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);\n        assert_eq!(r.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // Wait for `between_thresh_ms` after sending to trigger loss based on\n        // loss threshold.\n        now += between_thresh_ms;\n\n        // Ack packet: 1\n        //\n        // [0, 1, 2, 3, 4, 5]\n        //     ^\n        let mut acked = RangeSet::default();\n        acked.insert(1..2);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 1,\n                lost_bytes: 1000,\n                acked_bytes: 1000,\n                spurious_losses: 0,\n            }\n        );\n        // Thresholds after 1st loss\n        assert_eq!(r.pkt_thresh().unwrap(), INITIAL_PACKET_THRESHOLD);\n        assert_eq!(r.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // Ack packet: 0\n        //\n        // [0, 1, 2, 3, 4, 5]\n        //  ^  x\n        let mut acked = RangeSet::default();\n        acked.insert(0..1);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 0,\n                spurious_losses: 1,\n            }\n        );\n        // Thresholds after 1st spurious loss\n        //\n        // Packet threshold should be disabled. Time threshold overhead should\n        // stay the same.\n        assert_eq!(r.pkt_thresh(), None);\n        assert_eq!(r.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // Set now to send time of packet 2 so we can trigger spurious loss for\n        // packet 2.\n        now += between_thresh_ms;\n        // Then wait for `between_thresh_ms` after sending packet 2 to trigger\n        // loss. Since the time threshold has NOT increased, expect a\n        // loss.\n        now += between_thresh_ms;\n\n        // Ack packet: 3\n        //\n        // [2, 3, 4, 5]\n        //     ^\n        let mut acked = RangeSet::default();\n        acked.insert(3..4);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 1,\n                lost_bytes: 1000,\n                acked_bytes: 1000,\n                spurious_losses: 0,\n            }\n        );\n        // Thresholds after 2nd loss.\n        assert_eq!(r.pkt_thresh(), None);\n        assert_eq!(r.time_thresh(), INITIAL_TIME_THRESHOLD);\n\n        // Wait for and additional `plus_overhead` to trigger loss based on the\n        // new time threshold.\n        // now += THRESH_GAP;\n\n        // Ack packet: 2\n        //\n        // [2, 3, 4, 5]\n        //  ^  x\n        let mut acked = RangeSet::default();\n        acked.insert(2..3);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 0,\n                spurious_losses: 1,\n            }\n        );\n        // Thresholds after 2nd spurious loss.\n        //\n        // Time threshold overhead should double.\n        assert_eq!(r.pkt_thresh(), None);\n        let double_time_thresh_overhead =\n            1.0 + 2.0 * INITIAL_TIME_THRESHOLD_OVERHEAD;\n        assert_eq!(r.time_thresh(), double_time_thresh_overhead);\n    }\n\n    #[rstest]\n    fn pacing(\n        #[values(\"reno\", \"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n        #[values(false, true)] time_sent_set_to_now: bool,\n    ) {\n        let pacing_enabled = cc_algorithm_name == \"bbr2\" ||\n            cc_algorithm_name == \"bbr2_gcongestion\";\n\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        #[cfg(feature = \"internal\")]\n        cfg.set_custom_bbr_params(BbrParams {\n            time_sent_set_to_now: Some(time_sent_set_to_now),\n            ..Default::default()\n        });\n\n        let mut r = Recovery::new(&cfg);\n\n        let mut now = Instant::now();\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n\n        // send out first packet burst (a full initcwnd).\n        for i in 0..10 {\n            let p = Sent {\n                pkt_num: i,\n                frames: smallvec![],\n                time_sent: now,\n                time_acked: None,\n                time_lost: None,\n                size: 1200,\n                ack_eliciting: true,\n                in_flight: true,\n                delivered: 0,\n                delivered_time: now,\n                first_sent_time: now,\n                is_app_limited: false,\n                tx_in_flight: 0,\n                lost: 0,\n                has_data: true,\n                is_pmtud_probe: false,\n            };\n\n            r.on_packet_sent(\n                p,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n        }\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 10);\n        assert_eq!(r.bytes_in_flight(), 12000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        if !pacing_enabled {\n            assert_eq!(r.pacing_rate(), 0);\n        } else {\n            assert_eq!(r.pacing_rate(), 103963);\n        }\n        assert_eq!(r.get_packet_send_time(now), now);\n\n        assert_eq!(r.cwnd(), 12000);\n        assert_eq!(r.cwnd_available(), 0);\n\n        // Wait 50ms for ACK.\n        let initial_rtt = Duration::from_millis(50);\n        now += initial_rtt;\n\n        let mut acked = RangeSet::default();\n        acked.insert(0..10);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                10,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 12000,\n                spurious_losses: 0,\n            }\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), initial_rtt);\n        assert_eq!(r.min_rtt(), Some(initial_rtt));\n        assert_eq!(r.rtt(), initial_rtt);\n\n        // 10 MSS increased due to acks.\n        assert_eq!(r.cwnd(), 12000 + 1200 * 10);\n\n        // Send the second packet burst.\n        let p = Sent {\n            pkt_num: 10,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 6000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: true,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 1);\n        assert_eq!(r.bytes_in_flight(), 6000);\n        assert_eq!(r.bytes_in_flight_duration(), initial_rtt);\n\n        if !pacing_enabled {\n            // Pacing is disabled.\n            assert_eq!(r.get_packet_send_time(now), now);\n        } else {\n            // Pacing is done from the beginning.\n            assert_ne!(r.get_packet_send_time(now), now);\n        }\n\n        // Send the third and fourth packet bursts together.\n        let p = Sent {\n            pkt_num: 11,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 6000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: true,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 12000);\n        assert_eq!(r.bytes_in_flight_duration(), initial_rtt);\n\n        // Send the fourth packet burst.\n        let p = Sent {\n            pkt_num: 12,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: true,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 3);\n        assert_eq!(r.bytes_in_flight(), 13000);\n        assert_eq!(r.bytes_in_flight_duration(), initial_rtt);\n\n        // We pace this outgoing packet. as all conditions for pacing\n        // are passed.\n        let pacing_rate = if pacing_enabled {\n            let cwnd_gain: f64 = 2.0;\n            // Adjust for cwnd_gain.  BW estimate was made before the CWND\n            // increase.\n            let bw = r.cwnd() as f64 / cwnd_gain / initial_rtt.as_secs_f64();\n            bw as u64\n        } else {\n            0\n        };\n        assert_eq!(r.pacing_rate(), pacing_rate);\n\n        let scale_factor = if pacing_enabled {\n            // For bbr2_gcongestion, send time is almost 13000 / pacing_rate.\n            // Don't know where 13000 comes from.\n            1.08333332\n        } else {\n            1.0\n        };\n        assert_eq!(\n            r.get_packet_send_time(now) - now,\n            if pacing_enabled {\n                Duration::from_secs_f64(\n                    scale_factor * 12000.0 / pacing_rate as f64,\n                )\n            } else {\n                Duration::ZERO\n            }\n        );\n        assert_eq!(r.startup_exit(), None);\n\n        let reduced_rtt = Duration::from_millis(40);\n        now += reduced_rtt;\n\n        let mut acked = RangeSet::default();\n        acked.insert(10..11);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                0,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 6000,\n                spurious_losses: 0,\n            }\n        );\n\n        let expected_srtt = (7 * initial_rtt + reduced_rtt) / 8;\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 7000);\n        assert_eq!(r.bytes_in_flight_duration(), initial_rtt + reduced_rtt);\n        assert_eq!(r.min_rtt(), Some(reduced_rtt));\n        assert_eq!(r.rtt(), expected_srtt);\n\n        let mut acked = RangeSet::default();\n        acked.insert(11..12);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                0,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 6000,\n                spurious_losses: 0,\n            }\n        );\n\n        // When enabled, the pacer adds a 25msec delay to the packet\n        // sends which will be applied to the sent times tracked by\n        // the recovery module, bringing down RTT to 15msec.\n        let expected_min_rtt = if pacing_enabled &&\n            !time_sent_set_to_now &&\n            cfg!(feature = \"internal\")\n        {\n            reduced_rtt - Duration::from_millis(25)\n        } else {\n            reduced_rtt\n        };\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 1);\n        assert_eq!(r.bytes_in_flight(), 1000);\n        assert_eq!(r.bytes_in_flight_duration(), initial_rtt + reduced_rtt);\n        assert_eq!(r.min_rtt(), Some(expected_min_rtt));\n\n        let expected_srtt = (7 * expected_srtt + expected_min_rtt) / 8;\n        assert_eq!(r.rtt(), expected_srtt);\n\n        let mut acked = RangeSet::default();\n        acked.insert(12..13);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                0,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 1000,\n                spurious_losses: 0,\n            }\n        );\n\n        // Pacer adds 50msec delay to the second packet, resulting in\n        // an effective RTT of 0.\n        let expected_min_rtt = if pacing_enabled &&\n            !time_sent_set_to_now &&\n            cfg!(feature = \"internal\")\n        {\n            Duration::from_millis(0)\n        } else {\n            reduced_rtt\n        };\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), initial_rtt + reduced_rtt);\n        assert_eq!(r.min_rtt(), Some(expected_min_rtt));\n\n        let expected_srtt = (7 * expected_srtt + expected_min_rtt) / 8;\n        assert_eq!(r.rtt(), expected_srtt);\n    }\n\n    #[rstest]\n    // initial_cwnd / first_rtt == initial_pacing_rate.  Pacing is 1.0 * bw before\n    // and after.\n    #[case::bw_estimate_equal_after_first_rtt(1.0, 1.0)]\n    // initial_cwnd / first_rtt < initial_pacing_rate.  Pacing decreases from 2 *\n    // bw to 1.0 * bw.\n    #[case::bw_estimate_decrease_after_first_rtt(2.0, 1.0)]\n    // initial_cwnd / first_rtt > initial_pacing_rate from 0.5 * bw to 1.0 * bw.\n    // Initial pacing remains 0.5 * bw because the initial_pacing_rate parameter\n    // is used an upper bound for the pacing rate after the first RTT.\n    // Pacing rate after the first ACK should be:\n    // min(initial_pacing_rate_bytes_per_second, init_cwnd / first_rtt)\n    #[case::bw_estimate_increase_after_first_rtt(0.5, 0.5)]\n    #[cfg(feature = \"internal\")]\n    fn initial_pacing_rate_override(\n        #[case] initial_multipler: f64, #[case] expected_multiplier: f64,\n    ) {\n        let rtt = Duration::from_millis(50);\n        let bw = Bandwidth::from_bytes_and_time_delta(12000, rtt);\n        let initial_pacing_rate_hint = bw * initial_multipler;\n        let expected_pacing_with_rtt_measurement = bw * expected_multiplier;\n\n        let cc_algorithm_name = \"bbr2_gcongestion\";\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n        cfg.set_custom_bbr_params(BbrParams {\n            initial_pacing_rate_bytes_per_second: Some(\n                initial_pacing_rate_hint.to_bytes_per_second(),\n            ),\n            ..Default::default()\n        });\n\n        let mut r = Recovery::new(&cfg);\n\n        let mut now = Instant::now();\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n\n        // send some packets.\n        for i in 0..2 {\n            let p = test_utils::helper_packet_sent(i, now, 1200);\n            r.on_packet_sent(\n                p,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n        }\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 2400);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        // Initial pacing rate matches the override value.\n        assert_eq!(\n            r.pacing_rate(),\n            initial_pacing_rate_hint.to_bytes_per_second()\n        );\n        assert_eq!(r.get_packet_send_time(now), now);\n\n        assert_eq!(r.cwnd(), 12000);\n        assert_eq!(r.cwnd_available(), 9600);\n\n        // Wait 1 rtt for ACK.\n        now += rtt;\n\n        let mut acked = RangeSet::default();\n        acked.insert(0..2);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                10,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 2400,\n                spurious_losses: 0,\n            }\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), rtt);\n        assert_eq!(r.rtt(), rtt);\n\n        // Pacing rate is recalculated based on initial cwnd when the\n        // first RTT estimate is available.\n        assert_eq!(\n            r.pacing_rate(),\n            expected_pacing_with_rtt_measurement.to_bytes_per_second()\n        );\n    }\n\n    #[rstest]\n    fn validate_ack_range_on_ack_received(\n        #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    ) {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        cfg.set_cc_algorithm_name(cc_algorithm_name).unwrap();\n\n        let epoch = packet::Epoch::Application;\n        let mut r = Recovery::new(&cfg);\n        let mut now = Instant::now();\n        assert_eq!(r.sent_packets_len(epoch), 0);\n\n        // Send 4 packets\n        let pkt_size = 1000;\n        let pkt_count = 4;\n        for pkt_num in 0..pkt_count {\n            let sent = test_utils::helper_packet_sent(pkt_num, now, pkt_size);\n            r.on_packet_sent(sent, epoch, HandshakeStatus::default(), now, \"\");\n        }\n        assert_eq!(r.sent_packets_len(epoch), pkt_count as usize);\n        assert_eq!(r.bytes_in_flight(), pkt_count as usize * pkt_size);\n        assert!(r.get_largest_acked_on_epoch(epoch).is_none());\n        assert_eq!(r.largest_sent_pkt_num_on_path(epoch).unwrap(), 3);\n\n        // Wait for 10ms.\n        now += Duration::from_millis(10);\n\n        // ACK 2 packets\n        let mut acked = RangeSet::default();\n        acked.insert(0..2);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                epoch,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 2 * 1000,\n                spurious_losses: 0,\n            }\n        );\n\n        assert_eq!(r.sent_packets_len(epoch), 2);\n        assert_eq!(r.bytes_in_flight(), 2 * 1000);\n\n        assert_eq!(r.get_largest_acked_on_epoch(epoch).unwrap(), 1);\n        assert_eq!(r.largest_sent_pkt_num_on_path(epoch).unwrap(), 3);\n\n        // ACK large range\n        let mut acked = RangeSet::default();\n        acked.insert(0..10);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                epoch,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 2 * 1000,\n                spurious_losses: 0,\n            }\n        );\n        assert_eq!(r.sent_packets_len(epoch), 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n\n        assert_eq!(r.get_largest_acked_on_epoch(epoch).unwrap(), 3);\n        assert_eq!(r.largest_sent_pkt_num_on_path(epoch).unwrap(), 3);\n    }\n\n    #[rstest]\n    fn pmtud_loss_on_timer(\n        #[values(\"reno\", \"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    ) {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        let mut r = Recovery::new(&cfg);\n        assert_eq!(r.cwnd(), 12000);\n\n        let mut now = Instant::now();\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n\n        // Start by sending a few packets.\n        let p = Sent {\n            pkt_num: 0,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n\n        assert_eq!(r.in_flight_count(packet::Epoch::Application), 1);\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 1);\n        assert_eq!(r.bytes_in_flight(), 1000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        let p = Sent {\n            pkt_num: 1,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: true,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n\n        assert_eq!(r.in_flight_count(packet::Epoch::Application), 2);\n\n        let p = Sent {\n            pkt_num: 2,\n            frames: smallvec![],\n            time_sent: now,\n            time_acked: None,\n            time_lost: None,\n            size: 1000,\n            ack_eliciting: true,\n            in_flight: true,\n            delivered: 0,\n            delivered_time: now,\n            first_sent_time: now,\n            is_app_limited: false,\n            tx_in_flight: 0,\n            lost: 0,\n            has_data: false,\n            is_pmtud_probe: false,\n        };\n\n        r.on_packet_sent(\n            p,\n            packet::Epoch::Application,\n            HandshakeStatus::default(),\n            now,\n            \"\",\n        );\n\n        assert_eq!(r.in_flight_count(packet::Epoch::Application), 3);\n\n        // Wait for 10ms.\n        now += Duration::from_millis(10);\n\n        // Only the first  packets and the last one are acked.\n        let mut acked = RangeSet::default();\n        acked.insert(0..1);\n        acked.insert(2..3);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 2 * 1000,\n                spurious_losses: 0,\n            }\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.bytes_in_flight(), 1000);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_millis(10));\n        assert_eq!(r.lost_count(), 0);\n\n        // Wait until loss detection timer expires.\n        now = r.loss_detection_timer().unwrap();\n\n        // Packet is declared lost.\n        r.on_loss_detection_timeout(HandshakeStatus::default(), now, \"\");\n        assert_eq!(r.loss_probes(packet::Epoch::Application), 0);\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 2);\n        assert_eq!(r.in_flight_count(packet::Epoch::Application), 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_micros(11250));\n        assert_eq!(r.cwnd(), 12000);\n\n        assert_eq!(r.lost_count(), 0);\n\n        // Wait 1 RTT.\n        now += r.rtt();\n\n        assert_eq!(\n            r.detect_lost_packets_for_test(packet::Epoch::Application, now),\n            (0, 0)\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n        assert_eq!(r.in_flight_count(packet::Epoch::Application), 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::from_micros(11250));\n        assert_eq!(r.lost_count(), 0);\n        assert_eq!(r.startup_exit(), None);\n    }\n\n    // Modeling delivery_rate for gcongestion is non-trivial so we only test the\n    // congestion specific algorithms.\n    #[rstest]\n    fn congestion_delivery_rate(\n        #[values(\"reno\", \"cubic\", \"bbr2\")] cc_algorithm_name: &str,\n    ) {\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        let mut r = Recovery::new(&cfg);\n        assert_eq!(r.cwnd(), 12000);\n\n        let now = Instant::now();\n\n        let mut total_bytes_sent = 0;\n        for pn in 0..10 {\n            // Start by sending a few packets.\n            let bytes = 1000;\n            let sent = test_utils::helper_packet_sent(pn, now, bytes);\n            r.on_packet_sent(\n                sent,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n\n            total_bytes_sent += bytes;\n        }\n\n        // Ack\n        let interval = Duration::from_secs(10);\n        let mut acked = RangeSet::default();\n        acked.insert(0..10);\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                25,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now + interval,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: total_bytes_sent,\n                spurious_losses: 0,\n            }\n        );\n        assert_eq!(r.delivery_rate().to_bytes_per_second(), 1000);\n        assert_eq!(r.min_rtt().unwrap(), interval);\n        // delivery rate should be in units bytes/sec\n        assert_eq!(\n            total_bytes_sent as u64 / interval.as_secs(),\n            r.delivery_rate().to_bytes_per_second()\n        );\n        assert_eq!(r.startup_exit(), None);\n    }\n\n    #[rstest]\n    fn acks_with_no_retransmittable_data(\n        #[values(\"reno\", \"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    ) {\n        let rtt = Duration::from_millis(100);\n\n        let mut cfg = Config::new(crate::PROTOCOL_VERSION).unwrap();\n        assert_eq!(cfg.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n        let mut r = Recovery::new(&cfg);\n\n        let mut now = Instant::now();\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n\n        let mut next_packet = 0;\n        // send some packets.\n        for _ in 0..3 {\n            let p = test_utils::helper_packet_sent(next_packet, now, 1200);\n            next_packet += 1;\n            r.on_packet_sent(\n                p,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n        }\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 3);\n        assert_eq!(r.bytes_in_flight(), 3600);\n        assert_eq!(r.bytes_in_flight_duration(), Duration::ZERO);\n\n        assert_eq!(\n            r.pacing_rate(),\n            if cc_algorithm_name == \"bbr2_gcongestion\" {\n                103963\n            } else {\n                0\n            },\n        );\n        assert_eq!(r.get_packet_send_time(now), now);\n        assert_eq!(r.cwnd(), 12000);\n        assert_eq!(r.cwnd_available(), 8400);\n\n        // Wait 1 rtt for ACK.\n        now += rtt;\n\n        let mut acked = RangeSet::default();\n        acked.insert(0..3);\n\n        assert_eq!(\n            r.on_ack_received(\n                &acked,\n                10,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                None,\n                \"\",\n            )\n            .unwrap(),\n            OnAckReceivedOutcome {\n                lost_packets: 0,\n                lost_bytes: 0,\n                acked_bytes: 3600,\n                spurious_losses: 0,\n            }\n        );\n\n        assert_eq!(r.sent_packets_len(packet::Epoch::Application), 0);\n        assert_eq!(r.bytes_in_flight(), 0);\n        assert_eq!(r.bytes_in_flight_duration(), rtt);\n        assert_eq!(r.rtt(), rtt);\n\n        // Pacing rate is recalculated based on initial cwnd when the\n        // first RTT estimate is available.\n        assert_eq!(\n            r.pacing_rate(),\n            if cc_algorithm_name == \"bbr2_gcongestion\" {\n                120000\n            } else {\n                0\n            },\n        );\n\n        // Send some no \"in_flight\" packets\n        for iter in 3..1000 {\n            let mut p = test_utils::helper_packet_sent(next_packet, now, 1200);\n            // `in_flight = false` marks packets as if they only contained ACK\n            // frames.\n            p.in_flight = false;\n            next_packet += 1;\n            r.on_packet_sent(\n                p,\n                packet::Epoch::Application,\n                HandshakeStatus::default(),\n                now,\n                \"\",\n            );\n\n            now += rtt;\n\n            let mut acked = RangeSet::default();\n            acked.insert(iter..(iter + 1));\n\n            assert_eq!(\n                r.on_ack_received(\n                    &acked,\n                    10,\n                    packet::Epoch::Application,\n                    HandshakeStatus::default(),\n                    now,\n                    None,\n                    \"\",\n                )\n                .unwrap(),\n                OnAckReceivedOutcome {\n                    lost_packets: 0,\n                    lost_bytes: 0,\n                    acked_bytes: 0,\n                    spurious_losses: 0,\n                }\n            );\n\n            // Verify that connection has not exited startup.\n            assert_eq!(r.startup_exit(), None, \"{iter}\");\n\n            // Unchanged metrics.\n            assert_eq!(\n                r.sent_packets_len(packet::Epoch::Application),\n                0,\n                \"{iter}\"\n            );\n            assert_eq!(r.bytes_in_flight(), 0, \"{iter}\");\n            assert_eq!(r.bytes_in_flight_duration(), rtt, \"{iter}\");\n            assert_eq!(\n                r.pacing_rate(),\n                if cc_algorithm_name == \"bbr2_gcongestion\" ||\n                    cc_algorithm_name == \"bbr2\"\n                {\n                    120000\n                } else {\n                    0\n                },\n                \"{iter}\"\n            );\n        }\n    }\n}\n\nmod bandwidth;\nmod bytes_in_flight;\nmod congestion;\nmod gcongestion;\nmod rtt;\n"
  },
  {
    "path": "quiche/src/recovery/rtt.rs",
    "content": "// Copyright (C) 2024, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse crate::minmax::Minmax;\nuse crate::recovery::GRANULARITY;\n\npub(crate) const RTT_WINDOW: Duration = Duration::from_secs(300);\n\npub struct RttStats {\n    pub(super) latest_rtt: Duration,\n\n    max_rtt: Duration,\n\n    pub(super) smoothed_rtt: Duration,\n\n    pub(super) rttvar: Duration,\n\n    pub(super) min_rtt: Minmax<Duration>,\n\n    pub(super) max_ack_delay: Duration,\n\n    pub(super) has_first_rtt_sample: bool,\n}\n\nimpl std::fmt::Debug for RttStats {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        f.debug_struct(\"RttStats\")\n            .field(\"lastest_rtt\", &self.latest_rtt)\n            .field(\"srtt\", &self.smoothed_rtt)\n            .field(\"minrtt\", &*self.min_rtt)\n            .field(\"rttvar\", &self.rttvar)\n            .finish()\n    }\n}\n\nimpl RttStats {\n    pub(crate) fn new(initial_rtt: Duration, max_ack_delay: Duration) -> Self {\n        RttStats {\n            latest_rtt: Duration::ZERO,\n            min_rtt: Minmax::new(initial_rtt),\n            smoothed_rtt: initial_rtt,\n            max_rtt: initial_rtt,\n            rttvar: initial_rtt / 2,\n            has_first_rtt_sample: false,\n            max_ack_delay,\n        }\n    }\n\n    pub(crate) fn update_rtt(\n        &mut self, latest_rtt: Duration, mut ack_delay: Duration, now: Instant,\n        handshake_confirmed: bool,\n    ) {\n        self.latest_rtt = latest_rtt;\n\n        if !self.has_first_rtt_sample {\n            self.min_rtt.reset(now, latest_rtt);\n            self.smoothed_rtt = latest_rtt;\n            self.max_rtt = latest_rtt;\n            self.rttvar = latest_rtt / 2;\n            self.has_first_rtt_sample = true;\n            return;\n        }\n\n        // min_rtt ignores acknowledgment delay.\n        self.min_rtt.running_min(RTT_WINDOW, now, latest_rtt);\n\n        self.max_rtt = self.max_rtt.max(latest_rtt);\n\n        // Limit ack_delay by max_ack_delay after handshake confirmation.\n        if handshake_confirmed {\n            ack_delay = ack_delay.min(self.max_ack_delay);\n        }\n\n        // Adjust for acknowledgment delay if plausible.\n        let mut adjusted_rtt = latest_rtt;\n        if latest_rtt >= *self.min_rtt + ack_delay {\n            adjusted_rtt = latest_rtt - ack_delay;\n        }\n\n        self.rttvar = self.rttvar * 3 / 4 +\n            Duration::from_nanos(\n                self.smoothed_rtt\n                    .as_nanos()\n                    .abs_diff(adjusted_rtt.as_nanos()) as u64 /\n                    4,\n            );\n\n        self.smoothed_rtt = self.smoothed_rtt * 7 / 8 + adjusted_rtt / 8;\n    }\n\n    pub(crate) fn rtt(&self) -> Duration {\n        self.smoothed_rtt\n    }\n\n    #[allow(dead_code)]\n    pub(crate) fn latest_rtt(&self) -> Duration {\n        self.latest_rtt\n    }\n\n    pub(crate) fn rttvar(&self) -> Duration {\n        self.rttvar\n    }\n\n    pub(crate) fn min_rtt(&self) -> Option<Duration> {\n        if self.has_first_rtt_sample {\n            Some(*self.min_rtt)\n        } else {\n            None\n        }\n    }\n\n    pub(crate) fn max_rtt(&self) -> Option<Duration> {\n        if self.has_first_rtt_sample {\n            Some(self.max_rtt)\n        } else {\n            None\n        }\n    }\n\n    pub(crate) fn loss_delay(&self, time_thresh: f64) -> Duration {\n        self.latest_rtt\n            .max(self.smoothed_rtt)\n            .mul_f64(time_thresh)\n            .max(GRANULARITY)\n    }\n}\n"
  },
  {
    "path": "quiche/src/stream/mod.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::cmp;\n\nuse std::sync::Arc;\n\nuse std::collections::hash_map;\nuse std::collections::HashMap;\nuse std::collections::HashSet;\n\nuse intrusive_collections::intrusive_adapter;\nuse intrusive_collections::KeyAdapter;\nuse intrusive_collections::RBTree;\nuse intrusive_collections::RBTreeAtomicLink;\n\nuse smallvec::SmallVec;\n\nuse crate::buffers::DefaultBufFactory;\nuse crate::BufFactory;\nuse crate::Error;\nuse crate::Result;\n\nconst DEFAULT_URGENCY: u8 = 127;\n\n// The default size of the receiver stream flow control window.\nconst DEFAULT_STREAM_WINDOW: u64 = 32 * 1024;\n\n/// The maximum size of the receiver stream flow control window.\npub const MAX_STREAM_WINDOW: u64 = 16 * 1024 * 1024;\n\n/// A simple no-op hasher for Stream IDs.\n///\n/// The QUIC protocol and quiche library guarantees stream ID uniqueness, so\n/// we can save effort by avoiding using a more complicated algorithm.\n#[derive(Default)]\npub struct StreamIdHasher {\n    id: u64,\n}\n\n/// Return value type of `RecvBuf::reset()`\n#[derive(Debug, PartialEq, Clone, Copy)]\npub struct RecvBufResetReturn {\n    /// Returns the difference between the previous max_data offset\n    /// received and the final size reported by the reset\n    pub max_data_delta: u64,\n\n    /// The amount of flow control credit that should be returned to the\n    /// connection level flow control.\n    pub consumed_flowcontrol: u64,\n}\n\nimpl RecvBufResetReturn {\n    pub fn zero() -> Self {\n        Self {\n            max_data_delta: 0,\n            consumed_flowcontrol: 0,\n        }\n    }\n}\n\n/// Action to perform when reading from a stream's receive buffer.\npub enum RecvAction<'a> {\n    /// Emit data by copying it into the provided buffer.\n    Emit { out: &'a mut [u8] },\n    /// Discard up to the specified number of bytes without copying.\n    Discard { len: usize },\n}\n\nimpl std::hash::Hasher for StreamIdHasher {\n    #[inline]\n    fn finish(&self) -> u64 {\n        self.id\n    }\n\n    #[inline]\n    fn write_u64(&mut self, id: u64) {\n        self.id = id;\n    }\n\n    #[inline]\n    fn write(&mut self, _: &[u8]) {\n        // We need a default write() for the trait but stream IDs will always\n        // be a u64 so we just delegate to write_u64.\n        unimplemented!()\n    }\n}\n\ntype BuildStreamIdHasher = std::hash::BuildHasherDefault<StreamIdHasher>;\n\npub type StreamIdHashMap<V> = HashMap<u64, V, BuildStreamIdHasher>;\npub type StreamIdHashSet = HashSet<u64, BuildStreamIdHasher>;\n\n/// Keeps track of QUIC streams and enforces stream limits.\n#[derive(Default)]\npub struct StreamMap<F: BufFactory = DefaultBufFactory> {\n    /// Map of streams indexed by stream ID.\n    streams: StreamIdHashMap<Stream<F>>,\n\n    /// Set of streams that were completed and garbage collected.\n    ///\n    /// Instead of keeping the full stream state forever, we collect completed\n    /// streams to save memory, but we still need to keep track of previously\n    /// created streams, to prevent peers from re-creating them.\n    collected: StreamIdHashSet,\n\n    /// Peer's maximum bidirectional stream count limit.\n    peer_max_streams_bidi: u64,\n\n    /// Peer's maximum unidirectional stream count limit.\n    peer_max_streams_uni: u64,\n\n    /// The total number of bidirectional streams opened by the peer.\n    peer_opened_streams_bidi: u64,\n\n    /// The total number of unidirectional streams opened by the peer.\n    peer_opened_streams_uni: u64,\n\n    /// Local maximum bidirectional stream count limit.\n    local_max_streams_bidi: u64,\n    local_max_streams_bidi_next: u64,\n\n    /// Initial maximum bidirectional stream count.\n    initial_max_streams_bidi: u64,\n\n    /// Local maximum unidirectional stream count limit.\n    local_max_streams_uni: u64,\n    local_max_streams_uni_next: u64,\n\n    /// Initial maximum unidirectional stream count.\n    initial_max_streams_uni: u64,\n\n    /// The total number of bidirectional streams opened by the local endpoint.\n    local_opened_streams_bidi: u64,\n\n    /// The total number of unidirectional streams opened by the local endpoint.\n    local_opened_streams_uni: u64,\n\n    /// Queue of stream IDs corresponding to streams that have buffered data\n    /// ready to be sent to the peer. This also implies that the stream has\n    /// enough flow control credits to send at least some of that data.\n    flushable: RBTree<StreamFlushablePriorityAdapter>,\n\n    /// Set of stream IDs corresponding to streams that have outstanding data\n    /// to read. This is used to generate a `StreamIter` of streams without\n    /// having to iterate over the full list of streams.\n    pub readable: RBTree<StreamReadablePriorityAdapter>,\n\n    /// Set of stream IDs corresponding to streams that have enough flow control\n    /// capacity to be written to, and is not finished. This is used to generate\n    /// a `StreamIter` of streams without having to iterate over the full list\n    /// of streams.\n    pub writable: RBTree<StreamWritablePriorityAdapter>,\n\n    /// Set of stream IDs corresponding to streams that are almost out of flow\n    /// control credit and need to send MAX_STREAM_DATA. This is used to\n    /// generate a `StreamIter` of streams without having to iterate over the\n    /// full list of streams.\n    almost_full: StreamIdHashSet,\n\n    /// Set of stream IDs corresponding to streams that are blocked. The value\n    /// of the map elements represents the offset of the stream at which the\n    /// blocking occurred.\n    blocked: StreamIdHashMap<u64>,\n\n    /// Set of stream IDs corresponding to streams that are reset. The value\n    /// of the map elements is a tuple of the error code and final size values\n    /// to include in the RESET_STREAM frame.\n    reset: StreamIdHashMap<(u64, u64)>,\n\n    /// Set of stream IDs corresponding to streams that are shutdown on the\n    /// receive side, and need to send a STOP_SENDING frame. The value of the\n    /// map elements is the error code to include in the STOP_SENDING frame.\n    stopped: StreamIdHashMap<u64>,\n\n    /// The maximum size of a stream window.\n    max_stream_window: u64,\n}\n\nimpl<F: BufFactory> StreamMap<F> {\n    pub fn new(\n        max_streams_bidi: u64, max_streams_uni: u64, max_stream_window: u64,\n    ) -> Self {\n        StreamMap {\n            local_max_streams_bidi: max_streams_bidi,\n            local_max_streams_bidi_next: max_streams_bidi,\n            initial_max_streams_bidi: max_streams_bidi,\n\n            local_max_streams_uni: max_streams_uni,\n            local_max_streams_uni_next: max_streams_uni,\n            initial_max_streams_uni: max_streams_uni,\n\n            max_stream_window,\n\n            ..StreamMap::default()\n        }\n    }\n\n    /// Returns the stream with the given ID if it exists.\n    pub fn get(&self, id: u64) -> Option<&Stream<F>> {\n        self.streams.get(&id)\n    }\n\n    /// Returns the mutable stream with the given ID if it exists.\n    pub fn get_mut(&mut self, id: u64) -> Option<&mut Stream<F>> {\n        self.streams.get_mut(&id)\n    }\n\n    /// Returns the mutable stream with the given ID if it exists, or creates\n    /// a new one otherwise.\n    ///\n    /// The `local` parameter indicates whether the stream's creation was\n    /// requested by the local application rather than the peer, and is\n    /// used to validate the requested stream ID, and to select the initial\n    /// flow control values from the local and remote transport parameters\n    /// (also passed as arguments).\n    ///\n    /// This also takes care of enforcing both local and the peer's stream\n    /// count limits. If one of these limits is violated, the `StreamLimit`\n    /// error is returned.\n    pub(crate) fn get_or_create(\n        &mut self, id: u64, local_params: &crate::TransportParams,\n        peer_params: &crate::TransportParams, local: bool, is_server: bool,\n    ) -> Result<&mut Stream<F>> {\n        let (stream, is_new_and_writable) = match self.streams.entry(id) {\n            hash_map::Entry::Vacant(v) => {\n                // Stream has already been closed and garbage collected.\n                if self.collected.contains(&id) {\n                    return Err(Error::Done);\n                }\n\n                if local != is_local(id, is_server) {\n                    return Err(Error::InvalidStreamState(id));\n                }\n\n                let (max_rx_data, max_tx_data) = match (local, is_bidi(id)) {\n                    // Locally-initiated bidirectional stream.\n                    (true, true) => (\n                        local_params.initial_max_stream_data_bidi_local,\n                        peer_params.initial_max_stream_data_bidi_remote,\n                    ),\n\n                    // Locally-initiated unidirectional stream.\n                    (true, false) => (0, peer_params.initial_max_stream_data_uni),\n\n                    // Remotely-initiated bidirectional stream.\n                    (false, true) => (\n                        local_params.initial_max_stream_data_bidi_remote,\n                        peer_params.initial_max_stream_data_bidi_local,\n                    ),\n\n                    // Remotely-initiated unidirectional stream.\n                    (false, false) =>\n                        (local_params.initial_max_stream_data_uni, 0),\n                };\n\n                // The two least significant bits from a stream id identify the\n                // type of stream. Truncate those bits to get the sequence for\n                // that stream type.\n                let stream_sequence = id >> 2;\n\n                // Enforce stream count limits.\n                match (is_local(id, is_server), is_bidi(id)) {\n                    (true, true) => {\n                        let n = cmp::max(\n                            self.local_opened_streams_bidi,\n                            stream_sequence + 1,\n                        );\n\n                        if n > self.peer_max_streams_bidi {\n                            return Err(Error::StreamLimit);\n                        }\n\n                        self.local_opened_streams_bidi = n;\n                    },\n\n                    (true, false) => {\n                        let n = cmp::max(\n                            self.local_opened_streams_uni,\n                            stream_sequence + 1,\n                        );\n\n                        if n > self.peer_max_streams_uni {\n                            return Err(Error::StreamLimit);\n                        }\n\n                        self.local_opened_streams_uni = n;\n                    },\n\n                    (false, true) => {\n                        let n = cmp::max(\n                            self.peer_opened_streams_bidi,\n                            stream_sequence + 1,\n                        );\n\n                        if n > self.local_max_streams_bidi {\n                            return Err(Error::StreamLimit);\n                        }\n\n                        self.peer_opened_streams_bidi = n;\n                    },\n\n                    (false, false) => {\n                        let n = cmp::max(\n                            self.peer_opened_streams_uni,\n                            stream_sequence + 1,\n                        );\n\n                        if n > self.local_max_streams_uni {\n                            return Err(Error::StreamLimit);\n                        }\n\n                        self.peer_opened_streams_uni = n;\n                    },\n                };\n\n                let s = Stream::new(\n                    id,\n                    max_rx_data,\n                    max_tx_data,\n                    is_bidi(id),\n                    local,\n                    self.max_stream_window,\n                );\n\n                let is_writable = s.is_writable();\n\n                (v.insert(s), is_writable)\n            },\n\n            hash_map::Entry::Occupied(v) => (v.into_mut(), false),\n        };\n\n        // Newly created stream might already be writable due to initial flow\n        // control limits.\n        if is_new_and_writable {\n            self.writable.insert(Arc::clone(&stream.priority_key));\n        }\n\n        Ok(stream)\n    }\n\n    /// Adds the stream ID to the readable streams set.\n    ///\n    /// If the stream was already in the list, this does nothing.\n    pub fn insert_readable(&mut self, priority_key: &Arc<StreamPriorityKey>) {\n        if !priority_key.readable.is_linked() {\n            self.readable.insert(Arc::clone(priority_key));\n        }\n    }\n\n    /// Removes the stream ID from the readable streams set.\n    pub fn remove_readable(&mut self, priority_key: &Arc<StreamPriorityKey>) {\n        if !priority_key.readable.is_linked() {\n            return;\n        }\n\n        let mut c = {\n            let ptr = Arc::as_ptr(priority_key);\n            unsafe { self.readable.cursor_mut_from_ptr(ptr) }\n        };\n\n        c.remove();\n    }\n\n    /// Adds the stream ID to the writable streams set.\n    ///\n    /// This should also be called anytime a new stream is created, in addition\n    /// to when an existing stream becomes writable.\n    ///\n    /// If the stream was already in the list, this does nothing.\n    pub fn insert_writable(&mut self, priority_key: &Arc<StreamPriorityKey>) {\n        if !priority_key.writable.is_linked() {\n            self.writable.insert(Arc::clone(priority_key));\n        }\n    }\n\n    /// Removes the stream ID from the writable streams set.\n    ///\n    /// This should also be called anytime an existing stream stops being\n    /// writable.\n    pub fn remove_writable(&mut self, priority_key: &Arc<StreamPriorityKey>) {\n        if !priority_key.writable.is_linked() {\n            return;\n        }\n\n        let mut c = {\n            let ptr = Arc::as_ptr(priority_key);\n            unsafe { self.writable.cursor_mut_from_ptr(ptr) }\n        };\n\n        c.remove();\n    }\n\n    /// Adds the stream ID to the flushable streams set.\n    ///\n    /// If the stream was already in the list, this does nothing.\n    pub fn insert_flushable(&mut self, priority_key: &Arc<StreamPriorityKey>) {\n        if !priority_key.flushable.is_linked() {\n            self.flushable.insert(Arc::clone(priority_key));\n        }\n    }\n\n    /// Removes the stream ID from the flushable streams set.\n    pub fn remove_flushable(&mut self, priority_key: &Arc<StreamPriorityKey>) {\n        if !priority_key.flushable.is_linked() {\n            return;\n        }\n\n        let mut c = {\n            let ptr = Arc::as_ptr(priority_key);\n            unsafe { self.flushable.cursor_mut_from_ptr(ptr) }\n        };\n\n        c.remove();\n    }\n\n    pub fn peek_flushable(&self) -> Option<Arc<StreamPriorityKey>> {\n        self.flushable.front().clone_pointer()\n    }\n\n    /// Updates the priorities of a stream.\n    pub fn update_priority(\n        &mut self, old: &Arc<StreamPriorityKey>, new: &Arc<StreamPriorityKey>,\n    ) {\n        if old.readable.is_linked() {\n            self.remove_readable(old);\n            self.readable.insert(Arc::clone(new));\n        }\n\n        if old.writable.is_linked() {\n            self.remove_writable(old);\n            self.writable.insert(Arc::clone(new));\n        }\n\n        if old.flushable.is_linked() {\n            self.remove_flushable(old);\n            self.flushable.insert(Arc::clone(new));\n        }\n    }\n\n    /// Adds the stream ID to the almost full streams set.\n    ///\n    /// If the stream was already in the list, this does nothing.\n    pub fn insert_almost_full(&mut self, stream_id: u64) {\n        self.almost_full.insert(stream_id);\n    }\n\n    /// Removes the stream ID from the almost full streams set.\n    pub fn remove_almost_full(&mut self, stream_id: u64) {\n        self.almost_full.remove(&stream_id);\n    }\n\n    /// Adds the stream ID to the blocked streams set with the\n    /// given offset value.\n    ///\n    /// If the stream was already in the list, this does nothing.\n    pub fn insert_blocked(&mut self, stream_id: u64, off: u64) {\n        self.blocked.insert(stream_id, off);\n    }\n\n    /// Removes the stream ID from the blocked streams set.\n    pub fn remove_blocked(&mut self, stream_id: u64) {\n        self.blocked.remove(&stream_id);\n    }\n\n    /// Adds the stream ID to the reset streams set with the\n    /// given error code and final size values.\n    ///\n    /// If the stream was already in the list, this does nothing.\n    pub fn insert_reset(\n        &mut self, stream_id: u64, error_code: u64, final_size: u64,\n    ) {\n        self.reset.insert(stream_id, (error_code, final_size));\n    }\n\n    /// Removes the stream ID from the reset streams set.\n    pub fn remove_reset(&mut self, stream_id: u64) {\n        self.reset.remove(&stream_id);\n    }\n\n    /// Adds the stream ID to the stopped streams set with the\n    /// given error code.\n    ///\n    /// If the stream was already in the list, this does nothing.\n    pub fn insert_stopped(&mut self, stream_id: u64, error_code: u64) {\n        self.stopped.insert(stream_id, error_code);\n    }\n\n    /// Removes the stream ID from the stopped streams set.\n    pub fn remove_stopped(&mut self, stream_id: u64) {\n        self.stopped.remove(&stream_id);\n    }\n\n    /// Updates the peer's maximum bidirectional stream count limit.\n    pub fn update_peer_max_streams_bidi(&mut self, v: u64) {\n        self.peer_max_streams_bidi = cmp::max(self.peer_max_streams_bidi, v);\n    }\n\n    /// Updates the peer's maximum unidirectional stream count limit.\n    pub fn update_peer_max_streams_uni(&mut self, v: u64) {\n        self.peer_max_streams_uni = cmp::max(self.peer_max_streams_uni, v);\n    }\n\n    /// Commits the new max_streams_bidi limit.\n    pub fn update_max_streams_bidi(&mut self) {\n        self.local_max_streams_bidi = self.local_max_streams_bidi_next;\n    }\n\n    /// Sets the max_streams_bidi limit to the given value.\n    pub fn set_max_streams_bidi(&mut self, max: u64) {\n        self.local_max_streams_bidi = max;\n        self.local_max_streams_bidi_next = max;\n        self.initial_max_streams_bidi = max;\n    }\n\n    /// Returns the current max_streams_bidi limit.\n    pub fn max_streams_bidi(&self) -> u64 {\n        self.local_max_streams_bidi\n    }\n\n    /// Returns the new max_streams_bidi limit.\n    pub fn max_streams_bidi_next(&mut self) -> u64 {\n        self.local_max_streams_bidi_next\n    }\n\n    /// Commits the new max_streams_uni limit.\n    pub fn update_max_streams_uni(&mut self) {\n        self.local_max_streams_uni = self.local_max_streams_uni_next;\n    }\n\n    /// Returns the new max_streams_uni limit.\n    pub fn max_streams_uni_next(&mut self) -> u64 {\n        self.local_max_streams_uni_next\n    }\n\n    /// Returns the peer's current maximum bidirectional stream count limit.\n    pub fn peer_max_streams_bidi(&self) -> u64 {\n        self.peer_max_streams_bidi\n    }\n\n    /// Returns the number of bidirectional streams that can be created\n    /// before the peer's stream count limit is reached.\n    pub fn peer_streams_left_bidi(&self) -> u64 {\n        self.peer_max_streams_bidi - self.local_opened_streams_bidi\n    }\n\n    /// Returns the peer's current maximum unidirectional stream count limit.\n    pub fn peer_max_streams_uni(&self) -> u64 {\n        self.peer_max_streams_uni\n    }\n\n    /// Returns the number of unidirectional streams that can be created\n    /// before the peer's stream count limit is reached.\n    pub fn peer_streams_left_uni(&self) -> u64 {\n        self.peer_max_streams_uni - self.local_opened_streams_uni\n    }\n\n    /// Drops completed stream.\n    ///\n    /// This should only be called when Stream::is_complete() returns true for\n    /// the given stream.\n    pub fn collect(&mut self, stream_id: u64, local: bool) {\n        if !local {\n            // If the stream was created by the peer, give back a max streams\n            // credit.\n            if is_bidi(stream_id) {\n                self.local_max_streams_bidi_next =\n                    self.local_max_streams_bidi_next.saturating_add(1);\n            } else {\n                self.local_max_streams_uni_next =\n                    self.local_max_streams_uni_next.saturating_add(1);\n            }\n        }\n\n        let s = self.streams.remove(&stream_id).unwrap();\n\n        self.remove_readable(&s.priority_key);\n\n        self.remove_writable(&s.priority_key);\n\n        self.remove_flushable(&s.priority_key);\n\n        self.collected.insert(stream_id);\n    }\n\n    /// Creates an iterator over streams that have outstanding data to read.\n    pub fn readable(&self) -> StreamIter {\n        StreamIter {\n            streams: self.readable.iter().map(|s| s.id).collect(),\n            index: 0,\n        }\n    }\n\n    /// Creates an iterator over streams that can be written to.\n    pub fn writable(&self) -> StreamIter {\n        StreamIter {\n            streams: self.writable.iter().map(|s| s.id).collect(),\n            index: 0,\n        }\n    }\n\n    /// Creates an iterator over streams that need to send MAX_STREAM_DATA.\n    pub fn almost_full(&self) -> StreamIter {\n        StreamIter::from(&self.almost_full)\n    }\n\n    /// Creates an iterator over streams that need to send STREAM_DATA_BLOCKED.\n    pub fn blocked(&self) -> hash_map::Iter<'_, u64, u64> {\n        self.blocked.iter()\n    }\n\n    /// Creates an iterator over streams that need to send RESET_STREAM.\n    pub fn reset(&self) -> hash_map::Iter<'_, u64, (u64, u64)> {\n        self.reset.iter()\n    }\n\n    /// Creates an iterator over streams that need to send STOP_SENDING.\n    pub fn stopped(&self) -> hash_map::Iter<'_, u64, u64> {\n        self.stopped.iter()\n    }\n\n    /// Returns true if the stream has been collected.\n    pub fn is_collected(&self, stream_id: u64) -> bool {\n        self.collected.contains(&stream_id)\n    }\n\n    /// Returns true if there are any streams that have data to write.\n    pub fn has_flushable(&self) -> bool {\n        !self.flushable.is_empty()\n    }\n\n    /// Returns true if there are any streams that have data to read.\n    pub fn has_readable(&self) -> bool {\n        !self.readable.is_empty()\n    }\n\n    /// Returns true if there are any streams that need to update the local\n    /// flow control limit.\n    pub fn has_almost_full(&self) -> bool {\n        !self.almost_full.is_empty()\n    }\n\n    /// Returns true if there are any streams that are blocked.\n    pub fn has_blocked(&self) -> bool {\n        !self.blocked.is_empty()\n    }\n\n    /// Returns true if there are any streams that are reset.\n    pub fn has_reset(&self) -> bool {\n        !self.reset.is_empty()\n    }\n\n    /// Returns true if there are any streams that need to send STOP_SENDING.\n    pub fn has_stopped(&self) -> bool {\n        !self.stopped.is_empty()\n    }\n\n    /// Returns true if the max bidirectional streams count needs to be updated\n    /// by sending a MAX_STREAMS frame to the peer.\n    ///\n    /// This only sends MAX_STREAMS when available capacity is at or below 50%\n    /// of the initial maximum streams target.\n    pub fn should_update_max_streams_bidi(&self) -> bool {\n        let available = self\n            .local_max_streams_bidi\n            .saturating_sub(self.peer_opened_streams_bidi);\n        self.local_max_streams_bidi_next != self.local_max_streams_bidi &&\n            available <= self.initial_max_streams_bidi / 2\n    }\n\n    /// Returns true if the max unidirectional streams count needs to be updated\n    /// by sending a MAX_STREAMS frame to the peer.\n    ///\n    /// This only send MAX_STREAMS when available capacity is at or below 50% of\n    /// the initial maximum streams target.\n    pub fn should_update_max_streams_uni(&self) -> bool {\n        let available = self\n            .local_max_streams_uni\n            .saturating_sub(self.peer_opened_streams_uni);\n        self.local_max_streams_uni_next != self.local_max_streams_uni &&\n            available <= self.initial_max_streams_uni / 2\n    }\n\n    /// Returns the number of active streams in the map.\n    #[cfg(test)]\n    pub fn len(&self) -> usize {\n        self.streams.len()\n    }\n}\n\n/// A QUIC stream.\npub struct Stream<F: BufFactory = DefaultBufFactory> {\n    /// Receive-side stream buffer.\n    pub recv: recv_buf::RecvBuf,\n\n    /// Send-side stream buffer.\n    pub send: send_buf::SendBuf<F>,\n\n    pub send_lowat: usize,\n\n    /// Whether the stream is bidirectional.\n    pub bidi: bool,\n\n    /// Whether the stream was created by the local endpoint.\n    pub local: bool,\n\n    /// The stream's urgency (lower is better). Default is `DEFAULT_URGENCY`.\n    pub urgency: u8,\n\n    /// Whether the stream can be flushed incrementally. Default is `true`.\n    pub incremental: bool,\n\n    pub priority_key: Arc<StreamPriorityKey>,\n}\n\nimpl<F: BufFactory> Stream<F> {\n    /// Creates a new stream with the given flow control limits.\n    pub fn new(\n        id: u64, max_rx_data: u64, max_tx_data: u64, bidi: bool, local: bool,\n        max_window: u64,\n    ) -> Self {\n        let priority_key = Arc::new(StreamPriorityKey {\n            id,\n            ..Default::default()\n        });\n\n        Stream {\n            recv: recv_buf::RecvBuf::new(max_rx_data, max_window),\n            send: send_buf::SendBuf::new(max_tx_data),\n            send_lowat: 1,\n            bidi,\n            local,\n            urgency: priority_key.urgency,\n            incremental: priority_key.incremental,\n            priority_key,\n        }\n    }\n\n    /// Returns true if the stream has data to read.\n    pub fn is_readable(&self) -> bool {\n        self.recv.ready()\n    }\n\n    /// Returns true if the stream has enough flow control capacity to be\n    /// written to, and is not finished.\n    pub fn is_writable(&self) -> bool {\n        !self.send.is_shutdown() &&\n            !self.send.is_fin() &&\n            (self.send.off_back() + self.send_lowat as u64) <\n                self.send.max_off()\n    }\n\n    /// Returns true if the stream has data to send and is allowed to send at\n    /// least some of it.\n    pub fn is_flushable(&self) -> bool {\n        let off_front = self.send.off_front();\n\n        !self.send.is_empty() &&\n            off_front < self.send.off_back() &&\n            off_front < self.send.max_off()\n    }\n\n    /// Returns true if the stream is complete.\n    ///\n    /// For bidirectional streams this happens when both the receive and send\n    /// sides are complete. That is when all incoming data has been read by the\n    /// application, and when all outgoing data has been acked by the peer.\n    ///\n    /// For unidirectional streams this happens when either the receive or send\n    /// side is complete, depending on whether the stream was created locally\n    /// or not.\n    pub fn is_complete(&self) -> bool {\n        match (self.bidi, self.local) {\n            // For bidirectional streams we need to check both receive and send\n            // sides for completion.\n            (true, _) => self.recv.is_fin() && self.send.is_complete(),\n\n            // For unidirectional streams generated locally, we only need to\n            // check the send side for completion.\n            (false, true) => self.send.is_complete(),\n\n            // For unidirectional streams generated by the peer, we only need\n            // to check the receive side for completion.\n            (false, false) => self.recv.is_fin(),\n        }\n    }\n}\n\n/// Returns true if the stream was created locally.\npub fn is_local(stream_id: u64, is_server: bool) -> bool {\n    (stream_id & 0x1) == (is_server as u64)\n}\n\n/// Returns true if the stream is bidirectional.\npub fn is_bidi(stream_id: u64) -> bool {\n    (stream_id & 0x2) == 0\n}\n\n#[derive(Clone, Debug)]\npub struct StreamPriorityKey {\n    pub urgency: u8,\n    pub incremental: bool,\n    pub id: u64,\n\n    pub readable: RBTreeAtomicLink,\n    pub writable: RBTreeAtomicLink,\n    pub flushable: RBTreeAtomicLink,\n}\n\nimpl Default for StreamPriorityKey {\n    fn default() -> Self {\n        Self {\n            urgency: DEFAULT_URGENCY,\n            incremental: true,\n            id: Default::default(),\n            readable: Default::default(),\n            writable: Default::default(),\n            flushable: Default::default(),\n        }\n    }\n}\n\nimpl PartialEq for StreamPriorityKey {\n    fn eq(&self, other: &Self) -> bool {\n        self.id == other.id\n    }\n}\n\nimpl Eq for StreamPriorityKey {}\n\nimpl PartialOrd for StreamPriorityKey {\n    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for StreamPriorityKey {\n    fn cmp(&self, other: &Self) -> cmp::Ordering {\n        // Ignore priority if ID matches.\n        if self.id == other.id {\n            return cmp::Ordering::Equal;\n        }\n\n        // First, order by urgency...\n        if self.urgency != other.urgency {\n            return self.urgency.cmp(&other.urgency);\n        }\n\n        // ...when the urgency is the same, and both are not incremental, order\n        // by stream ID...\n        if !self.incremental && !other.incremental {\n            return self.id.cmp(&other.id);\n        }\n\n        // ...non-incremental takes priority over incremental...\n        if self.incremental && !other.incremental {\n            return cmp::Ordering::Greater;\n        }\n        if !self.incremental && other.incremental {\n            return cmp::Ordering::Less;\n        }\n\n        // ...finally, when both are incremental, `other` takes precedence (so\n        // `self` is always sorted after other same-urgency incremental\n        // entries).\n        cmp::Ordering::Greater\n    }\n}\n\nintrusive_adapter!(pub StreamWritablePriorityAdapter = Arc<StreamPriorityKey>: StreamPriorityKey { writable: RBTreeAtomicLink });\n\nimpl KeyAdapter<'_> for StreamWritablePriorityAdapter {\n    type Key = StreamPriorityKey;\n\n    fn get_key(&self, s: &StreamPriorityKey) -> Self::Key {\n        s.clone()\n    }\n}\n\nintrusive_adapter!(pub StreamReadablePriorityAdapter = Arc<StreamPriorityKey>: StreamPriorityKey { readable: RBTreeAtomicLink });\n\nimpl KeyAdapter<'_> for StreamReadablePriorityAdapter {\n    type Key = StreamPriorityKey;\n\n    fn get_key(&self, s: &StreamPriorityKey) -> Self::Key {\n        s.clone()\n    }\n}\n\nintrusive_adapter!(pub StreamFlushablePriorityAdapter = Arc<StreamPriorityKey>: StreamPriorityKey { flushable: RBTreeAtomicLink });\n\nimpl KeyAdapter<'_> for StreamFlushablePriorityAdapter {\n    type Key = StreamPriorityKey;\n\n    fn get_key(&self, s: &StreamPriorityKey) -> Self::Key {\n        s.clone()\n    }\n}\n\n/// An iterator over QUIC streams.\n#[derive(Default)]\npub struct StreamIter {\n    streams: SmallVec<[u64; 8]>,\n    index: usize,\n}\n\nimpl StreamIter {\n    #[inline]\n    fn from(streams: &StreamIdHashSet) -> Self {\n        StreamIter {\n            streams: streams.iter().copied().collect(),\n            index: 0,\n        }\n    }\n}\n\nimpl Iterator for StreamIter {\n    type Item = u64;\n\n    #[inline]\n    fn next(&mut self) -> Option<Self::Item> {\n        let v = self.streams.get(self.index)?;\n        self.index += 1;\n        Some(*v)\n    }\n}\n\nimpl ExactSizeIterator for StreamIter {\n    #[inline]\n    fn len(&self) -> usize {\n        self.streams.len() - self.index\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::range_buf::RangeBuf;\n\n    use super::*;\n\n    #[test]\n    fn recv_flow_control() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let mut buf = [0; 32];\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n        let second = RangeBuf::from(b\"world\", 5, false);\n        let third = RangeBuf::from(b\"something\", 10, false);\n\n        assert_eq!(stream.recv.write(second), Ok(()));\n        assert_eq!(stream.recv.write(first), Ok(()));\n        assert!(!stream.recv.almost_full());\n\n        assert_eq!(stream.recv.write(third), Err(Error::FlowControl));\n\n        let (len, fin) = stream.recv.emit(&mut buf).unwrap();\n        assert_eq!(&buf[..len], b\"helloworld\");\n        assert!(!fin);\n\n        assert!(stream.recv.almost_full());\n\n        stream.recv.update_max_data(std::time::Instant::now());\n        assert_eq!(stream.recv.max_data_next(), 25);\n        assert!(!stream.recv.almost_full());\n\n        let third = RangeBuf::from(b\"something\", 10, false);\n        assert_eq!(stream.recv.write(third), Ok(()));\n    }\n\n    #[test]\n    fn recv_past_fin() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, true);\n        let second = RangeBuf::from(b\"world\", 5, false);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n        assert_eq!(stream.recv.write(second), Err(Error::FinalSize));\n    }\n\n    #[test]\n    fn recv_fin_dup() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, true);\n        let second = RangeBuf::from(b\"hello\", 0, true);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n        assert_eq!(stream.recv.write(second), Ok(()));\n\n        let mut buf = [0; 32];\n\n        let (len, fin) = stream.recv.emit(&mut buf).unwrap();\n        assert_eq!(&buf[..len], b\"hello\");\n        assert!(fin);\n    }\n\n    #[test]\n    fn recv_fin_change() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, true);\n        let second = RangeBuf::from(b\"world\", 5, true);\n\n        assert_eq!(stream.recv.write(second), Ok(()));\n        assert_eq!(stream.recv.write(first), Err(Error::FinalSize));\n    }\n\n    #[test]\n    fn recv_fin_lower_than_received() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, true);\n        let second = RangeBuf::from(b\"world\", 5, false);\n\n        assert_eq!(stream.recv.write(second), Ok(()));\n        assert_eq!(stream.recv.write(first), Err(Error::FinalSize));\n    }\n\n    #[test]\n    fn recv_fin_flow_control() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let mut buf = [0; 32];\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n        let second = RangeBuf::from(b\"world\", 5, true);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n        assert_eq!(stream.recv.write(second), Ok(()));\n\n        let (len, fin) = stream.recv.emit(&mut buf).unwrap();\n        assert_eq!(&buf[..len], b\"helloworld\");\n        assert!(fin);\n\n        assert!(!stream.recv.almost_full());\n    }\n\n    #[test]\n    fn recv_fin_reset_mismatch() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, true);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n        assert_eq!(stream.recv.reset(0, 10), Err(Error::FinalSize));\n    }\n\n    #[test]\n    fn recv_reset_with_gap() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n        // Read one byte.\n        assert_eq!(stream.recv.emit(&mut [0; 1]), Ok((1, false)));\n        // Reset with a final size > than max previously received\n        assert_eq!(\n            stream.recv.reset(0, 10),\n            Ok(RecvBufResetReturn {\n                max_data_delta: 5,\n                // consumed_flowcontrol is 9, since we already read 1 byte\n                consumed_flowcontrol: 9\n            })\n        );\n        assert_eq!(stream.recv.reset(0, 10), Ok(RecvBufResetReturn::zero()));\n    }\n\n    #[test]\n    fn recv_reset_dup() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n        assert_eq!(\n            stream.recv.reset(0, 5),\n            Ok(RecvBufResetReturn {\n                max_data_delta: 0,\n                consumed_flowcontrol: 5\n            })\n        );\n        assert_eq!(stream.recv.reset(0, 5), Ok(RecvBufResetReturn::zero()));\n    }\n\n    #[test]\n    fn recv_reset_change() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n        assert_eq!(\n            stream.recv.reset(0, 5),\n            Ok(RecvBufResetReturn {\n                max_data_delta: 0,\n                consumed_flowcontrol: 5\n            })\n        );\n        assert_eq!(stream.recv.reset(0, 10), Err(Error::FinalSize));\n    }\n\n    #[test]\n    fn recv_reset_lower_than_received() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n        assert!(!stream.recv.almost_full());\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n        assert_eq!(stream.recv.reset(0, 4), Err(Error::FinalSize));\n    }\n\n    #[test]\n    fn send_flow_control() {\n        let mut buf = [0; 25];\n\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        let first = b\"hello\";\n        let second = b\"world\";\n        let third = b\"something\";\n\n        assert!(stream.send.write(first, false).is_ok());\n        assert!(stream.send.write(second, false).is_ok());\n        assert!(stream.send.write(third, false).is_ok());\n\n        assert_eq!(stream.send.off_front(), 0);\n\n        let (written, fin) = stream.send.emit(&mut buf[..25]).unwrap();\n        assert_eq!(written, 15);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"helloworldsomet\");\n\n        assert_eq!(stream.send.off_front(), 15);\n\n        let (written, fin) = stream.send.emit(&mut buf[..25]).unwrap();\n        assert_eq!(written, 0);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"\");\n\n        stream.send.retransmit(0, 15);\n\n        assert_eq!(stream.send.off_front(), 0);\n\n        let (written, fin) = stream.send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 10);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"helloworld\");\n\n        assert_eq!(stream.send.off_front(), 10);\n\n        let (written, fin) = stream.send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 5);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"somet\");\n    }\n\n    #[test]\n    fn send_past_fin() {\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        let first = b\"hello\";\n        let second = b\"world\";\n        let third = b\"third\";\n\n        assert_eq!(stream.send.write(first, false), Ok(5));\n\n        assert_eq!(stream.send.write(second, true), Ok(5));\n        assert!(stream.send.is_fin());\n\n        assert_eq!(stream.send.write(third, false), Err(Error::FinalSize));\n    }\n\n    #[test]\n    fn send_fin_dup() {\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", true), Ok(5));\n        assert!(stream.send.is_fin());\n\n        assert_eq!(stream.send.write(b\"\", true), Ok(0));\n        assert!(stream.send.is_fin());\n    }\n\n    #[test]\n    fn send_undo_fin() {\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", true), Ok(5));\n        assert!(stream.send.is_fin());\n\n        assert_eq!(\n            stream.send.write(b\"helloworld\", true),\n            Err(Error::FinalSize)\n        );\n    }\n\n    #[test]\n    fn send_fin_max_data_match() {\n        let mut buf = [0; 15];\n\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        let slice = b\"hellohellohello\";\n\n        assert!(stream.send.write(slice, true).is_ok());\n\n        let (written, fin) = stream.send.emit(&mut buf[..15]).unwrap();\n        assert_eq!(written, 15);\n        assert!(fin);\n        assert_eq!(&buf[..written], slice);\n    }\n\n    #[test]\n    fn send_fin_zero_length() {\n        let mut buf = [0; 5];\n\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"\", true), Ok(0));\n        assert!(stream.send.is_fin());\n\n        let (written, fin) = stream.send.emit(&mut buf[..5]).unwrap();\n        assert_eq!(written, 5);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"hello\");\n    }\n\n    #[test]\n    fn send_ack() {\n        let mut buf = [0; 5];\n\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"world\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"\", true), Ok(0));\n        assert!(stream.send.is_fin());\n\n        assert_eq!(stream.send.off_front(), 0);\n\n        let (written, fin) = stream.send.emit(&mut buf[..5]).unwrap();\n        assert_eq!(written, 5);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"hello\");\n\n        stream.send.ack_and_drop(0, 5);\n\n        stream.send.retransmit(0, 5);\n\n        assert_eq!(stream.send.off_front(), 5);\n\n        let (written, fin) = stream.send.emit(&mut buf[..5]).unwrap();\n        assert_eq!(written, 5);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"world\");\n    }\n\n    #[test]\n    fn send_ack_reordering() {\n        let mut buf = [0; 5];\n\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"world\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"\", true), Ok(0));\n        assert!(stream.send.is_fin());\n\n        assert_eq!(stream.send.off_front(), 0);\n\n        let (written, fin) = stream.send.emit(&mut buf[..5]).unwrap();\n        assert_eq!(written, 5);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"hello\");\n\n        assert_eq!(stream.send.off_front(), 5);\n\n        let (written, fin) = stream.send.emit(&mut buf[..1]).unwrap();\n        assert_eq!(written, 1);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"w\");\n\n        stream.send.ack_and_drop(5, 1);\n        stream.send.ack_and_drop(0, 5);\n\n        stream.send.retransmit(0, 5);\n        stream.send.retransmit(5, 1);\n\n        assert_eq!(stream.send.off_front(), 6);\n\n        let (written, fin) = stream.send.emit(&mut buf[..5]).unwrap();\n        assert_eq!(written, 4);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"orld\");\n    }\n\n    #[test]\n    fn recv_data_below_off() {\n        let mut stream =\n            <Stream>::new(0, 15, 0, true, true, DEFAULT_STREAM_WINDOW);\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n\n        assert_eq!(stream.recv.write(first), Ok(()));\n\n        let mut buf = [0; 10];\n\n        let (len, fin) = stream.recv.emit(&mut buf).unwrap();\n        assert_eq!(&buf[..len], b\"hello\");\n        assert!(!fin);\n\n        let first = RangeBuf::from(b\"elloworld\", 1, true);\n        assert_eq!(stream.recv.write(first), Ok(()));\n\n        let (len, fin) = stream.recv.emit(&mut buf).unwrap();\n        assert_eq!(&buf[..len], b\"world\");\n        assert!(fin);\n    }\n\n    #[test]\n    fn stream_complete() {\n        let mut stream =\n            <Stream>::new(0, 30, 30, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"world\", false), Ok(5));\n\n        assert!(!stream.send.is_complete());\n        assert!(!stream.send.is_fin());\n\n        assert_eq!(stream.send.write(b\"\", true), Ok(0));\n\n        assert!(!stream.send.is_complete());\n        assert!(stream.send.is_fin());\n\n        let buf = RangeBuf::from(b\"hello\", 0, true);\n        assert!(stream.recv.write(buf).is_ok());\n        assert!(!stream.recv.is_fin());\n\n        stream.send.ack(6, 4);\n        assert!(!stream.send.is_complete());\n\n        let mut buf = [0; 2];\n        assert_eq!(stream.recv.emit(&mut buf), Ok((2, false)));\n        assert!(!stream.recv.is_fin());\n\n        stream.send.ack(1, 5);\n        assert!(!stream.send.is_complete());\n\n        stream.send.ack(0, 1);\n        assert!(stream.send.is_complete());\n\n        assert!(!stream.is_complete());\n\n        let mut buf = [0; 3];\n        assert_eq!(stream.recv.emit(&mut buf), Ok((3, true)));\n        assert!(stream.recv.is_fin());\n\n        assert!(stream.is_complete());\n    }\n\n    #[test]\n    fn send_fin_zero_length_output() {\n        let mut buf = [0; 5];\n\n        let mut stream =\n            <Stream>::new(0, 0, 15, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", false), Ok(5));\n        assert_eq!(stream.send.off_front(), 0);\n        assert!(!stream.send.is_fin());\n\n        let (written, fin) = stream.send.emit(&mut buf).unwrap();\n        assert_eq!(written, 5);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"hello\");\n\n        assert_eq!(stream.send.write(b\"\", true), Ok(0));\n        assert!(stream.send.is_fin());\n        assert_eq!(stream.send.off_front(), 5);\n\n        let (written, fin) = stream.send.emit(&mut buf).unwrap();\n        assert_eq!(written, 0);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"\");\n    }\n\n    fn stream_send_ready(stream: &Stream) -> bool {\n        !stream.send.is_empty() &&\n            stream.send.off_front() < stream.send.off_back()\n    }\n\n    #[test]\n    fn send_emit() {\n        let mut buf = [0; 5];\n\n        let mut stream =\n            <Stream>::new(0, 0, 20, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"world\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"olleh\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"dlrow\", true), Ok(5));\n        assert_eq!(stream.send.off_front(), 0);\n        assert_eq!(stream.send.bufs_count(), 4);\n\n        assert!(stream.is_flushable());\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false)));\n        assert_eq!(stream.send.off_front(), 4);\n        assert_eq!(&buf[..4], b\"hell\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false)));\n        assert_eq!(stream.send.off_front(), 8);\n        assert_eq!(&buf[..4], b\"owor\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false)));\n        assert_eq!(stream.send.off_front(), 10);\n        assert_eq!(&buf[..2], b\"ld\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..1]), Ok((1, false)));\n        assert_eq!(stream.send.off_front(), 11);\n        assert_eq!(&buf[..1], b\"o\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false)));\n        assert_eq!(stream.send.off_front(), 16);\n        assert_eq!(&buf[..5], b\"llehd\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((4, true)));\n        assert_eq!(stream.send.off_front(), 20);\n        assert_eq!(&buf[..4], b\"lrow\");\n\n        assert!(!stream.is_flushable());\n\n        assert!(!stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((0, true)));\n        assert_eq!(stream.send.off_front(), 20);\n    }\n\n    #[test]\n    fn send_emit_ack() {\n        let mut buf = [0; 5];\n\n        let mut stream =\n            <Stream>::new(0, 0, 20, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"world\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"olleh\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"dlrow\", true), Ok(5));\n        assert_eq!(stream.send.off_front(), 0);\n        assert_eq!(stream.send.bufs_count(), 4);\n\n        assert!(stream.is_flushable());\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false)));\n        assert_eq!(stream.send.off_front(), 4);\n        assert_eq!(&buf[..4], b\"hell\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false)));\n        assert_eq!(stream.send.off_front(), 8);\n        assert_eq!(&buf[..4], b\"owor\");\n\n        stream.send.ack_and_drop(0, 5);\n        assert_eq!(stream.send.bufs_count(), 3);\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false)));\n        assert_eq!(stream.send.off_front(), 10);\n        assert_eq!(&buf[..2], b\"ld\");\n\n        stream.send.ack_and_drop(7, 5);\n        assert_eq!(stream.send.bufs_count(), 3);\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..1]), Ok((1, false)));\n        assert_eq!(stream.send.off_front(), 11);\n        assert_eq!(&buf[..1], b\"o\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false)));\n        assert_eq!(stream.send.off_front(), 16);\n        assert_eq!(&buf[..5], b\"llehd\");\n\n        stream.send.ack_and_drop(5, 7);\n        assert_eq!(stream.send.bufs_count(), 2);\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((4, true)));\n        assert_eq!(stream.send.off_front(), 20);\n        assert_eq!(&buf[..4], b\"lrow\");\n\n        assert!(!stream.is_flushable());\n\n        assert!(!stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((0, true)));\n        assert_eq!(stream.send.off_front(), 20);\n\n        stream.send.ack_and_drop(22, 4);\n        assert_eq!(stream.send.bufs_count(), 2);\n\n        stream.send.ack_and_drop(20, 1);\n        assert_eq!(stream.send.bufs_count(), 2);\n    }\n\n    #[test]\n    fn send_emit_retransmit() {\n        let mut buf = [0; 5];\n\n        let mut stream =\n            <Stream>::new(0, 0, 20, true, true, DEFAULT_STREAM_WINDOW);\n\n        assert_eq!(stream.send.write(b\"hello\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"world\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"olleh\", false), Ok(5));\n        assert_eq!(stream.send.write(b\"dlrow\", true), Ok(5));\n        assert_eq!(stream.send.off_front(), 0);\n        assert_eq!(stream.send.bufs_count(), 4);\n\n        assert!(stream.is_flushable());\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false)));\n        assert_eq!(stream.send.off_front(), 4);\n        assert_eq!(&buf[..4], b\"hell\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..4]), Ok((4, false)));\n        assert_eq!(stream.send.off_front(), 8);\n        assert_eq!(&buf[..4], b\"owor\");\n\n        stream.send.retransmit(3, 3);\n        assert_eq!(stream.send.off_front(), 3);\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..3]), Ok((3, false)));\n        assert_eq!(stream.send.off_front(), 8);\n        assert_eq!(&buf[..3], b\"low\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false)));\n        assert_eq!(stream.send.off_front(), 10);\n        assert_eq!(&buf[..2], b\"ld\");\n\n        stream.send.ack_and_drop(7, 2);\n\n        stream.send.retransmit(8, 2);\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false)));\n        assert_eq!(stream.send.off_front(), 10);\n        assert_eq!(&buf[..2], b\"ld\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..1]), Ok((1, false)));\n        assert_eq!(stream.send.off_front(), 11);\n        assert_eq!(&buf[..1], b\"o\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false)));\n        assert_eq!(stream.send.off_front(), 16);\n        assert_eq!(&buf[..5], b\"llehd\");\n\n        stream.send.retransmit(12, 2);\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..2]), Ok((2, false)));\n        assert_eq!(stream.send.off_front(), 16);\n        assert_eq!(&buf[..2], b\"le\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((4, true)));\n        assert_eq!(stream.send.off_front(), 20);\n        assert_eq!(&buf[..4], b\"lrow\");\n\n        assert!(!stream.is_flushable());\n\n        assert!(!stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((0, true)));\n        assert_eq!(stream.send.off_front(), 20);\n\n        stream.send.retransmit(7, 12);\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false)));\n        assert_eq!(stream.send.off_front(), 12);\n        assert_eq!(&buf[..5], b\"rldol\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false)));\n        assert_eq!(stream.send.off_front(), 17);\n        assert_eq!(&buf[..5], b\"lehdl\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((2, false)));\n        assert_eq!(stream.send.off_front(), 20);\n        assert_eq!(&buf[..2], b\"ro\");\n\n        stream.send.ack_and_drop(12, 7);\n\n        stream.send.retransmit(7, 12);\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false)));\n        assert_eq!(stream.send.off_front(), 12);\n        assert_eq!(&buf[..5], b\"rldol\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((5, false)));\n        assert_eq!(stream.send.off_front(), 17);\n        assert_eq!(&buf[..5], b\"lehdl\");\n\n        assert!(stream_send_ready(&stream));\n        assert_eq!(stream.send.emit(&mut buf[..5]), Ok((2, false)));\n        assert_eq!(stream.send.off_front(), 20);\n        assert_eq!(&buf[..2], b\"ro\");\n    }\n\n    #[test]\n    fn rangebuf_split_off() {\n        let mut buf = <RangeBuf>::from(b\"helloworld\", 5, true);\n        assert_eq!(buf.start, 0);\n        assert_eq!(buf.pos, 0);\n        assert_eq!(buf.len, 10);\n        assert_eq!(buf.off, 5);\n        assert!(buf.fin);\n\n        assert_eq!(buf.len(), 10);\n        assert_eq!(buf.off(), 5);\n        assert!(buf.fin());\n\n        assert_eq!(&buf[..], b\"helloworld\");\n\n        // Advance buffer.\n        buf.consume(5);\n\n        assert_eq!(buf.start, 0);\n        assert_eq!(buf.pos, 5);\n        assert_eq!(buf.len, 10);\n        assert_eq!(buf.off, 5);\n        assert!(buf.fin);\n\n        assert_eq!(buf.len(), 5);\n        assert_eq!(buf.off(), 10);\n        assert!(buf.fin());\n\n        assert_eq!(&buf[..], b\"world\");\n\n        // Split buffer before position.\n        let mut new_buf = buf.split_off(3);\n\n        assert_eq!(buf.start, 0);\n        assert_eq!(buf.pos, 3);\n        assert_eq!(buf.len, 3);\n        assert_eq!(buf.off, 5);\n        assert!(!buf.fin);\n\n        assert_eq!(buf.len(), 0);\n        assert_eq!(buf.off(), 8);\n        assert!(!buf.fin());\n\n        assert_eq!(&buf[..], b\"\");\n\n        assert_eq!(new_buf.start, 3);\n        assert_eq!(new_buf.pos, 5);\n        assert_eq!(new_buf.len, 7);\n        assert_eq!(new_buf.off, 8);\n        assert!(new_buf.fin);\n\n        assert_eq!(new_buf.len(), 5);\n        assert_eq!(new_buf.off(), 10);\n        assert!(new_buf.fin());\n\n        assert_eq!(&new_buf[..], b\"world\");\n\n        // Advance buffer.\n        new_buf.consume(2);\n\n        assert_eq!(new_buf.start, 3);\n        assert_eq!(new_buf.pos, 7);\n        assert_eq!(new_buf.len, 7);\n        assert_eq!(new_buf.off, 8);\n        assert!(new_buf.fin);\n\n        assert_eq!(new_buf.len(), 3);\n        assert_eq!(new_buf.off(), 12);\n        assert!(new_buf.fin());\n\n        assert_eq!(&new_buf[..], b\"rld\");\n\n        // Split buffer after position.\n        let mut new_new_buf = new_buf.split_off(5);\n\n        assert_eq!(new_buf.start, 3);\n        assert_eq!(new_buf.pos, 7);\n        assert_eq!(new_buf.len, 5);\n        assert_eq!(new_buf.off, 8);\n        assert!(!new_buf.fin);\n\n        assert_eq!(new_buf.len(), 1);\n        assert_eq!(new_buf.off(), 12);\n        assert!(!new_buf.fin());\n\n        assert_eq!(&new_buf[..], b\"r\");\n\n        assert_eq!(new_new_buf.start, 8);\n        assert_eq!(new_new_buf.pos, 8);\n        assert_eq!(new_new_buf.len, 2);\n        assert_eq!(new_new_buf.off, 13);\n        assert!(new_new_buf.fin);\n\n        assert_eq!(new_new_buf.len(), 2);\n        assert_eq!(new_new_buf.off(), 13);\n        assert!(new_new_buf.fin());\n\n        assert_eq!(&new_new_buf[..], b\"ld\");\n\n        // Advance buffer.\n        new_new_buf.consume(2);\n\n        assert_eq!(new_new_buf.start, 8);\n        assert_eq!(new_new_buf.pos, 10);\n        assert_eq!(new_new_buf.len, 2);\n        assert_eq!(new_new_buf.off, 13);\n        assert!(new_new_buf.fin);\n\n        assert_eq!(new_new_buf.len(), 0);\n        assert_eq!(new_new_buf.off(), 15);\n        assert!(new_new_buf.fin());\n\n        assert_eq!(&new_new_buf[..], b\"\");\n    }\n\n    /// RFC9000 2.1: A stream ID that is used out of order results in all\n    /// streams of that type with lower-numbered stream IDs also being opened.\n    #[test]\n    fn stream_limit_auto_open() {\n        let local_tp = crate::TransportParams::default();\n        let peer_tp = crate::TransportParams::default();\n\n        let mut streams = <StreamMap>::new(5, 5, 5);\n\n        let stream_id = 500;\n        assert!(!is_local(stream_id, true), \"stream id is peer initiated\");\n        assert!(is_bidi(stream_id), \"stream id is bidirectional\");\n        assert_eq!(\n            streams\n                .get_or_create(stream_id, &local_tp, &peer_tp, false, true)\n                .err(),\n            Some(Error::StreamLimit),\n            \"stream limit should be exceeded\"\n        );\n    }\n\n    /// Stream limit should be satisfied regardless of what order we open\n    /// streams\n    #[test]\n    fn stream_create_out_of_order() {\n        let local_tp = crate::TransportParams::default();\n        let peer_tp = crate::TransportParams::default();\n\n        let mut streams = <StreamMap>::new(5, 5, 5);\n\n        for stream_id in [8, 12, 4] {\n            assert!(is_local(stream_id, false), \"stream id is client initiated\");\n            assert!(is_bidi(stream_id), \"stream id is bidirectional\");\n            assert!(streams\n                .get_or_create(stream_id, &local_tp, &peer_tp, false, true)\n                .is_ok());\n        }\n    }\n\n    /// Check stream limit boundary cases\n    #[test]\n    fn stream_limit_edge() {\n        let local_tp = crate::TransportParams::default();\n        let peer_tp = crate::TransportParams::default();\n\n        let mut streams = <StreamMap>::new(3, 3, 3);\n\n        // Highest permitted\n        let stream_id = 8;\n        assert!(streams\n            .get_or_create(stream_id, &local_tp, &peer_tp, false, true)\n            .is_ok());\n\n        // One more than highest permitted\n        let stream_id = 12;\n        assert_eq!(\n            streams\n                .get_or_create(stream_id, &local_tp, &peer_tp, false, true)\n                .err(),\n            Some(Error::StreamLimit)\n        );\n    }\n\n    fn cycle_stream_priority(stream_id: u64, streams: &mut StreamMap) {\n        let key = streams.get(stream_id).unwrap().priority_key.clone();\n        streams.update_priority(&key.clone(), &key);\n    }\n\n    #[test]\n    fn writable_prioritized_default_priority() {\n        let local_tp = crate::TransportParams::default();\n        let peer_tp = crate::TransportParams {\n            initial_max_stream_data_bidi_local: 100,\n            initial_max_stream_data_uni: 100,\n            ..Default::default()\n        };\n\n        let mut streams = StreamMap::new(100, 100, 100);\n\n        for id in [0, 4, 8, 12] {\n            assert!(streams\n                .get_or_create(id, &local_tp, &peer_tp, false, true)\n                .is_ok());\n        }\n\n        let walk_1: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(*walk_1.first().unwrap(), &mut streams);\n        let walk_2: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(*walk_2.first().unwrap(), &mut streams);\n        let walk_3: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(*walk_3.first().unwrap(), &mut streams);\n        let walk_4: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(*walk_4.first().unwrap(), &mut streams);\n        let walk_5: Vec<u64> = streams.writable().collect();\n\n        // All streams are non-incremental and same urgency by default. Multiple\n        // visits shuffle their order.\n        assert_eq!(walk_1, vec![0, 4, 8, 12]);\n        assert_eq!(walk_2, vec![4, 8, 12, 0]);\n        assert_eq!(walk_3, vec![8, 12, 0, 4]);\n        assert_eq!(walk_4, vec![12, 0, 4, 8,]);\n        assert_eq!(walk_5, vec![0, 4, 8, 12]);\n    }\n\n    #[test]\n    fn writable_prioritized_insert_order() {\n        let local_tp = crate::TransportParams::default();\n        let peer_tp = crate::TransportParams {\n            initial_max_stream_data_bidi_local: 100,\n            initial_max_stream_data_uni: 100,\n            ..Default::default()\n        };\n\n        let mut streams = StreamMap::new(100, 100, 100);\n\n        // Inserting same-urgency incremental streams in a \"random\" order yields\n        // same order to start with.\n        for id in [12, 4, 8, 0] {\n            assert!(streams\n                .get_or_create(id, &local_tp, &peer_tp, false, true)\n                .is_ok());\n        }\n\n        let walk_1: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(*walk_1.first().unwrap(), &mut streams);\n        let walk_2: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(*walk_2.first().unwrap(), &mut streams);\n        let walk_3: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(*walk_3.first().unwrap(), &mut streams);\n        let walk_4: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(*walk_4.first().unwrap(), &mut streams);\n        let walk_5: Vec<u64> = streams.writable().collect();\n        assert_eq!(walk_1, vec![12, 4, 8, 0]);\n        assert_eq!(walk_2, vec![4, 8, 0, 12]);\n        assert_eq!(walk_3, vec![8, 0, 12, 4,]);\n        assert_eq!(walk_4, vec![0, 12, 4, 8]);\n        assert_eq!(walk_5, vec![12, 4, 8, 0]);\n    }\n\n    #[test]\n    fn writable_prioritized_mixed_urgency() {\n        let local_tp = crate::TransportParams::default();\n        let peer_tp = crate::TransportParams {\n            initial_max_stream_data_bidi_local: 100,\n            initial_max_stream_data_uni: 100,\n            ..Default::default()\n        };\n\n        let mut streams = <StreamMap>::new(100, 100, 100);\n\n        // Streams where the urgency descends (becomes more important). No stream\n        // shares an urgency.\n        let input = vec![\n            (0, 100),\n            (4, 90),\n            (8, 80),\n            (12, 70),\n            (16, 60),\n            (20, 50),\n            (24, 40),\n            (28, 30),\n            (32, 20),\n            (36, 10),\n            (40, 0),\n        ];\n\n        for (id, urgency) in input.clone() {\n            // this duplicates some code from stream_priority in order to access\n            // streams and the collection they're in\n            let stream = streams\n                .get_or_create(id, &local_tp, &peer_tp, false, true)\n                .unwrap();\n\n            stream.urgency = urgency;\n\n            let new_priority_key = Arc::new(StreamPriorityKey {\n                urgency: stream.urgency,\n                incremental: stream.incremental,\n                id,\n                ..Default::default()\n            });\n\n            let old_priority_key = std::mem::replace(\n                &mut stream.priority_key,\n                new_priority_key.clone(),\n            );\n\n            streams.update_priority(&old_priority_key, &new_priority_key);\n        }\n\n        let walk_1: Vec<u64> = streams.writable().collect();\n        assert_eq!(walk_1, vec![40, 36, 32, 28, 24, 20, 16, 12, 8, 4, 0]);\n\n        // Re-applying priority to a stream does not cause duplication.\n        for (id, urgency) in input {\n            // this duplicates some code from stream_priority in order to access\n            // streams and the collection they're in\n            let stream = streams\n                .get_or_create(id, &local_tp, &peer_tp, false, true)\n                .unwrap();\n\n            stream.urgency = urgency;\n\n            let new_priority_key = Arc::new(StreamPriorityKey {\n                urgency: stream.urgency,\n                incremental: stream.incremental,\n                id,\n                ..Default::default()\n            });\n\n            let old_priority_key = std::mem::replace(\n                &mut stream.priority_key,\n                new_priority_key.clone(),\n            );\n\n            streams.update_priority(&old_priority_key, &new_priority_key);\n        }\n\n        let walk_2: Vec<u64> = streams.writable().collect();\n        assert_eq!(walk_2, vec![40, 36, 32, 28, 24, 20, 16, 12, 8, 4, 0]);\n\n        // Removing streams doesn't break expected ordering.\n        streams.collect(24, true);\n\n        let walk_3: Vec<u64> = streams.writable().collect();\n        assert_eq!(walk_3, vec![40, 36, 32, 28, 20, 16, 12, 8, 4, 0]);\n\n        streams.collect(40, true);\n        streams.collect(0, true);\n\n        let walk_4: Vec<u64> = streams.writable().collect();\n        assert_eq!(walk_4, vec![36, 32, 28, 20, 16, 12, 8, 4]);\n\n        // Adding streams doesn't break expected ordering.\n        streams\n            .get_or_create(44, &local_tp, &peer_tp, false, true)\n            .unwrap();\n\n        let walk_5: Vec<u64> = streams.writable().collect();\n        assert_eq!(walk_5, vec![36, 32, 28, 20, 16, 12, 8, 4, 44]);\n    }\n\n    #[test]\n    fn writable_prioritized_mixed_urgencies_incrementals() {\n        let local_tp = crate::TransportParams::default();\n        let peer_tp = crate::TransportParams {\n            initial_max_stream_data_bidi_local: 100,\n            initial_max_stream_data_uni: 100,\n            ..Default::default()\n        };\n\n        let mut streams = StreamMap::new(100, 100, 100);\n\n        // Streams that share some urgency level\n        let input = vec![\n            (0, 100),\n            (4, 20),\n            (8, 100),\n            (12, 20),\n            (16, 90),\n            (20, 25),\n            (24, 90),\n            (28, 30),\n            (32, 80),\n            (36, 20),\n            (40, 0),\n        ];\n\n        for (id, urgency) in input.clone() {\n            // this duplicates some code from stream_priority in order to access\n            // streams and the collection they're in\n            let stream = streams\n                .get_or_create(id, &local_tp, &peer_tp, false, true)\n                .unwrap();\n\n            stream.urgency = urgency;\n\n            let new_priority_key = Arc::new(StreamPriorityKey {\n                urgency: stream.urgency,\n                incremental: stream.incremental,\n                id,\n                ..Default::default()\n            });\n\n            let old_priority_key = std::mem::replace(\n                &mut stream.priority_key,\n                new_priority_key.clone(),\n            );\n\n            streams.update_priority(&old_priority_key, &new_priority_key);\n        }\n\n        let walk_1: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(4, &mut streams);\n        cycle_stream_priority(16, &mut streams);\n        cycle_stream_priority(0, &mut streams);\n        let walk_2: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(12, &mut streams);\n        cycle_stream_priority(24, &mut streams);\n        cycle_stream_priority(8, &mut streams);\n        let walk_3: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(36, &mut streams);\n        cycle_stream_priority(16, &mut streams);\n        cycle_stream_priority(0, &mut streams);\n        let walk_4: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(4, &mut streams);\n        cycle_stream_priority(24, &mut streams);\n        cycle_stream_priority(8, &mut streams);\n        let walk_5: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(12, &mut streams);\n        cycle_stream_priority(16, &mut streams);\n        cycle_stream_priority(0, &mut streams);\n        let walk_6: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(36, &mut streams);\n        cycle_stream_priority(24, &mut streams);\n        cycle_stream_priority(8, &mut streams);\n        let walk_7: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(4, &mut streams);\n        cycle_stream_priority(16, &mut streams);\n        cycle_stream_priority(0, &mut streams);\n        let walk_8: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(12, &mut streams);\n        cycle_stream_priority(24, &mut streams);\n        cycle_stream_priority(8, &mut streams);\n        let walk_9: Vec<u64> = streams.writable().collect();\n        cycle_stream_priority(36, &mut streams);\n        cycle_stream_priority(16, &mut streams);\n        cycle_stream_priority(0, &mut streams);\n\n        assert_eq!(walk_1, vec![40, 4, 12, 36, 20, 28, 32, 16, 24, 0, 8]);\n        assert_eq!(walk_2, vec![40, 12, 36, 4, 20, 28, 32, 24, 16, 8, 0]);\n        assert_eq!(walk_3, vec![40, 36, 4, 12, 20, 28, 32, 16, 24, 0, 8]);\n        assert_eq!(walk_4, vec![40, 4, 12, 36, 20, 28, 32, 24, 16, 8, 0]);\n        assert_eq!(walk_5, vec![40, 12, 36, 4, 20, 28, 32, 16, 24, 0, 8]);\n        assert_eq!(walk_6, vec![40, 36, 4, 12, 20, 28, 32, 24, 16, 8, 0]);\n        assert_eq!(walk_7, vec![40, 4, 12, 36, 20, 28, 32, 16, 24, 0, 8]);\n        assert_eq!(walk_8, vec![40, 12, 36, 4, 20, 28, 32, 24, 16, 8, 0]);\n        assert_eq!(walk_9, vec![40, 36, 4, 12, 20, 28, 32, 16, 24, 0, 8]);\n\n        // Removing streams doesn't break expected ordering.\n        streams.collect(20, true);\n\n        let walk_10: Vec<u64> = streams.writable().collect();\n        assert_eq!(walk_10, vec![40, 4, 12, 36, 28, 32, 24, 16, 8, 0]);\n\n        // Adding streams doesn't break expected ordering.\n        let stream = streams\n            .get_or_create(44, &local_tp, &peer_tp, false, true)\n            .unwrap();\n\n        stream.urgency = 20;\n        stream.incremental = true;\n\n        let new_priority_key = Arc::new(StreamPriorityKey {\n            urgency: stream.urgency,\n            incremental: stream.incremental,\n            id: 44,\n            ..Default::default()\n        });\n\n        let old_priority_key =\n            std::mem::replace(&mut stream.priority_key, new_priority_key.clone());\n\n        streams.update_priority(&old_priority_key, &new_priority_key);\n\n        let walk_11: Vec<u64> = streams.writable().collect();\n        assert_eq!(walk_11, vec![40, 4, 12, 36, 44, 28, 32, 24, 16, 8, 0]);\n    }\n\n    #[test]\n    fn priority_tree_dupes() {\n        let mut prioritized_writable: RBTree<StreamWritablePriorityAdapter> =\n            Default::default();\n\n        for id in [0, 4, 8, 12] {\n            let s = Arc::new(StreamPriorityKey {\n                urgency: 0,\n                incremental: false,\n                id,\n                ..Default::default()\n            });\n\n            prioritized_writable.insert(s);\n        }\n\n        let walk_1: Vec<u64> =\n            prioritized_writable.iter().map(|s| s.id).collect();\n        assert_eq!(walk_1, vec![0, 4, 8, 12]);\n\n        // Default keys could cause duplicate entries, this is normally protected\n        // against via StreamMap.\n        for id in [0, 4, 8, 12] {\n            let s = Arc::new(StreamPriorityKey {\n                urgency: 0,\n                incremental: false,\n                id,\n                ..Default::default()\n            });\n\n            prioritized_writable.insert(s);\n        }\n\n        let walk_2: Vec<u64> =\n            prioritized_writable.iter().map(|s| s.id).collect();\n        assert_eq!(walk_2, vec![0, 0, 4, 4, 8, 8, 12, 12]);\n    }\n}\n\nmod recv_buf;\nmod send_buf;\n"
  },
  {
    "path": "quiche/src/stream/recv_buf.rs",
    "content": "// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::cmp;\n\nuse std::collections::BTreeMap;\nuse std::collections::VecDeque;\n\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse crate::stream::RecvAction;\nuse crate::stream::RecvBufResetReturn;\nuse crate::Error;\nuse crate::Result;\n\nuse crate::flowcontrol;\n\nuse crate::range_buf::RangeBuf;\n\nuse super::DEFAULT_STREAM_WINDOW;\n\n/// Receive-side stream buffer.\n///\n/// Stream data received by the peer is buffered in a list of data chunks\n/// ordered by offset in ascending order. Contiguous data can then be read\n/// into a slice.\n#[derive(Debug, Default)]\npub struct RecvBuf {\n    /// Chunks of data received from the peer that have not yet been read by\n    /// the application, ordered by offset.\n    data: BTreeMap<u64, RangeBuf>,\n\n    /// The lowest data offset that has yet to be read by the application.\n    off: u64,\n\n    /// The total length of data received on this stream.\n    len: u64,\n\n    /// Receiver flow controller.\n    flow_control: flowcontrol::FlowControl,\n\n    /// The final stream offset received from the peer, if any.\n    fin_off: Option<u64>,\n\n    /// The error code received via RESET_STREAM.\n    error: Option<u64>,\n\n    /// Whether incoming data is validated but not buffered.\n    drain: bool,\n}\n\nimpl RecvBuf {\n    /// Creates a new receive buffer.\n    pub fn new(max_data: u64, max_window: u64) -> RecvBuf {\n        RecvBuf {\n            flow_control: flowcontrol::FlowControl::new(\n                max_data,\n                cmp::min(max_data, DEFAULT_STREAM_WINDOW),\n                max_window,\n            ),\n            ..RecvBuf::default()\n        }\n    }\n\n    /// Inserts the given chunk of data in the buffer.\n    ///\n    /// This also takes care of enforcing stream flow control limits, as well\n    /// as handling incoming data that overlaps data that is already in the\n    /// buffer.\n    pub fn write(&mut self, buf: RangeBuf) -> Result<()> {\n        if buf.max_off() > self.max_data() {\n            return Err(Error::FlowControl);\n        }\n\n        if let Some(fin_off) = self.fin_off {\n            // Stream's size is known, forbid data beyond that point.\n            if buf.max_off() > fin_off {\n                return Err(Error::FinalSize);\n            }\n\n            // Stream's size is already known, forbid changing it.\n            if buf.fin() && fin_off != buf.max_off() {\n                return Err(Error::FinalSize);\n            }\n        }\n\n        // Stream's known size is lower than data already received.\n        if buf.fin() && buf.max_off() < self.len {\n            return Err(Error::FinalSize);\n        }\n\n        // We already saved the final offset, so there's nothing else we\n        // need to keep from the RangeBuf if it's empty.\n        if self.fin_off.is_some() && buf.is_empty() {\n            return Ok(());\n        }\n\n        if buf.fin() {\n            self.fin_off = Some(buf.max_off());\n        }\n\n        // No need to store empty buffer that doesn't carry the fin flag.\n        if !buf.fin() && buf.is_empty() {\n            return Ok(());\n        }\n\n        // Check if data is fully duplicate, that is the buffer's max offset is\n        // lower or equal to the offset already stored in the recv buffer.\n        if self.off >= buf.max_off() {\n            // An exception is applied to empty range buffers, because an empty\n            // buffer's max offset matches the max offset of the recv buffer.\n            //\n            // By this point all spurious empty buffers should have already been\n            // discarded, so allowing empty buffers here should be safe.\n            if !buf.is_empty() {\n                return Ok(());\n            }\n        }\n\n        let mut tmp_bufs = VecDeque::with_capacity(2);\n        tmp_bufs.push_back(buf);\n\n        'tmp: while let Some(mut buf) = tmp_bufs.pop_front() {\n            // Discard incoming data below current stream offset. Bytes up to\n            // `self.off` have already been received so we should not buffer\n            // them again. This is also important to make sure `ready()` doesn't\n            // get stuck when a buffer with lower offset than the stream's is\n            // buffered.\n            if self.off_front() > buf.off() {\n                buf = buf.split_off((self.off_front() - buf.off()) as usize);\n            }\n\n            // Handle overlapping data. If the incoming data's starting offset\n            // is above the previous maximum received offset, there is clearly\n            // no overlap so this logic can be skipped. However do still try to\n            // merge an empty final buffer (i.e. an empty buffer with the fin\n            // flag set, which is the only kind of empty buffer that should\n            // reach this point).\n            if buf.off() < self.max_off() || buf.is_empty() {\n                for (_, b) in self.data.range(buf.off()..) {\n                    let off = buf.off();\n\n                    // We are past the current buffer.\n                    if b.off() > buf.max_off() {\n                        break;\n                    }\n\n                    // New buffer is fully contained in existing buffer.\n                    if off >= b.off() && buf.max_off() <= b.max_off() {\n                        continue 'tmp;\n                    }\n\n                    // New buffer's start overlaps existing buffer.\n                    if off >= b.off() && off < b.max_off() {\n                        buf = buf.split_off((b.max_off() - off) as usize);\n                    }\n\n                    // New buffer's end overlaps existing buffer.\n                    if off < b.off() && buf.max_off() > b.off() {\n                        tmp_bufs\n                            .push_back(buf.split_off((b.off() - off) as usize));\n                    }\n                }\n            }\n\n            self.len = cmp::max(self.len, buf.max_off());\n\n            if !self.drain {\n                self.data.insert(buf.max_off(), buf);\n            } else {\n                // we are not storing any data, off == len\n                self.off = self.len;\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Reads contiguous data from the receive buffer.\n    ///\n    /// Data is written into the given `out` buffer, up to the length of `out`.\n    ///\n    /// Only contiguous data is removed, starting from offset 0. The offset is\n    /// incremented as data is taken out of the receive buffer. If there is no\n    /// data at the expected read offset, the `Done` error is returned.\n    ///\n    /// On success the amount of data read and a flag indicating\n    /// if there is no more data in the buffer, are returned as a tuple.\n    pub fn emit(&mut self, out: &mut [u8]) -> Result<(usize, bool)> {\n        self.emit_or_discard(RecvAction::Emit { out })\n    }\n\n    /// Reads or discards contiguous data from the receive buffer.\n    ///\n    /// Passing an `action` of `StreamRecvAction::Emit` results in data being\n    /// written into the provided buffer, up to its length.\n    ///\n    /// Passing an `action` of `StreamRecvAction::Discard` results in up to\n    /// the indicated number of bytes being discarded without copying.\n    ///\n    /// Only contiguous data is removed, starting from offset 0. The offset is\n    /// incremented as data is taken out of the receive buffer. If there is no\n    /// data at the expected read offset, the `Done` error is returned.\n    ///\n    /// On success the amount of data read or discarded, and a flag indicating\n    /// if there is no more data in the buffer, are returned as a tuple.\n    pub fn emit_or_discard(\n        &mut self, mut action: RecvAction,\n    ) -> Result<(usize, bool)> {\n        let mut len = 0;\n        let mut cap = match &action {\n            RecvAction::Emit { out } => out.len(),\n            RecvAction::Discard { len } => *len,\n        };\n\n        if !self.ready() {\n            return Err(Error::Done);\n        }\n\n        // The stream was reset, so clear its data and return the error code\n        // instead.\n        if let Some(e) = self.error {\n            self.data.clear();\n            return Err(Error::StreamReset(e));\n        }\n\n        while cap > 0 && self.ready() {\n            let mut entry = match self.data.first_entry() {\n                Some(entry) => entry,\n                None => break,\n            };\n\n            let buf = entry.get_mut();\n\n            let buf_len = cmp::min(buf.len(), cap);\n\n            // Only copy data if we're emitting, not discarding.\n            if let RecvAction::Emit { ref mut out } = action {\n                out[len..len + buf_len].copy_from_slice(&buf[..buf_len]);\n            }\n\n            self.off += buf_len as u64;\n\n            len += buf_len;\n            cap -= buf_len;\n\n            if buf_len < buf.len() {\n                buf.consume(buf_len);\n\n                // We reached the maximum capacity, so end here.\n                break;\n            }\n\n            entry.remove();\n        }\n\n        // Update consumed bytes for flow control.\n        self.flow_control.add_consumed(len as u64);\n\n        Ok((len, self.is_fin()))\n    }\n\n    /// Resets the stream at the given offset.\n    pub fn reset(\n        &mut self, error_code: u64, final_size: u64,\n    ) -> Result<RecvBufResetReturn> {\n        // Stream's size is already known, forbid changing it.\n        if let Some(fin_off) = self.fin_off {\n            if fin_off != final_size {\n                return Err(Error::FinalSize);\n            }\n        }\n\n        // Stream's known size is lower than data already received.\n        if final_size < self.len {\n            return Err(Error::FinalSize);\n        }\n\n        if self.error.is_some() {\n            // We already verified that the final size matches\n            return Ok(RecvBufResetReturn::zero());\n        }\n\n        // Calculate how many bytes need to be removed from the connection flow\n        // control.\n        let result = RecvBufResetReturn {\n            max_data_delta: final_size - self.len,\n            consumed_flowcontrol: final_size - self.off,\n        };\n\n        self.error = Some(error_code);\n\n        // Clear all data already buffered.\n        self.off = final_size;\n\n        self.data.clear();\n\n        // In order to ensure the application is notified when the stream is\n        // reset, enqueue a zero-length buffer at the final size offset.\n        let buf = RangeBuf::from(b\"\", final_size, true);\n        self.write(buf)?;\n\n        Ok(result)\n    }\n\n    /// Commits the new max_data limit.\n    pub fn update_max_data(&mut self, now: Instant) {\n        self.flow_control.update_max_data(now);\n    }\n\n    /// Return the new max_data limit.\n    pub fn max_data_next(&mut self) -> u64 {\n        self.flow_control.max_data_next()\n    }\n\n    /// Return the current flow control limit.\n    pub fn max_data(&self) -> u64 {\n        self.flow_control.max_data()\n    }\n\n    /// Return the current window.\n    pub fn window(&self) -> u64 {\n        self.flow_control.window()\n    }\n\n    /// Autotune the window size.\n    pub fn autotune_window(&mut self, now: Instant, rtt: Duration) {\n        self.flow_control.autotune_window(now, rtt);\n    }\n\n    /// Shuts down receiving data and returns the number of bytes\n    /// that should be returned to the connection level flow\n    /// control\n    pub fn shutdown(&mut self) -> Result<u64> {\n        if self.drain {\n            return Err(Error::Done);\n        }\n\n        self.drain = true;\n\n        self.data.clear();\n\n        let consumed = self.max_off() - self.off;\n        self.off = self.max_off();\n\n        Ok(consumed)\n    }\n\n    /// Returns the lowest offset of data buffered.\n    pub fn off_front(&self) -> u64 {\n        self.off\n    }\n\n    /// Returns true if we need to update the local flow control limit.\n    pub fn almost_full(&self) -> bool {\n        self.fin_off.is_none() && self.flow_control.should_update_max_data()\n    }\n\n    /// Returns the largest offset ever received.\n    pub fn max_off(&self) -> u64 {\n        self.len\n    }\n\n    /// Returns true if the receive-side of the stream is complete.\n    ///\n    /// This happens when the stream's receive final size is known, and the\n    /// application has read all data from the stream.\n    pub fn is_fin(&self) -> bool {\n        if self.fin_off == Some(self.off) {\n            return true;\n        }\n\n        false\n    }\n\n    /// Returns true if the stream is not storing incoming data.\n    pub fn is_draining(&self) -> bool {\n        self.drain\n    }\n\n    /// Returns true if the stream has data to be read.\n    pub fn ready(&self) -> bool {\n        let (_, buf) = match self.data.first_key_value() {\n            Some(v) => v,\n            None => return false,\n        };\n\n        buf.off() == self.off\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use rstest::rstest;\n\n    // Helper function for testing either buffer emit or discard.\n    //\n    // The `emit` parameter controls whether data is emitted or discarded from\n    // `recv`.\n    //\n    // The `target_len` parameter controls the maximum amount of bytes that\n    // could be read, up to the capacity of `recv`. The `result_len` is the\n    // actual number of bytes that were taken out of `recv`. An assert is\n    // performed on `result_len` to ensure the number of bytes read meets the\n    // caller expectations.\n    //\n    // The `is_fin` parameter relates to the buffer's finished status. An assert\n    // is performed on it to ensure the status meet the caller expectations.\n    //\n    // The `test_bytes` parameter carries an optional slice of bytes. Is set, an\n    // assert is performed against the bytes that were read out of the buffer,\n    // to ensure caller expectations are met.\n    fn assert_emit_discard(\n        recv: &mut RecvBuf, emit: bool, target_len: usize, result_len: usize,\n        is_fin: bool, test_bytes: Option<&[u8]>,\n    ) {\n        let mut buf = [0; 32];\n        let action = if emit {\n            RecvAction::Emit {\n                out: &mut buf[..target_len],\n            }\n        } else {\n            RecvAction::Discard { len: target_len }\n        };\n\n        let (read, fin) = recv.emit_or_discard(action).unwrap();\n\n        if emit {\n            if let Some(v) = test_bytes {\n                assert_eq!(&buf[..read], v);\n            }\n        }\n\n        assert_eq!(read, result_len);\n        assert_eq!(is_fin, fin);\n    }\n\n    // Helper function for testing buffer status for either emit or discard.\n    fn assert_emit_discard_done(recv: &mut RecvBuf, emit: bool) {\n        let mut buf = [0; 32];\n        let action = if emit {\n            RecvAction::Emit { out: &mut buf }\n        } else {\n            RecvAction::Discard { len: 32 }\n        };\n        assert_eq!(recv.emit_or_discard(action), Err(Error::Done));\n    }\n\n    #[rstest]\n    fn empty_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn empty_stream_frame(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(15, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let buf = RangeBuf::from(b\"hello\", 0, false);\n        assert!(recv.write(buf).is_ok());\n        assert_eq!(recv.len, 5);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert_emit_discard(&mut recv, emit, 32, 5, false, None);\n\n        // Don't store non-fin empty buffer.\n        let buf = RangeBuf::from(b\"\", 10, false);\n        assert!(recv.write(buf).is_ok());\n        assert_eq!(recv.len, 5);\n        assert_eq!(recv.off, 5);\n        assert_eq!(recv.data.len(), 0);\n\n        // Check flow control for empty buffer.\n        let buf = RangeBuf::from(b\"\", 16, false);\n        assert_eq!(recv.write(buf), Err(Error::FlowControl));\n\n        // Store fin empty buffer.\n        let buf = RangeBuf::from(b\"\", 5, true);\n        assert!(recv.write(buf).is_ok());\n        assert_eq!(recv.len, 5);\n        assert_eq!(recv.off, 5);\n        assert_eq!(recv.data.len(), 1);\n\n        // Don't store additional fin empty buffers.\n        let buf = RangeBuf::from(b\"\", 5, true);\n        assert!(recv.write(buf).is_ok());\n        assert_eq!(recv.len, 5);\n        assert_eq!(recv.off, 5);\n        assert_eq!(recv.data.len(), 1);\n\n        // Don't store additional fin non-empty buffers.\n        let buf = RangeBuf::from(b\"aa\", 3, true);\n        assert!(recv.write(buf).is_ok());\n        assert_eq!(recv.len, 5);\n        assert_eq!(recv.off, 5);\n        assert_eq!(recv.data.len(), 1);\n\n        // Validate final size with fin empty buffers.\n        let buf = RangeBuf::from(b\"\", 6, true);\n        assert_eq!(recv.write(buf), Err(Error::FinalSize));\n        let buf = RangeBuf::from(b\"\", 4, true);\n        assert_eq!(recv.write(buf), Err(Error::FinalSize));\n\n        assert_emit_discard(&mut recv, emit, 32, 0, true, None);\n    }\n\n    #[rstest]\n    fn ordered_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n        let second = RangeBuf::from(b\"world\", 5, false);\n        let third = RangeBuf::from(b\"something\", 10, true);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 10);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n\n        assert!(recv.write(third).is_ok());\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard(\n            &mut recv,\n            emit,\n            32,\n            19,\n            true,\n            Some(b\"helloworldsomething\"),\n        );\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 19);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    /// Test shutdown behavior\n    #[rstest]\n    fn shutdown(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n        let second = RangeBuf::from(b\"world\", 5, false);\n        let third = RangeBuf::from(b\"something\", 10, false);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 10);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n\n        // shutdown the buffer. Buffer is dropped.\n        assert_eq!(recv.shutdown(), Ok(10));\n        assert_eq!(recv.len, 10);\n        assert_eq!(recv.off, 10);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n\n        // subsequent writes are validated but not added to the buffer\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 10);\n        assert_eq!(recv.off, 10);\n        assert_eq!(recv.data.len(), 0);\n\n        // the max offset of received data can increase and\n        // the recv.off must increase with it\n        assert!(recv.write(third).is_ok());\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 19);\n        assert_eq!(recv.data.len(), 0);\n\n        // Send a reset\n        assert_emit_discard_done(&mut recv, emit);\n        assert_eq!(\n            recv.reset(42, 123),\n            Ok(RecvBufResetReturn {\n                max_data_delta: 104,\n                consumed_flowcontrol: 104,\n            })\n        );\n        assert_eq!(recv.len, 123);\n        assert_eq!(recv.off, 123);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn split_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"something\", 0, false);\n        let second = RangeBuf::from(b\"helloworld\", 9, true);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard(&mut recv, emit, 10, 10, false, Some(b\"somethingh\"));\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 10);\n\n        assert_emit_discard(&mut recv, emit, 5, 5, false, Some(b\"ellow\"));\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 15);\n\n        assert_emit_discard(&mut recv, emit, 5, 4, true, Some(b\"orld\"));\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 19);\n    }\n\n    #[rstest]\n    fn incomplete_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let mut buf = [0; 32];\n\n        let first = RangeBuf::from(b\"something\", 0, false);\n        let second = RangeBuf::from(b\"helloworld\", 9, true);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 0);\n\n        let action = if emit {\n            RecvAction::Emit { out: &mut buf }\n        } else {\n            RecvAction::Discard { len: 32 }\n        };\n        assert_eq!(recv.emit_or_discard(action), Err(Error::Done));\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard(\n            &mut recv,\n            emit,\n            32,\n            19,\n            true,\n            Some(b\"somethinghelloworld\"),\n        );\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 19);\n    }\n\n    #[rstest]\n    fn zero_len_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"something\", 0, false);\n        let second = RangeBuf::from(b\"\", 9, true);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert_emit_discard(&mut recv, emit, 32, 9, true, Some(b\"something\"));\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 9);\n    }\n\n    #[rstest]\n    fn past_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"something\", 0, false);\n        let second = RangeBuf::from(b\"hello\", 3, false);\n        let third = RangeBuf::from(b\"ello\", 4, true);\n        let fourth = RangeBuf::from(b\"ello\", 5, true);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert_emit_discard(&mut recv, emit, 32, 9, false, Some(b\"something\"));\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 9);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 9);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_eq!(recv.write(third), Err(Error::FinalSize));\n\n        assert!(recv.write(fourth).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 9);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn fully_overlapping_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"something\", 0, false);\n        let second = RangeBuf::from(b\"hello\", 4, false);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert_emit_discard(&mut recv, emit, 32, 9, false, Some(b\"something\"));\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 9);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn fully_overlapping_read2(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"something\", 0, false);\n        let second = RangeBuf::from(b\"hello\", 4, false);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 2);\n\n        assert_emit_discard(&mut recv, emit, 32, 9, false, Some(b\"somehello\"));\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 9);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn fully_overlapping_read3(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"something\", 0, false);\n        let second = RangeBuf::from(b\"hello\", 3, false);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 8);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 3);\n\n        assert_emit_discard(&mut recv, emit, 32, 9, false, Some(b\"somhellog\"));\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 9);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn fully_overlapping_read_multi(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"somethingsomething\", 0, false);\n        let second = RangeBuf::from(b\"hello\", 3, false);\n        let third = RangeBuf::from(b\"hello\", 12, false);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 8);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(third).is_ok());\n        assert_eq!(recv.len, 17);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 2);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 18);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 5);\n\n        assert_emit_discard(\n            &mut recv,\n            emit,\n            32,\n            18,\n            false,\n            Some(b\"somhellogsomhellog\"),\n        );\n        assert_eq!(recv.len, 18);\n        assert_eq!(recv.off, 18);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn overlapping_start_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"something\", 0, false);\n        let second = RangeBuf::from(b\"hello\", 8, true);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 13);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 2);\n\n        assert_emit_discard(\n            &mut recv,\n            emit,\n            32,\n            13,\n            true,\n            Some(b\"somethingello\"),\n        );\n\n        assert_eq!(recv.len, 13);\n        assert_eq!(recv.off, 13);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn overlapping_end_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n        let second = RangeBuf::from(b\"something\", 3, true);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 12);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 12);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 2);\n\n        assert_emit_discard(&mut recv, emit, 32, 12, true, Some(b\"helsomething\"));\n        assert_eq!(recv.len, 12);\n        assert_eq!(recv.off, 12);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn overlapping_end_twice_read(#[values(true, false)] emit: bool) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"he\", 0, false);\n        let second = RangeBuf::from(b\"ow\", 4, false);\n        let third = RangeBuf::from(b\"rl\", 7, false);\n        let fourth = RangeBuf::from(b\"helloworld\", 0, true);\n\n        assert!(recv.write(third).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 2);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 3);\n\n        assert!(recv.write(fourth).is_ok());\n        assert_eq!(recv.len, 10);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 6);\n\n        assert_emit_discard(&mut recv, emit, 32, 10, true, Some(b\"helloworld\"));\n        assert_eq!(recv.len, 10);\n        assert_eq!(recv.off, 10);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn overlapping_end_twice_and_contained_read(\n        #[values(true, false)] emit: bool,\n    ) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"hellow\", 0, false);\n        let second = RangeBuf::from(b\"barfoo\", 10, true);\n        let third = RangeBuf::from(b\"rl\", 7, false);\n        let fourth = RangeBuf::from(b\"elloworldbarfoo\", 1, true);\n\n        assert!(recv.write(third).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 16);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 2);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 16);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 3);\n\n        assert!(recv.write(fourth).is_ok());\n        assert_eq!(recv.len, 16);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 5);\n\n        assert_emit_discard(\n            &mut recv,\n            emit,\n            32,\n            16,\n            true,\n            Some(b\"helloworldbarfoo\"),\n        );\n        assert_eq!(recv.len, 16);\n        assert_eq!(recv.off, 16);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn partially_multi_overlapping_reordered_read(\n        #[values(true, false)] emit: bool,\n    ) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"hello\", 8, false);\n        let second = RangeBuf::from(b\"something\", 0, false);\n        let third = RangeBuf::from(b\"moar\", 11, true);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 13);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 13);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 2);\n\n        assert!(recv.write(third).is_ok());\n        assert_eq!(recv.len, 15);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 3);\n\n        assert_emit_discard(\n            &mut recv,\n            emit,\n            32,\n            15,\n            true,\n            Some(b\"somethinhelloar\"),\n        );\n        assert_eq!(recv.len, 15);\n        assert_eq!(recv.off, 15);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[rstest]\n    fn partially_multi_overlapping_reordered_read2(\n        #[values(true, false)] emit: bool,\n    ) {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"aaa\", 0, false);\n        let second = RangeBuf::from(b\"bbb\", 2, false);\n        let third = RangeBuf::from(b\"ccc\", 4, false);\n        let fourth = RangeBuf::from(b\"ddd\", 6, false);\n        let fifth = RangeBuf::from(b\"eee\", 9, false);\n        let sixth = RangeBuf::from(b\"fff\", 11, false);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 5);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 1);\n\n        assert!(recv.write(fourth).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 2);\n\n        assert!(recv.write(third).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 3);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 9);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 4);\n\n        assert!(recv.write(sixth).is_ok());\n        assert_eq!(recv.len, 14);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 5);\n\n        assert!(recv.write(fifth).is_ok());\n        assert_eq!(recv.len, 14);\n        assert_eq!(recv.off, 0);\n        assert_eq!(recv.data.len(), 6);\n\n        assert_emit_discard(\n            &mut recv,\n            emit,\n            32,\n            14,\n            false,\n            Some(b\"aabbbcdddeefff\"),\n        );\n        assert_eq!(recv.len, 14);\n        assert_eq!(recv.off, 14);\n        assert_eq!(recv.data.len(), 0);\n\n        assert_emit_discard_done(&mut recv, emit);\n    }\n\n    #[test]\n    fn mixed_read_actions() {\n        let mut recv = RecvBuf::new(u64::MAX, DEFAULT_STREAM_WINDOW);\n        assert_eq!(recv.len, 0);\n\n        let first = RangeBuf::from(b\"hello\", 0, false);\n        let second = RangeBuf::from(b\"world\", 5, false);\n        let third = RangeBuf::from(b\"something\", 10, true);\n\n        assert!(recv.write(second).is_ok());\n        assert_eq!(recv.len, 10);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard_done(&mut recv, true);\n        assert_emit_discard_done(&mut recv, false);\n\n        assert!(recv.write(third).is_ok());\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard_done(&mut recv, true);\n        assert_emit_discard_done(&mut recv, false);\n\n        assert!(recv.write(first).is_ok());\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 0);\n\n        assert_emit_discard(&mut recv, true, 5, 5, false, Some(b\"hello\"));\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 5);\n\n        assert_emit_discard(&mut recv, false, 5, 5, false, None);\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 10);\n\n        assert_emit_discard(&mut recv, true, 9, 9, true, Some(b\"something\"));\n        assert_eq!(recv.len, 19);\n        assert_eq!(recv.off, 19);\n\n        assert_emit_discard_done(&mut recv, true);\n        assert_emit_discard_done(&mut recv, false);\n    }\n}\n"
  },
  {
    "path": "quiche/src/stream/send_buf.rs",
    "content": "// Copyright (C) 2023, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::cmp;\n\nuse std::collections::VecDeque;\n\nuse crate::buffers::BufSplit;\nuse crate::range_buf::RangeBuf;\nuse crate::BufFactory;\nuse crate::Error;\nuse crate::Result;\n\nuse crate::buffers::DefaultBufFactory;\nuse crate::ranges;\n\n#[cfg(test)]\nconst SEND_BUFFER_SIZE: usize = 5;\n\n#[cfg(not(test))]\nconst SEND_BUFFER_SIZE: usize = 4096;\n\nstruct SendReserve<'a, F: BufFactory> {\n    inner: &'a mut SendBuf<F>,\n    reserved: usize,\n    fin: bool,\n}\n\nimpl<F: BufFactory> SendReserve<'_, F> {\n    fn append_buf(&mut self, buf: F::Buf) -> Result<()> {\n        let len = buf.as_ref().len();\n        let inner = &mut self.inner;\n\n        if len > self.reserved {\n            return Err(Error::BufferTooShort);\n        }\n\n        let fin: bool = self.reserved == len && self.fin;\n\n        let buf = RangeBuf::from_raw(buf, inner.off, fin);\n\n        // The new data can simply be appended at the end of the send buffer.\n        inner.data.push_back(buf);\n\n        inner.off += len as u64;\n        inner.len += len as u64;\n        self.reserved -= len;\n\n        Ok(())\n    }\n}\n\nimpl<F: BufFactory> Drop for SendReserve<'_, F> {\n    fn drop(&mut self) {\n        assert_eq!(self.reserved, 0)\n    }\n}\n\n/// Send-side stream buffer.\n///\n/// Stream data scheduled to be sent to the peer is buffered in a list of data\n/// chunks ordered by offset in ascending order. Contiguous data can then be\n/// read into a slice.\n///\n/// By default, new data is appended at the end of the stream, but data can be\n/// inserted at the start of the buffer (this is to allow data that needs to be\n/// retransmitted to be re-buffered).\n#[derive(Debug, Default)]\npub struct SendBuf<F = DefaultBufFactory>\nwhere\n    F: BufFactory,\n{\n    /// Chunks of data to be sent, ordered by offset.\n    data: VecDeque<RangeBuf<F>>,\n\n    /// The index of the buffer that needs to be sent next.\n    pos: usize,\n\n    /// The maximum offset of data buffered in the stream.\n    off: u64,\n\n    /// The maximum offset of data sent to the peer, regardless of\n    /// retransmissions.\n    emit_off: u64,\n\n    /// The amount of data currently buffered.\n    len: u64,\n\n    /// The maximum offset we are allowed to send to the peer.\n    max_data: u64,\n\n    /// The last offset the stream was blocked at, if any.\n    blocked_at: Option<u64>,\n\n    /// The final stream offset written to the stream, if any.\n    fin_off: Option<u64>,\n\n    /// Whether the stream's send-side has been shut down.\n    shutdown: bool,\n\n    /// Ranges of data offsets that have been acked.\n    acked: ranges::RangeSet,\n\n    /// The error code received via STOP_SENDING.\n    error: Option<u64>,\n}\n\nimpl<F: BufFactory> SendBuf<F> {\n    /// Creates a new send buffer.\n    pub fn new(max_data: u64) -> SendBuf<F> {\n        SendBuf {\n            max_data,\n            ..SendBuf::default()\n        }\n    }\n\n    /// Try to reserve the required number of bytes to be sent\n    fn reserve_for_write(\n        &mut self, mut len: usize, mut fin: bool,\n    ) -> Result<SendReserve<'_, F>> {\n        let max_off = self.off + len as u64;\n\n        // Get the stream send capacity. This will return an error if the stream\n        // was stopped.\n        if len > self.cap()? {\n            len = self.cap()?;\n            fin = false;\n        }\n\n        if let Some(fin_off) = self.fin_off {\n            // Can't write past final offset.\n            if max_off > fin_off {\n                return Err(Error::FinalSize);\n            }\n\n            // Can't \"undo\" final offset.\n            if max_off == fin_off && !fin {\n                return Err(Error::FinalSize);\n            }\n        }\n\n        if fin {\n            self.fin_off = Some(max_off);\n        }\n\n        // Don't queue data that was already fully acked.\n        if self.ack_off() >= max_off {\n            return Ok(SendReserve {\n                inner: self,\n                reserved: 0,\n                fin,\n            });\n        }\n\n        Ok(SendReserve {\n            inner: self,\n            reserved: len,\n            fin,\n        })\n    }\n\n    /// Inserts the given slice of data at the end of the buffer.\n    ///\n    /// The number of bytes that were actually stored in the buffer is returned\n    /// (this may be lower than the size of the input buffer, in case of partial\n    /// writes).\n    pub fn write(&mut self, data: &[u8], fin: bool) -> Result<usize> {\n        let mut reserve = self.reserve_for_write(data.len(), fin)?;\n\n        if reserve.reserved == 0 {\n            return Ok(0);\n        }\n\n        let ret = reserve.reserved;\n\n        // Split the remaining input data into consistently-sized buffers to\n        // avoid fragmentation.\n        for chunk in data[..reserve.reserved].chunks(SEND_BUFFER_SIZE) {\n            reserve.append_buf(F::buf_from_slice(chunk))?;\n        }\n\n        Ok(ret)\n    }\n\n    /// Inserts the given buffer of data at the end of the buffer.\n    ///\n    /// The number of bytes that were actually stored in the buffer is returned\n    /// (this may be lower than the size of the input buffer, in case of partial\n    /// writes, in which case the unwritten buffer is also returned).\n    pub fn append_buf(\n        &mut self, mut data: F::Buf, cap: usize, fin: bool,\n    ) -> Result<(usize, Option<F::Buf>)>\n    where\n        F::Buf: BufSplit,\n    {\n        let len = data.as_ref().len();\n        let mut reserve = self.reserve_for_write(cap.min(len), fin)?;\n\n        if reserve.reserved == 0 {\n            return Ok((0, Some(data)));\n        }\n\n        let remainder =\n            (reserve.reserved < len).then(|| data.split_at(reserve.reserved));\n\n        let ret = reserve.reserved;\n\n        reserve.append_buf(data)?;\n\n        Ok((ret, remainder))\n    }\n\n    /// Writes data from the send buffer into the given output buffer.\n    pub fn emit(&mut self, out: &mut [u8]) -> Result<(usize, bool)> {\n        let mut out_len = out.len();\n        let out_off = self.off_front();\n\n        let mut next_off = out_off;\n\n        while out_len > 0 {\n            let off_front = self.off_front();\n\n            if self.is_empty() ||\n                off_front >= self.off ||\n                off_front != next_off ||\n                off_front >= self.max_data\n            {\n                break;\n            }\n\n            let buf = match self.data.get_mut(self.pos) {\n                Some(v) => v,\n\n                None => break,\n            };\n\n            if buf.is_empty() {\n                self.pos += 1;\n                continue;\n            }\n\n            let buf_len = cmp::min(buf.len(), out_len);\n            let partial = buf_len < buf.len();\n\n            // Copy data to the output buffer.\n            let out_pos = (next_off - out_off) as usize;\n            out[out_pos..out_pos + buf_len].copy_from_slice(&buf[..buf_len]);\n\n            self.len -= buf_len as u64;\n\n            out_len -= buf_len;\n\n            next_off = buf.off() + buf_len as u64;\n\n            buf.consume(buf_len);\n\n            if partial {\n                // We reached the maximum capacity, so end here.\n                break;\n            }\n\n            self.pos += 1;\n        }\n\n        // Override the `fin` flag set for the output buffer by matching the\n        // buffer's maximum offset against the stream's final offset (if known).\n        //\n        // This is more efficient than tracking `fin` using the range buffers\n        // themselves, and lets us avoid queueing empty buffers just so we can\n        // propagate the final size.\n        let fin = self.fin_off == Some(next_off);\n\n        // Record the largest offset that has been sent so we can accurately\n        // report final_size\n        self.emit_off = cmp::max(self.emit_off, next_off);\n\n        Ok((out.len() - out_len, fin))\n    }\n\n    /// Updates the max_data limit to the given value.\n    pub fn update_max_data(&mut self, max_data: u64) {\n        self.max_data = cmp::max(self.max_data, max_data);\n    }\n\n    /// Updates the last offset the stream was blocked at, if any.\n    pub fn update_blocked_at(&mut self, blocked_at: Option<u64>) {\n        self.blocked_at = blocked_at;\n    }\n\n    /// The last offset the stream was blocked at, if any.\n    pub fn blocked_at(&self) -> Option<u64> {\n        self.blocked_at\n    }\n\n    /// Increments the acked data offset.\n    pub fn ack(&mut self, off: u64, len: usize) {\n        self.acked.insert(off..off + len as u64);\n    }\n\n    pub fn ack_and_drop(&mut self, off: u64, len: usize) {\n        self.ack(off, len);\n\n        let ack_off = self.ack_off();\n\n        if self.data.is_empty() {\n            return;\n        }\n\n        if off > ack_off {\n            return;\n        }\n\n        let mut drop_until = None;\n\n        // Drop contiguously acked data from the front of the buffer.\n        for (i, buf) in self.data.iter_mut().enumerate() {\n            // Newly acked range is past highest contiguous acked range, so we\n            // can't drop it.\n            if buf.off >= ack_off {\n                break;\n            }\n\n            // Highest contiguous acked range falls within newly acked range,\n            // so we can't drop it.\n            if buf.off < ack_off && ack_off < buf.max_off() {\n                break;\n            }\n\n            // Newly acked range can be dropped.\n            drop_until = Some(i);\n        }\n\n        if let Some(drop) = drop_until {\n            self.data.drain(..=drop);\n\n            // When a buffer is marked for retransmission, but then acked before\n            // it could be retransmitted, we might end up decreasing the SendBuf\n            // position too much, so make sure that doesn't happen.\n            self.pos = self.pos.saturating_sub(drop + 1);\n        }\n    }\n\n    pub fn retransmit(&mut self, off: u64, len: usize) {\n        let max_off = off + len as u64;\n        let ack_off = self.ack_off();\n\n        if self.data.is_empty() {\n            return;\n        }\n\n        if max_off <= ack_off {\n            return;\n        }\n\n        for i in 0..self.data.len() {\n            let buf = &mut self.data[i];\n\n            if buf.off >= max_off {\n                break;\n            }\n\n            if off > buf.max_off() {\n                continue;\n            }\n\n            // Split the buffer into 2 if the retransmit range ends before the\n            // buffer's final offset.\n            let new_buf = if buf.off < max_off && max_off < buf.max_off() {\n                Some(buf.split_off((max_off - buf.off) as usize))\n            } else {\n                None\n            };\n\n            let prev_pos = buf.pos;\n\n            // Reduce the buffer's position (expand the buffer) if the retransmit\n            // range is past the buffer's starting offset.\n            buf.pos = if off > buf.off && off <= buf.max_off() {\n                cmp::min(buf.pos, buf.start + (off - buf.off) as usize)\n            } else {\n                buf.start\n            };\n\n            self.pos = cmp::min(self.pos, i);\n\n            self.len += (prev_pos - buf.pos) as u64;\n\n            if let Some(b) = new_buf {\n                self.data.insert(i + 1, b);\n            }\n        }\n    }\n\n    /// Resets the stream at the current offset and clears all buffered data.\n    pub fn reset(&mut self) -> (u64, u64) {\n        let unsent_off = cmp::max(self.off_front(), self.emit_off);\n        let unsent_len = self.off_back().saturating_sub(unsent_off);\n\n        self.fin_off = Some(unsent_off);\n\n        // Drop all buffered data.\n        self.data.clear();\n\n        // Mark relevant data as acked.\n        self.off = unsent_off;\n        self.ack(0, self.off as usize);\n\n        self.pos = 0;\n        self.len = 0;\n\n        (self.emit_off, unsent_len)\n    }\n\n    /// Resets the streams and records the received error code.\n    ///\n    /// Calling this again after the first time has no effect.\n    pub fn stop(&mut self, error_code: u64) -> Result<(u64, u64)> {\n        if self.error.is_some() {\n            return Err(Error::Done);\n        }\n\n        let (max_off, unsent) = self.reset();\n\n        self.error = Some(error_code);\n\n        Ok((max_off, unsent))\n    }\n\n    /// Shuts down sending data.\n    pub fn shutdown(&mut self) -> Result<(u64, u64)> {\n        if self.shutdown {\n            return Err(Error::Done);\n        }\n\n        self.shutdown = true;\n\n        Ok(self.reset())\n    }\n\n    /// Returns the largest offset of data buffered.\n    pub fn off_back(&self) -> u64 {\n        self.off\n    }\n\n    /// Returns the lowest offset of data buffered.\n    pub fn off_front(&self) -> u64 {\n        let mut pos = self.pos;\n\n        // Skip empty buffers from the start of the queue.\n        while let Some(b) = self.data.get(pos) {\n            if !b.is_empty() {\n                return b.off();\n            }\n\n            pos += 1;\n        }\n\n        self.off\n    }\n\n    /// The maximum offset we are allowed to send to the peer.\n    pub fn max_off(&self) -> u64 {\n        self.max_data\n    }\n\n    /// Returns true if all data in the stream has been sent.\n    ///\n    /// This happens when the stream's send final size is known, and the\n    /// application has already written data up to that point.\n    pub fn is_fin(&self) -> bool {\n        if self.fin_off == Some(self.off) {\n            return true;\n        }\n\n        false\n    }\n\n    /// Returns true if the send-side of the stream is complete.\n    ///\n    /// This happens when the stream's send final size is known, and the peer\n    /// has already acked all stream data up to that point.\n    pub fn is_complete(&self) -> bool {\n        if let Some(fin_off) = self.fin_off {\n            if self.acked == (0..fin_off) {\n                return true;\n            }\n        }\n\n        false\n    }\n\n    /// Returns true if the stream was stopped before completion.\n    pub fn is_stopped(&self) -> bool {\n        self.error.is_some()\n    }\n\n    /// Returns true if the stream was shut down.\n    pub fn is_shutdown(&self) -> bool {\n        self.shutdown\n    }\n\n    /// Returns true if there is no data.\n    pub fn is_empty(&self) -> bool {\n        self.data.is_empty()\n    }\n\n    /// Returns the highest contiguously acked offset.\n    pub fn ack_off(&self) -> u64 {\n        match self.acked.iter().next() {\n            // Only consider the initial range if it contiguously covers the\n            // start of the stream (i.e. from offset 0).\n            Some(std::ops::Range { start: 0, end }) => end,\n\n            Some(_) | None => 0,\n        }\n    }\n\n    /// Returns the outgoing flow control capacity.\n    pub fn cap(&self) -> Result<usize> {\n        // The stream was stopped, so return the error code instead.\n        if let Some(e) = self.error {\n            return Err(Error::StreamStopped(e));\n        }\n\n        Ok((self.max_data - self.off) as usize)\n    }\n\n    /// Returns the number of separate buffers stored.\n    #[allow(dead_code)]\n    pub fn bufs_count(&self) -> usize {\n        self.data.len()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn empty_write() {\n        let mut buf = [0; 5];\n\n        let mut send = <SendBuf>::new(u64::MAX);\n        assert_eq!(send.len, 0);\n\n        let (written, fin) = send.emit(&mut buf).unwrap();\n        assert_eq!(written, 0);\n        assert!(!fin);\n    }\n\n    #[test]\n    fn multi_write() {\n        let mut buf = [0; 128];\n\n        let mut send = <SendBuf>::new(u64::MAX);\n        assert_eq!(send.len, 0);\n\n        let first = b\"something\";\n        let second = b\"helloworld\";\n\n        assert!(send.write(first, false).is_ok());\n        assert_eq!(send.len, 9);\n\n        assert!(send.write(second, true).is_ok());\n        assert_eq!(send.len, 19);\n\n        let (written, fin) = send.emit(&mut buf[..128]).unwrap();\n        assert_eq!(written, 19);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"somethinghelloworld\");\n        assert_eq!(send.len, 0);\n    }\n\n    #[test]\n    fn split_write() {\n        let mut buf = [0; 10];\n\n        let mut send = <SendBuf>::new(u64::MAX);\n        assert_eq!(send.len, 0);\n\n        let first = b\"something\";\n        let second = b\"helloworld\";\n\n        assert!(send.write(first, false).is_ok());\n        assert_eq!(send.len, 9);\n\n        assert!(send.write(second, true).is_ok());\n        assert_eq!(send.len, 19);\n\n        assert_eq!(send.off_front(), 0);\n\n        let (written, fin) = send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 10);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"somethingh\");\n        assert_eq!(send.len, 9);\n\n        assert_eq!(send.off_front(), 10);\n\n        let (written, fin) = send.emit(&mut buf[..5]).unwrap();\n        assert_eq!(written, 5);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"ellow\");\n        assert_eq!(send.len, 4);\n\n        assert_eq!(send.off_front(), 15);\n\n        let (written, fin) = send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 4);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"orld\");\n        assert_eq!(send.len, 0);\n\n        assert_eq!(send.off_front(), 19);\n    }\n\n    #[test]\n    fn resend() {\n        let mut buf = [0; 15];\n\n        let mut send = <SendBuf>::new(u64::MAX);\n        assert_eq!(send.len, 0);\n        assert_eq!(send.off_front(), 0);\n\n        let first = b\"something\";\n        let second = b\"helloworld\";\n\n        assert!(send.write(first, false).is_ok());\n        assert_eq!(send.off_front(), 0);\n\n        assert!(send.write(second, true).is_ok());\n        assert_eq!(send.off_front(), 0);\n\n        assert_eq!(send.len, 19);\n\n        let (written, fin) = send.emit(&mut buf[..4]).unwrap();\n        assert_eq!(written, 4);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"some\");\n        assert_eq!(send.len, 15);\n        assert_eq!(send.off_front(), 4);\n\n        let (written, fin) = send.emit(&mut buf[..5]).unwrap();\n        assert_eq!(written, 5);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"thing\");\n        assert_eq!(send.len, 10);\n        assert_eq!(send.off_front(), 9);\n\n        let (written, fin) = send.emit(&mut buf[..5]).unwrap();\n        assert_eq!(written, 5);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"hello\");\n        assert_eq!(send.len, 5);\n        assert_eq!(send.off_front(), 14);\n\n        send.retransmit(4, 5);\n        assert_eq!(send.len, 10);\n        assert_eq!(send.off_front(), 4);\n\n        send.retransmit(0, 4);\n        assert_eq!(send.len, 14);\n        assert_eq!(send.off_front(), 0);\n\n        let (written, fin) = send.emit(&mut buf[..11]).unwrap();\n        assert_eq!(written, 9);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"something\");\n        assert_eq!(send.len, 5);\n        assert_eq!(send.off_front(), 14);\n\n        let (written, fin) = send.emit(&mut buf[..11]).unwrap();\n        assert_eq!(written, 5);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"world\");\n        assert_eq!(send.len, 0);\n        assert_eq!(send.off_front(), 19);\n    }\n\n    #[test]\n    fn write_blocked_by_off() {\n        let mut buf = [0; 10];\n\n        let mut send = <SendBuf>::default();\n        assert_eq!(send.len, 0);\n\n        let first = b\"something\";\n        let second = b\"helloworld\";\n\n        assert_eq!(send.write(first, false), Ok(0));\n        assert_eq!(send.len, 0);\n\n        assert_eq!(send.write(second, true), Ok(0));\n        assert_eq!(send.len, 0);\n\n        send.update_max_data(5);\n\n        assert_eq!(send.write(first, false), Ok(5));\n        assert_eq!(send.len, 5);\n\n        assert_eq!(send.write(second, true), Ok(0));\n        assert_eq!(send.len, 5);\n\n        assert_eq!(send.off_front(), 0);\n\n        let (written, fin) = send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 5);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"somet\");\n        assert_eq!(send.len, 0);\n\n        assert_eq!(send.off_front(), 5);\n\n        let (written, fin) = send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 0);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"\");\n        assert_eq!(send.len, 0);\n\n        send.update_max_data(15);\n\n        assert_eq!(send.write(&first[5..], false), Ok(4));\n        assert_eq!(send.len, 4);\n\n        assert_eq!(send.write(second, true), Ok(6));\n        assert_eq!(send.len, 10);\n\n        assert_eq!(send.off_front(), 5);\n\n        let (written, fin) = send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 10);\n        assert!(!fin);\n        assert_eq!(&buf[..10], b\"hinghellow\");\n        assert_eq!(send.len, 0);\n\n        send.update_max_data(25);\n\n        assert_eq!(send.write(&second[6..], true), Ok(4));\n        assert_eq!(send.len, 4);\n\n        assert_eq!(send.off_front(), 15);\n\n        let (written, fin) = send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 4);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"orld\");\n        assert_eq!(send.len, 0);\n    }\n\n    #[test]\n    fn zero_len_write() {\n        let mut buf = [0; 10];\n\n        let mut send = <SendBuf>::new(u64::MAX);\n        assert_eq!(send.len, 0);\n\n        let first = b\"something\";\n\n        assert!(send.write(first, false).is_ok());\n        assert_eq!(send.len, 9);\n\n        assert!(send.write(&[], true).is_ok());\n        assert_eq!(send.len, 9);\n\n        assert_eq!(send.off_front(), 0);\n\n        let (written, fin) = send.emit(&mut buf[..10]).unwrap();\n        assert_eq!(written, 9);\n        assert!(fin);\n        assert_eq!(&buf[..written], b\"something\");\n        assert_eq!(send.len, 0);\n    }\n\n    /// Check SendBuf::len calculation on a retransmit case\n    #[test]\n    fn send_buf_len_on_retransmit() {\n        let mut buf = [0; 15];\n\n        let mut send = <SendBuf>::new(u64::MAX);\n        assert_eq!(send.len, 0);\n        assert_eq!(send.off_front(), 0);\n\n        let first = b\"something\";\n\n        assert!(send.write(first, false).is_ok());\n        assert_eq!(send.off_front(), 0);\n\n        assert_eq!(send.len, 9);\n\n        let (written, fin) = send.emit(&mut buf[..4]).unwrap();\n        assert_eq!(written, 4);\n        assert!(!fin);\n        assert_eq!(&buf[..written], b\"some\");\n        assert_eq!(send.len, 5);\n        assert_eq!(send.off_front(), 4);\n\n        send.retransmit(3, 5);\n        assert_eq!(send.len, 6);\n        assert_eq!(send.off_front(), 3);\n    }\n\n    #[test]\n    fn send_buf_final_size_retransmit() {\n        let mut buf = [0; 50];\n        let mut send = <SendBuf>::new(u64::MAX);\n\n        send.write(&buf, false).unwrap();\n        assert_eq!(send.off_front(), 0);\n\n        // Emit the whole buffer\n        let (written, _fin) = send.emit(&mut buf).unwrap();\n        assert_eq!(written, buf.len());\n        assert_eq!(send.off_front(), buf.len() as u64);\n\n        // Server decides to retransmit the last 10 bytes. It's possible\n        // it's not actually lost and that the client did receive it.\n        send.retransmit(40, 10);\n\n        // Server receives STOP_SENDING from client. The final_size we\n        // send in the RESET_STREAM should be 50. If we send anything less,\n        // it's a FINAL_SIZE_ERROR.\n        let (fin_off, unsent) = send.stop(0).unwrap();\n        assert_eq!(fin_off, 50);\n        assert_eq!(unsent, 0);\n    }\n}\n"
  },
  {
    "path": "quiche/src/test_utils.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::*;\n\nuse smallvec::smallvec;\n\nuse crate::recovery::Sent;\n\npub struct Pipe<F = DefaultBufFactory>\nwhere\n    F: BufFactory,\n{\n    pub client: Connection<F>,\n    pub server: Connection<F>,\n}\n\nimpl Pipe {\n    pub fn default_config(cc_algorithm_name: &str) -> Result<Config> {\n        let mut config = Config::new(PROTOCOL_VERSION)?;\n        assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n        config.load_cert_chain_from_pem_file(\"examples/cert.crt\")?;\n        config.load_priv_key_from_pem_file(\"examples/cert.key\")?;\n        config.set_application_protos(&[b\"proto1\", b\"proto2\"])?;\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.set_max_idle_timeout(180_000);\n        config.verify_peer(false);\n        config.set_ack_delay_exponent(8);\n        Ok(config)\n    }\n\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    pub fn default_tls_ctx_builder() -> boring::ssl::SslContextBuilder {\n        let mut ctx_builder =\n            boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls())\n                .unwrap();\n        ctx_builder\n            .set_certificate_chain_file(\"examples/cert.crt\")\n            .unwrap();\n        ctx_builder\n            .set_private_key_file(\n                \"examples/cert.key\",\n                boring::ssl::SslFiletype::PEM,\n            )\n            .unwrap();\n\n        ctx_builder\n    }\n\n    pub fn client_addr() -> SocketAddr {\n        \"127.0.0.1:1234\".parse().unwrap()\n    }\n\n    pub fn server_addr() -> SocketAddr {\n        \"127.0.0.1:4321\".parse().unwrap()\n    }\n\n    pub fn new(cc_algorithm_name: &str) -> Result<Pipe> {\n        let mut config = Self::default_config(cc_algorithm_name)?;\n        Pipe::with_config(&mut config)\n    }\n\n    pub fn with_config(config: &mut Config) -> Result<Pipe> {\n        Pipe::<DefaultBufFactory>::with_config_and_buf(config)\n    }\n\n    pub fn with_config_and_scid_lengths(\n        config: &mut Config, client_scid_len: usize, server_scid_len: usize,\n    ) -> Result<Pipe> {\n        Pipe::<DefaultBufFactory>::with_config_and_scid_lengths_and_buf(\n            config,\n            client_scid_len,\n            server_scid_len,\n        )\n    }\n\n    pub fn with_client_config(client_config: &mut Config) -> Result<Pipe> {\n        Pipe::<DefaultBufFactory>::with_client_config_and_buf(client_config)\n    }\n\n    pub fn with_server_config(server_config: &mut Config) -> Result<Pipe> {\n        Pipe::<DefaultBufFactory>::with_server_config_and_buf(server_config)\n    }\n\n    pub fn with_client_and_server_config(\n        client_config: &mut Config, server_config: &mut Config,\n    ) -> Result<Pipe> {\n        Pipe::<DefaultBufFactory>::with_client_and_server_config_and_buf(\n            client_config,\n            server_config,\n        )\n    }\n}\n\nimpl<F: BufFactory> Pipe<F> {\n    pub fn new_with_buf(cc_algorithm_name: &str) -> Result<Pipe<F>> {\n        let mut config = Pipe::default_config(cc_algorithm_name)?;\n        Pipe::with_config_and_buf(&mut config)\n    }\n\n    pub fn with_config_and_buf(config: &mut Config) -> Result<Pipe<F>> {\n        let mut client_scid = [0; 16];\n        rand::rand_bytes(&mut client_scid[..]);\n        let client_scid = ConnectionId::from_ref(&client_scid);\n        let client_addr = Pipe::client_addr();\n\n        let mut server_scid = [0; 16];\n        rand::rand_bytes(&mut server_scid[..]);\n        let server_scid = ConnectionId::from_ref(&server_scid);\n        let server_addr = Pipe::server_addr();\n\n        Ok(Pipe {\n            client: connect_with_buffer_factory(\n                Some(\"quic.tech\"),\n                &client_scid,\n                client_addr,\n                server_addr,\n                config,\n            )?,\n            server: accept_with_buf_factory(\n                &server_scid,\n                None,\n                server_addr,\n                client_addr,\n                config,\n            )?,\n        })\n    }\n\n    pub fn with_config_and_scid_lengths_and_buf(\n        config: &mut Config, client_scid_len: usize, server_scid_len: usize,\n    ) -> Result<Pipe<F>> {\n        let mut client_scid = vec![0; client_scid_len];\n        rand::rand_bytes(&mut client_scid[..]);\n        let client_scid = ConnectionId::from_ref(&client_scid);\n        let client_addr = Pipe::client_addr();\n\n        let mut server_scid = vec![0; server_scid_len];\n        rand::rand_bytes(&mut server_scid[..]);\n        let server_scid = ConnectionId::from_ref(&server_scid);\n        let server_addr = Pipe::server_addr();\n\n        Ok(Pipe {\n            client: connect_with_buffer_factory(\n                Some(\"quic.tech\"),\n                &client_scid,\n                client_addr,\n                server_addr,\n                config,\n            )?,\n            server: accept_with_buf_factory(\n                &server_scid,\n                None,\n                server_addr,\n                client_addr,\n                config,\n            )?,\n        })\n    }\n\n    pub fn with_client_config_and_buf(\n        client_config: &mut Config,\n    ) -> Result<Pipe<F>> {\n        let mut client_scid = [0; 16];\n        rand::rand_bytes(&mut client_scid[..]);\n        let client_scid = ConnectionId::from_ref(&client_scid);\n        let client_addr = Pipe::client_addr();\n\n        let mut server_scid = [0; 16];\n        rand::rand_bytes(&mut server_scid[..]);\n        let server_scid = ConnectionId::from_ref(&server_scid);\n        let server_addr = Pipe::server_addr();\n\n        let mut config = Config::new(PROTOCOL_VERSION)?;\n        config.load_cert_chain_from_pem_file(\"examples/cert.crt\")?;\n        config.load_priv_key_from_pem_file(\"examples/cert.key\")?;\n        config.set_application_protos(&[b\"proto1\", b\"proto2\"])?;\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.set_ack_delay_exponent(8);\n\n        Ok(Pipe {\n            client: connect_with_buffer_factory(\n                Some(\"quic.tech\"),\n                &client_scid,\n                client_addr,\n                server_addr,\n                client_config,\n            )?,\n            server: accept_with_buf_factory(\n                &server_scid,\n                None,\n                server_addr,\n                client_addr,\n                &mut config,\n            )?,\n        })\n    }\n\n    pub fn with_server_config_and_buf(\n        server_config: &mut Config,\n    ) -> Result<Pipe<F>> {\n        let mut client_scid = [0; 16];\n        rand::rand_bytes(&mut client_scid[..]);\n        let client_scid = ConnectionId::from_ref(&client_scid);\n        let client_addr = Pipe::client_addr();\n\n        let mut server_scid = [0; 16];\n        rand::rand_bytes(&mut server_scid[..]);\n        let server_scid = ConnectionId::from_ref(&server_scid);\n        let server_addr = Pipe::server_addr();\n\n        let mut config = Config::new(PROTOCOL_VERSION)?;\n        config.set_application_protos(&[b\"proto1\", b\"proto2\"])?;\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.set_ack_delay_exponent(8);\n\n        Ok(Pipe {\n            client: connect_with_buffer_factory(\n                Some(\"quic.tech\"),\n                &client_scid,\n                client_addr,\n                server_addr,\n                &mut config,\n            )?,\n            server: accept_with_buf_factory(\n                &server_scid,\n                None,\n                server_addr,\n                client_addr,\n                server_config,\n            )?,\n        })\n    }\n\n    pub fn with_client_and_server_config_and_buf(\n        client_config: &mut Config, server_config: &mut Config,\n    ) -> Result<Pipe<F>> {\n        let mut client_scid = [0; 16];\n        rand::rand_bytes(&mut client_scid[..]);\n        let client_scid = ConnectionId::from_ref(&client_scid);\n        let client_addr = Pipe::client_addr();\n\n        let mut server_scid = [0; 16];\n        rand::rand_bytes(&mut server_scid[..]);\n        let server_scid = ConnectionId::from_ref(&server_scid);\n        let server_addr = Pipe::server_addr();\n\n        Ok(Pipe {\n            client: connect_with_buffer_factory(\n                Some(\"quic.tech\"),\n                &client_scid,\n                client_addr,\n                server_addr,\n                client_config,\n            )?,\n            server: accept_with_buf_factory(\n                &server_scid,\n                None,\n                server_addr,\n                client_addr,\n                server_config,\n            )?,\n        })\n    }\n\n    pub fn handshake(&mut self) -> Result<()> {\n        while !self.client.is_established() || !self.server.is_established() {\n            let flight = emit_flight(&mut self.client)?;\n            process_flight(&mut self.server, flight)?;\n\n            let flight = emit_flight(&mut self.server)?;\n            process_flight(&mut self.client, flight)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn advance(&mut self) -> Result<()> {\n        let mut client_done = false;\n        let mut server_done = false;\n\n        while !client_done || !server_done {\n            match emit_flight(&mut self.client) {\n                Ok(flight) => process_flight(&mut self.server, flight)?,\n\n                Err(Error::Done) => client_done = true,\n\n                Err(e) => return Err(e),\n            };\n\n            match emit_flight(&mut self.server) {\n                Ok(flight) => process_flight(&mut self.client, flight)?,\n\n                Err(Error::Done) => server_done = true,\n\n                Err(e) => return Err(e),\n            };\n        }\n\n        Ok(())\n    }\n\n    pub fn client_recv(&mut self, buf: &mut [u8]) -> Result<usize> {\n        let server_path = &self.server.paths.get_active().unwrap();\n        let info = RecvInfo {\n            to: server_path.peer_addr(),\n            from: server_path.local_addr(),\n        };\n\n        self.client.recv(buf, info)\n    }\n\n    pub fn server_recv(&mut self, buf: &mut [u8]) -> Result<usize> {\n        let client_path = &self.client.paths.get_active().unwrap();\n        let info = RecvInfo {\n            to: client_path.peer_addr(),\n            from: client_path.local_addr(),\n        };\n\n        self.server.recv(buf, info)\n    }\n\n    pub fn send_pkt_to_server(\n        &mut self, pkt_type: Type, frames: &[frame::Frame], buf: &mut [u8],\n    ) -> Result<usize> {\n        let written = encode_pkt(&mut self.client, pkt_type, frames, buf)?;\n        recv_send(&mut self.server, buf, written)\n    }\n\n    pub fn client_update_key(&mut self) -> Result<()> {\n        let crypto_ctx = &mut self.client.crypto_ctx[packet::Epoch::Application];\n\n        let open_next = crypto_ctx\n            .crypto_open\n            .as_ref()\n            .unwrap()\n            .derive_next_packet_key()\n            .unwrap();\n\n        let seal_next = crypto_ctx\n            .crypto_seal\n            .as_ref()\n            .unwrap()\n            .derive_next_packet_key()?;\n\n        let open_prev = crypto_ctx.crypto_open.replace(open_next);\n        crypto_ctx.crypto_seal.replace(seal_next);\n\n        crypto_ctx.key_update = Some(packet::KeyUpdate {\n            crypto_open: open_prev.unwrap(),\n            pn_on_update: self.client.next_pkt_num,\n            update_acked: true,\n            timer: Instant::now(),\n        });\n\n        self.client.key_phase = !self.client.key_phase;\n\n        Ok(())\n    }\n}\n\npub fn recv_send<F: BufFactory>(\n    conn: &mut Connection<F>, buf: &mut [u8], len: usize,\n) -> Result<usize> {\n    let active_path = conn.paths.get_active()?;\n    let info = RecvInfo {\n        to: active_path.local_addr(),\n        from: active_path.peer_addr(),\n    };\n\n    conn.recv(&mut buf[..len], info)?;\n\n    let mut off = 0;\n\n    match conn.send(&mut buf[off..]) {\n        Ok((write, _)) => off += write,\n\n        Err(Error::Done) => (),\n\n        Err(e) => return Err(e),\n    }\n\n    Ok(off)\n}\n\npub fn process_flight<F: BufFactory>(\n    conn: &mut Connection<F>, flight: Vec<(Vec<u8>, SendInfo)>,\n) -> Result<()> {\n    for (mut pkt, si) in flight {\n        let info = RecvInfo {\n            to: si.to,\n            from: si.from,\n        };\n\n        conn.recv(&mut pkt, info)?;\n    }\n\n    Ok(())\n}\n\npub fn emit_flight_with_max_buffer<F: BufFactory>(\n    conn: &mut Connection<F>, out_size: usize, from: Option<SocketAddr>,\n    to: Option<SocketAddr>,\n) -> Result<Vec<(Vec<u8>, SendInfo)>> {\n    let mut flight = Vec::new();\n\n    loop {\n        let mut out = vec![0u8; out_size];\n\n        let info = match conn.send_on_path(&mut out, from, to) {\n            Ok((written, info)) => {\n                out.truncate(written);\n                info\n            },\n\n            Err(Error::Done) => break,\n\n            Err(e) => return Err(e),\n        };\n\n        flight.push((out, info));\n    }\n\n    if flight.is_empty() {\n        return Err(Error::Done);\n    }\n\n    Ok(flight)\n}\n\npub fn emit_flight_on_path<F: BufFactory>(\n    conn: &mut Connection<F>, from: Option<SocketAddr>, to: Option<SocketAddr>,\n) -> Result<Vec<(Vec<u8>, SendInfo)>> {\n    emit_flight_with_max_buffer(conn, 65535, from, to)\n}\n\npub fn emit_flight<F: BufFactory>(\n    conn: &mut Connection<F>,\n) -> Result<Vec<(Vec<u8>, SendInfo)>> {\n    emit_flight_on_path(conn, None, None)\n}\n\npub fn encode_pkt<F: BufFactory>(\n    conn: &mut Connection<F>, pkt_type: Type, frames: &[frame::Frame],\n    buf: &mut [u8],\n) -> Result<usize> {\n    let mut b = octets::OctetsMut::with_slice(buf);\n\n    let epoch = pkt_type.to_epoch()?;\n\n    let crypto_ctx = &mut conn.crypto_ctx[epoch];\n\n    let pn = conn.next_pkt_num;\n    let pn_len = 4;\n\n    let send_path = conn.paths.get_active()?;\n    let active_dcid_seq = send_path\n        .active_dcid_seq\n        .as_ref()\n        .ok_or(Error::InvalidState)?;\n    let active_scid_seq = send_path\n        .active_scid_seq\n        .as_ref()\n        .ok_or(Error::InvalidState)?;\n\n    let hdr = Header {\n        ty: pkt_type,\n        version: conn.version,\n        dcid: ConnectionId::from_ref(\n            conn.ids.get_dcid(*active_dcid_seq)?.cid.as_ref(),\n        ),\n        scid: ConnectionId::from_ref(\n            conn.ids.get_scid(*active_scid_seq)?.cid.as_ref(),\n        ),\n        pkt_num: pn,\n        pkt_num_len: pn_len,\n        token: conn.token.clone(),\n        versions: None,\n        key_phase: conn.key_phase,\n    };\n\n    hdr.to_bytes(&mut b)?;\n\n    let payload_len = frames.iter().fold(0, |acc, x| acc + x.wire_len());\n\n    if pkt_type != Type::Short {\n        let len = pn_len + payload_len + crypto_ctx.crypto_overhead().unwrap();\n        b.put_varint(len as u64)?;\n    }\n\n    // Always encode packet number in 4 bytes, to allow encoding packets\n    // with empty payloads.\n    b.put_u32(pn as u32)?;\n\n    let payload_offset = b.off();\n\n    for frame in frames {\n        frame.to_bytes(&mut b)?;\n    }\n\n    let aead = match crypto_ctx.crypto_seal {\n        Some(ref mut v) => v,\n        None => return Err(Error::InvalidState),\n    };\n\n    let written = packet::encrypt_pkt(\n        &mut b,\n        pn,\n        pn_len,\n        payload_len,\n        payload_offset,\n        None,\n        aead,\n    )?;\n\n    conn.next_pkt_num += 1;\n\n    Ok(written)\n}\n\npub fn decode_pkt<F: BufFactory>(\n    conn: &mut Connection<F>, buf: &mut [u8],\n) -> Result<Vec<frame::Frame>> {\n    let mut b = octets::OctetsMut::with_slice(buf);\n\n    let mut hdr = Header::from_bytes(&mut b, conn.source_id().len()).unwrap();\n\n    let epoch = hdr.ty.to_epoch()?;\n\n    let aead = conn.crypto_ctx[epoch].crypto_open.as_ref().unwrap();\n\n    let payload_len = b.cap();\n\n    packet::decrypt_hdr(&mut b, &mut hdr, aead).unwrap();\n\n    let pn = packet::decode_pkt_num(\n        conn.pkt_num_spaces[epoch].largest_rx_pkt_num,\n        hdr.pkt_num,\n        hdr.pkt_num_len,\n    );\n\n    let mut payload =\n        packet::decrypt_pkt(&mut b, pn, hdr.pkt_num_len, payload_len, aead)\n            .unwrap();\n\n    let mut frames = Vec::new();\n\n    while payload.cap() > 0 {\n        let frame = frame::Frame::from_bytes(&mut payload, hdr.ty)?;\n        frames.push(frame);\n    }\n\n    Ok(frames)\n}\n\npub fn create_cid_and_reset_token(\n    cid_len: usize,\n) -> (ConnectionId<'static>, u128) {\n    let mut cid = vec![0; cid_len];\n    rand::rand_bytes(&mut cid[..]);\n    let cid = ConnectionId::from(cid);\n\n    let mut reset_token = [0; 16];\n    rand::rand_bytes(&mut reset_token);\n    let reset_token = u128::from_be_bytes(reset_token);\n\n    (cid, reset_token)\n}\n\npub fn helper_packet_sent(pkt_num: u64, now: Instant, size: usize) -> Sent {\n    Sent {\n        pkt_num,\n        frames: smallvec![],\n        time_sent: now,\n        time_acked: None,\n        time_lost: None,\n        size,\n        ack_eliciting: true,\n        in_flight: true,\n        delivered: 0,\n        delivered_time: now,\n        first_sent_time: now,\n        is_app_limited: false,\n        tx_in_flight: 0,\n        lost: 0,\n        has_data: true,\n        is_pmtud_probe: false,\n    }\n}\n\n// Helper function for testing either stream receive or discard.\npub fn stream_recv_discard<F: BufFactory>(\n    conn: &mut Connection<F>, discard: bool, stream_id: u64,\n) -> Result<(usize, bool)> {\n    let mut buf = [0; 65535];\n    if discard {\n        conn.stream_discard(stream_id, 65535)\n    } else {\n        conn.stream_recv(stream_id, &mut buf)\n    }\n}\n\n/// Triggers ACK-based loss detection for packets sent by `sender` before this\n/// call.\n///\n/// This works by sending multiple PING packets from the sender and having the\n/// receiver ACK them. Since loss detection uses a packet threshold, this\n/// function sends as many packets as needed to ensure any previously\n/// unacknowledged packets from the sender are detected as lost.\n#[cfg(test)]\npub fn trigger_ack_based_loss<F: BufFactory>(\n    sender: &mut Connection<F>, receiver: &mut Connection<F>,\n) {\n    let mut buf = [0; 65535];\n\n    // Use the active path's packet loss threshold.\n    let pkt_thresh = sender\n        .paths\n        .get_active()\n        .unwrap()\n        .recovery\n        .pkt_thresh()\n        .unwrap();\n\n    for _ in 0..pkt_thresh {\n        sender.send_ack_eliciting().unwrap();\n        let (len, _) = sender.send(&mut buf).unwrap();\n\n        let info = RecvInfo {\n            to: receiver.paths.get_active().unwrap().local_addr(),\n            from: receiver.paths.get_active().unwrap().peer_addr(),\n        };\n        receiver.recv(&mut buf[..len], info).unwrap();\n    }\n\n    // Receiver sends ACK for the new packets.\n    let (ack_len, _) = receiver.send(&mut buf).unwrap();\n\n    // Sender receives ACK, triggering loss detection.\n    let info = RecvInfo {\n        to: sender.paths.get_active().unwrap().local_addr(),\n        from: sender.paths.get_active().unwrap().peer_addr(),\n    };\n    sender.recv(&mut buf[..ack_len], info).unwrap();\n}\n"
  },
  {
    "path": "quiche/src/tests.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::*;\n\nuse crate::range_buf::RangeBuf;\nuse crate::test_utils::stream_recv_discard;\nuse crate::Header;\n\nuse rstest::rstest;\n\n#[test]\nfn transport_params() {\n    // Server encodes, client decodes.\n    let tp = TransportParams {\n        original_destination_connection_id: None,\n        max_idle_timeout: 30,\n        stateless_reset_token: Some(u128::from_be_bytes([0xba; 16])),\n        max_udp_payload_size: 23_421,\n        initial_max_data: 424_645_563,\n        initial_max_stream_data_bidi_local: 154_323_123,\n        initial_max_stream_data_bidi_remote: 6_587_456,\n        initial_max_stream_data_uni: 2_461_234,\n        initial_max_streams_bidi: 12_231,\n        initial_max_streams_uni: 18_473,\n        ack_delay_exponent: 20,\n        max_ack_delay: 2_u64.pow(14) - 1,\n        disable_active_migration: true,\n        active_conn_id_limit: 8,\n        initial_source_connection_id: Some(b\"woot woot\".to_vec().into()),\n        retry_source_connection_id: Some(b\"retry\".to_vec().into()),\n        max_datagram_frame_size: Some(32),\n        unknown_params: Default::default(),\n    };\n\n    let mut raw_params = [42; 256];\n    let raw_params = TransportParams::encode(&tp, true, &mut raw_params).unwrap();\n    assert_eq!(raw_params.len(), 94);\n\n    let new_tp = TransportParams::decode(raw_params, false, None).unwrap();\n\n    assert_eq!(new_tp, tp);\n\n    // Client encodes, server decodes.\n    let tp = TransportParams {\n        original_destination_connection_id: None,\n        max_idle_timeout: 30,\n        stateless_reset_token: None,\n        max_udp_payload_size: 23_421,\n        initial_max_data: 424_645_563,\n        initial_max_stream_data_bidi_local: 154_323_123,\n        initial_max_stream_data_bidi_remote: 6_587_456,\n        initial_max_stream_data_uni: 2_461_234,\n        initial_max_streams_bidi: 12_231,\n        initial_max_streams_uni: 18_473,\n        ack_delay_exponent: 20,\n        max_ack_delay: 2_u64.pow(14) - 1,\n        disable_active_migration: true,\n        active_conn_id_limit: 8,\n        initial_source_connection_id: Some(b\"woot woot\".to_vec().into()),\n        retry_source_connection_id: None,\n        max_datagram_frame_size: Some(32),\n        unknown_params: Default::default(),\n    };\n\n    let mut raw_params = [42; 256];\n    let raw_params =\n        TransportParams::encode(&tp, false, &mut raw_params).unwrap();\n    assert_eq!(raw_params.len(), 69);\n\n    let new_tp = TransportParams::decode(raw_params, true, None).unwrap();\n\n    assert_eq!(new_tp, tp);\n}\n\n#[test]\nfn transport_params_forbid_duplicates() {\n    // Given an encoded param.\n    let initial_source_connection_id = b\"id\";\n    let initial_source_connection_id_raw = [\n        15,\n        initial_source_connection_id.len() as u8,\n        initial_source_connection_id[0],\n        initial_source_connection_id[1],\n    ];\n\n    // No error when decoding the param.\n    let tp = TransportParams::decode(\n        initial_source_connection_id_raw.as_slice(),\n        true,\n        None,\n    )\n    .unwrap();\n\n    assert_eq!(\n        tp.initial_source_connection_id,\n        Some(initial_source_connection_id.to_vec().into())\n    );\n\n    // Duplicate the param.\n    let mut raw_params = Vec::new();\n    raw_params.append(&mut initial_source_connection_id_raw.to_vec());\n    raw_params.append(&mut initial_source_connection_id_raw.to_vec());\n\n    // Decoding fails.\n    assert_eq!(\n        TransportParams::decode(raw_params.as_slice(), true, None),\n        Err(Error::InvalidTransportParam)\n    );\n}\n\n#[test]\nfn transport_params_unknown_zero_space() {\n    let mut unknown_params: UnknownTransportParameters =\n        UnknownTransportParameters {\n            capacity: 0,\n            parameters: vec![],\n        };\n    let massive_unknown_param = UnknownTransportParameter::<&[u8]> {\n        id: 5,\n        value: &[0xau8; 280],\n    };\n    assert!(unknown_params.push(massive_unknown_param).is_err());\n    assert!(unknown_params.capacity == 0);\n    assert!(unknown_params.parameters.is_empty());\n}\n\n#[test]\nfn transport_params_unknown_max_space_respected() {\n    let mut unknown_params: UnknownTransportParameters =\n        UnknownTransportParameters {\n            capacity: 256,\n            parameters: vec![],\n        };\n\n    let massive_unknown_param = UnknownTransportParameter::<&[u8]> {\n        id: 5,\n        value: &[0xau8; 280],\n    };\n    let big_unknown_param = UnknownTransportParameter::<&[u8]> {\n        id: 5,\n        value: &[0xau8; 232],\n    };\n    let little_unknown_param = UnknownTransportParameter::<&[u8]> {\n        id: 6,\n        value: &[0xau8; 7],\n    };\n\n    assert!(unknown_params.push(massive_unknown_param).is_err());\n    assert!(unknown_params.capacity == 256);\n    assert!(unknown_params.parameters.is_empty());\n\n    unknown_params.push(big_unknown_param).unwrap();\n    assert!(unknown_params.capacity == 16);\n    assert!(unknown_params.parameters.len() == 1);\n\n    unknown_params.push(little_unknown_param.clone()).unwrap();\n    assert!(unknown_params.capacity == 1);\n    assert!(unknown_params.parameters.len() == 2);\n\n    assert!(unknown_params.push(little_unknown_param).is_err());\n\n    let mut unknown_params_iter = unknown_params.into_iter();\n\n    let unknown_params_first = unknown_params_iter\n        .next()\n        .expect(\"Should have a 0th element.\");\n    assert!(\n        unknown_params_first.id == 5 &&\n            unknown_params_first.value == vec![0xau8; 232]\n    );\n\n    let unknown_params_second = unknown_params_iter\n        .next()\n        .expect(\"Should have a 1th element.\");\n    assert!(\n        unknown_params_second.id == 6 &&\n            unknown_params_second.value == vec![0xau8; 7]\n    );\n}\n\n#[test]\nfn transport_params_unknown_is_reserved() {\n    let reserved_unknown_param = UnknownTransportParameter::<&[u8]> {\n        id: 31 * 17 + 27,\n        value: &[0xau8; 280],\n    };\n    let not_reserved_unknown_param = UnknownTransportParameter::<&[u8]> {\n        id: 32 * 17 + 27,\n        value: &[0xau8; 280],\n    };\n\n    assert!(reserved_unknown_param.is_reserved());\n    assert!(!not_reserved_unknown_param.is_reserved());\n}\n#[test]\nfn unknown_version() {\n    let mut config = Config::new(0xbabababa).unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Err(Error::UnknownVersion));\n}\n\n#[test]\nfn config_version_reserved() {\n    Config::new(0xbabababa).unwrap();\n    Config::new(0x1a2a3a4a).unwrap();\n}\n\n#[test]\nfn config_version_invalid() {\n    assert_eq!(\n        Config::new(0xb1bababa).err().unwrap(),\n        Error::UnknownVersion\n    );\n}\n\n#[test]\nfn version_negotiation() {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(0xbabababa).unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n\n    let (mut len, _) = pipe.client.send(&mut buf).unwrap();\n\n    let hdr = Header::from_slice(&mut buf[..len], 0).unwrap();\n    len = negotiate_version(&hdr.scid, &hdr.dcid, &mut buf).unwrap();\n\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.version, PROTOCOL_VERSION);\n    assert_eq!(pipe.server.version, PROTOCOL_VERSION);\n}\n\n#[test]\nfn verify_custom_root() {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config.verify_peer(true);\n    config\n        .load_verify_locations_from_file(\"examples/rootca.crt\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n}\n\n// Disable this for openssl as it seems to fail for some reason. It could be\n// because of the way the get_certs API differs from bssl.\n#[cfg(not(feature = \"openssl\"))]\n#[test]\nfn verify_client_invalid() {\n    let mut server_config = Config::new(PROTOCOL_VERSION).unwrap();\n    server_config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    server_config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    server_config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    server_config.set_initial_max_data(30);\n    server_config.set_initial_max_stream_data_bidi_local(15);\n    server_config.set_initial_max_stream_data_bidi_remote(15);\n    server_config.set_initial_max_streams_bidi(3);\n\n    // The server shouldn't be able to verify the client's certificate due\n    // to missing CA.\n    server_config.verify_peer(true);\n\n    let mut client_config = Config::new(PROTOCOL_VERSION).unwrap();\n    client_config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    client_config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    client_config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    client_config.set_initial_max_data(30);\n    client_config.set_initial_max_stream_data_bidi_local(15);\n    client_config.set_initial_max_stream_data_bidi_remote(15);\n    client_config.set_initial_max_streams_bidi(3);\n\n    // The client is able to verify the server's certificate with the\n    // appropriate CA.\n    client_config\n        .load_verify_locations_from_file(\"examples/rootca.crt\")\n        .unwrap();\n    client_config.verify_peer(true);\n\n    let mut pipe = test_utils::Pipe::with_client_and_server_config(\n        &mut client_config,\n        &mut server_config,\n    )\n    .unwrap();\n    assert_eq!(pipe.handshake(), Err(Error::TlsFail));\n\n    // Client did send a certificate.\n    assert!(pipe.server.peer_cert().is_some());\n}\n\n#[test]\nfn verify_client_anonymous() {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n\n    // Try to validate client certificate.\n    config.verify_peer(true);\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client didn't send a certificate.\n    assert!(pipe.server.peer_cert().is_none());\n}\n\n#[rstest]\nfn missing_initial_source_connection_id(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Reset initial_source_connection_id.\n    pipe.client\n        .local_transport_params\n        .initial_source_connection_id = None;\n    assert_eq!(pipe.client.encode_transport_params(), Ok(()));\n\n    // Client sends initial flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server rejects transport parameters.\n    assert_eq!(\n        pipe.server_recv(&mut buf[..len]),\n        Err(Error::InvalidTransportParam)\n    );\n}\n\n#[rstest]\nfn invalid_initial_source_connection_id(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Scramble initial_source_connection_id.\n    pipe.client\n        .local_transport_params\n        .initial_source_connection_id = Some(b\"bogus value\".to_vec().into());\n    assert_eq!(pipe.client.encode_transport_params(), Ok(()));\n\n    // Client sends initial flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server rejects transport parameters.\n    assert_eq!(\n        pipe.server_recv(&mut buf[..len]),\n        Err(Error::InvalidTransportParam)\n    );\n}\n\n#[rstest]\nfn change_idle_timeout(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(0x1).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_max_idle_timeout(999999);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.client.local_transport_params.max_idle_timeout, 999999);\n    assert_eq!(pipe.client.peer_transport_params.max_idle_timeout, 0);\n    assert_eq!(pipe.server.local_transport_params.max_idle_timeout, 0);\n    assert_eq!(pipe.server.peer_transport_params.max_idle_timeout, 0);\n\n    pipe.client.set_max_idle_timeout(456000).unwrap();\n    pipe.server.set_max_idle_timeout(234000).unwrap();\n    assert_eq!(pipe.client.local_transport_params.max_idle_timeout, 456000);\n    assert_eq!(pipe.client.peer_transport_params.max_idle_timeout, 0);\n    assert_eq!(pipe.server.local_transport_params.max_idle_timeout, 234000);\n    assert_eq!(pipe.server.peer_transport_params.max_idle_timeout, 0);\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(\n        pipe.client.idle_timeout(),\n        Some(Duration::from_millis(234000))\n    );\n    assert_eq!(\n        pipe.server.idle_timeout(),\n        Some(Duration::from_millis(234000))\n    );\n}\n\n#[rstest]\nfn handshake(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(\n        pipe.client.application_proto(),\n        pipe.server.application_proto()\n    );\n\n    assert_eq!(pipe.server.server_name(), Some(\"quic.tech\"));\n}\n\n#[rstest]\nfn handshake_done(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Disable session tickets on the server (SSL_OP_NO_TICKET) to avoid\n    // triggering 1-RTT packet send with a CRYPTO frame.\n    pipe.server.handshake.set_options(0x0000_4000);\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert!(pipe.server.handshake_done_sent);\n}\n\n#[rstest]\nfn handshake_confirmation(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    // Server sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    assert!(!pipe.client.is_established());\n    assert!(!pipe.client.handshake_confirmed);\n\n    assert!(!pipe.server.is_established());\n    assert!(!pipe.server.handshake_confirmed);\n\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Client sends Handshake packet and completes handshake.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    assert!(pipe.client.is_established());\n    assert!(!pipe.client.handshake_confirmed);\n\n    assert!(!pipe.server.is_established());\n    assert!(!pipe.server.handshake_confirmed);\n\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    // Server completes and confirms handshake, and sends HANDSHAKE_DONE.\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    assert!(pipe.client.is_established());\n    assert!(!pipe.client.handshake_confirmed);\n\n    assert!(pipe.server.is_established());\n    assert!(pipe.server.handshake_confirmed);\n\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Client acks 1-RTT packet, and confirms handshake.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.client.handshake_confirmed);\n\n    assert!(pipe.server.is_established());\n    assert!(pipe.server.handshake_confirmed);\n\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.client.handshake_confirmed);\n\n    assert!(pipe.server.is_established());\n    assert!(pipe.server.handshake_confirmed);\n}\n\n#[rstest]\nfn handshake_resumption(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    #[cfg(not(feature = \"openssl\"))]\n    const SESSION_TICKET_KEY: [u8; 48] = [0xa; 48];\n\n    // 80-byte key(AES 256)\n    // TODO: We can set the default? or query the ticket size by calling\n    // the same API(SSL_CTX_set_tlsext_ticket_keys) twice to fetch the size.\n    #[cfg(feature = \"openssl\")]\n    const SESSION_TICKET_KEY: [u8; 80] = [0xa; 80];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n    config.set_ticket_key(&SESSION_TICKET_KEY).unwrap();\n\n    // Perform initial handshake.\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.server.is_established());\n\n    assert!(!pipe.client.is_resumed());\n    assert!(!pipe.server.is_resumed());\n\n    // Extract session,\n    let session = pipe.client.session().unwrap();\n\n    // Configure session on new connection and perform handshake.\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n    config.set_ticket_key(&SESSION_TICKET_KEY).unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    assert_eq!(pipe.client.set_session(session), Ok(()));\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.server.is_established());\n\n    assert!(pipe.client.is_resumed());\n    assert!(pipe.server.is_resumed());\n}\n\n#[rstest]\nfn handshake_alpn_mismatch(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto3\\x06proto4\"])\n        .unwrap();\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Err(Error::TlsFail));\n\n    assert_eq!(pipe.client.application_proto(), b\"\");\n    assert_eq!(pipe.server.application_proto(), b\"\");\n\n    // Server should only send one packet in response to ALPN mismatch.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    assert_eq!(len, 1200);\n\n    assert_eq!(pipe.server.send(&mut buf), Err(Error::Done));\n    assert_eq!(pipe.server.sent_count, 1);\n}\n\n#[cfg(not(feature = \"openssl\"))] // 0-RTT not supported when using openssl/quictls\n#[rstest]\nfn handshake_0rtt(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n    config.enable_early_data();\n    config.verify_peer(false);\n\n    // Perform initial handshake.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Extract session,\n    let session = pipe.client.session().unwrap();\n\n    // Configure session on new connection.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.client.set_session(session), Ok(()));\n\n    // Client sends initial flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    // Client sends 0-RTT packet.\n    let pkt_type = Type::ZeroRTT;\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"aaaaa\", 0, true),\n    }];\n\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Ok(1200)\n    );\n\n    assert_eq!(pipe.server.undecryptable_pkts.len(), 0);\n\n    // 0-RTT stream data is readable.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    let mut b = [0; 15];\n    assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((5, true)));\n    assert_eq!(&b[..5], b\"aaaaa\");\n}\n\n#[cfg(not(feature = \"openssl\"))] // 0-RTT not supported when using openssl/quictls\n#[rstest]\nfn handshake_0rtt_reordered(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n    config.enable_early_data();\n    config.verify_peer(false);\n\n    // Perform initial handshake.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Extract session,\n    let session = pipe.client.session().unwrap();\n\n    // Configure session on new connection.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.client.set_session(session), Ok(()));\n\n    // Client sends initial flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    let mut initial = buf[..len].to_vec();\n\n    // Client sends 0-RTT packet.\n    let pkt_type = Type::ZeroRTT;\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"aaaaa\", 0, true),\n    }];\n\n    let len =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n    let mut zrtt = buf[..len].to_vec();\n\n    // 0-RTT packet is received before the Initial one.\n    assert_eq!(pipe.server_recv(&mut zrtt), Ok(zrtt.len()));\n\n    assert_eq!(pipe.server.undecryptable_pkts.len(), 1);\n    assert_eq!(pipe.server.undecryptable_pkts[0].0.len(), zrtt.len());\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Initial packet is also received.\n    assert_eq!(pipe.server_recv(&mut initial), Ok(initial.len()));\n\n    // 0-RTT stream data is readable.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    let mut b = [0; 15];\n    assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((5, true)));\n    assert_eq!(&b[..5], b\"aaaaa\");\n}\n\n#[cfg(not(feature = \"openssl\"))] // 0-RTT not supported when using openssl/quictls\n#[rstest]\nfn handshake_0rtt_truncated(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n    config.enable_early_data();\n    config.verify_peer(false);\n\n    // Perform initial handshake.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Extract session,\n    let session = pipe.client.session().unwrap();\n\n    // Configure session on new connection.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.client.set_session(session), Ok(()));\n\n    // Client sends initial flight.\n    pipe.client.send(&mut buf).unwrap();\n\n    // Client sends 0-RTT packet.\n    let pkt_type = Type::ZeroRTT;\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"aaaaa\", 0, true),\n    }];\n\n    let len =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    // Simulate a truncated packet by sending one byte less.\n    let mut zrtt = buf[..len - 1].to_vec();\n\n    // 0-RTT packet is received before the Initial one.\n    assert_eq!(pipe.server_recv(&mut zrtt), Err(Error::InvalidPacket));\n\n    assert_eq!(pipe.server.undecryptable_pkts.len(), 0);\n\n    assert!(pipe.server.is_closed());\n}\n\n#[rstest]\nfn crypto_limit(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n    config.enable_early_data();\n    config.verify_peer(false);\n\n    // Perform initial handshake.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client send a 1-byte frame that starts from the crypto stream offset\n    // limit.\n    let frames = [frame::Frame::Crypto {\n        data: RangeBuf::from(b\"a\", MAX_CRYPTO_STREAM_OFFSET, false),\n    }];\n\n    let pkt_type = Type::Short;\n\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    let active_path = pipe.server.paths.get_active().unwrap();\n    let info = RecvInfo {\n        to: active_path.local_addr(),\n        from: active_path.peer_addr(),\n    };\n\n    assert_eq!(\n        pipe.server.recv(&mut buf[..written], info),\n        Err(Error::CryptoBufferExceeded)\n    );\n\n    let written = match pipe.server.send(&mut buf) {\n        Ok((write, _)) => write,\n\n        Err(_) => unreachable!(),\n    };\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..written]).unwrap();\n    let mut iter = frames.iter();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ConnectionClose {\n            error_code: 0x0d,\n            frame_type: 0,\n            reason: Vec::new(),\n        })\n    );\n}\n\n#[rstest]\nfn limit_handshake_data(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert-big.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    let client_sent = flight.iter().fold(0, |out, p| out + p.0.len());\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n    let server_sent = flight.iter().fold(0, |out, p| out + p.0.len());\n\n    assert_eq!(server_sent, client_sent * MAX_AMPLIFICATION_FACTOR);\n}\n\n#[rstest]\nfn custom_limit_handshake_data(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    const CUSTOM_AMPLIFICATION_FACTOR: usize = 2;\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert-big.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_max_amplification_factor(CUSTOM_AMPLIFICATION_FACTOR);\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    let client_sent = flight.iter().fold(0, |out, p| out + p.0.len());\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n    let server_sent = flight.iter().fold(0, |out, p| out + p.0.len());\n\n    assert_eq!(server_sent, client_sent * CUSTOM_AMPLIFICATION_FACTOR);\n}\n\n#[rstest]\nfn streamio(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"hello, world\", true), Ok(12));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(!pipe.server.stream_finished(4));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    let mut b = [0; 15];\n    assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((12, true)));\n    assert_eq!(&b[..12], b\"hello, world\");\n\n    assert!(pipe.server.stream_finished(4));\n}\n\n#[rstest]\nfn streamio_mixed_actions(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"helloworldttfn\", true), Ok(14));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(!pipe.server.stream_finished(4));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    let mut b = [0; 15];\n    assert_eq!(pipe.server.stream_recv(4, &mut b[..5]), Ok((5, false)));\n    assert_eq!(&b[..5], b\"hello\");\n    assert_eq!(pipe.server.stream_discard(4, 5), Ok((5, false)));\n    assert_eq!(pipe.server.stream_recv(4, &mut b[..2]), Ok((2, false)));\n    assert_eq!(&b[..2], b\"tt\");\n    assert_eq!(pipe.server.stream_discard(4, 2), Ok((2, true)));\n\n    assert!(pipe.server.stream_finished(4));\n}\n\n#[cfg(not(feature = \"openssl\"))] // 0-RTT not supported when using openssl/quictls\n#[rstest]\nfn zero_rtt(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n    config.enable_early_data();\n    config.verify_peer(false);\n\n    // Perform initial handshake.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Extract session,\n    let session = pipe.client.session().unwrap();\n\n    // Configure session on new connection.\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.client.set_session(session), Ok(()));\n\n    // Client sends initial flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    let mut initial = buf[..len].to_vec();\n\n    assert!(pipe.client.is_in_early_data());\n\n    // Client sends 0-RTT data.\n    assert_eq!(pipe.client.stream_send(4, b\"hello, world\", true), Ok(12));\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    let mut zrtt = buf[..len].to_vec();\n\n    // Server receives packets.\n    assert_eq!(pipe.server_recv(&mut initial), Ok(initial.len()));\n    assert!(pipe.server.is_in_early_data());\n\n    assert_eq!(pipe.server_recv(&mut zrtt), Ok(zrtt.len()));\n\n    // 0-RTT stream data is readable.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    let mut b = [0; 15];\n    assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((12, true)));\n    assert_eq!(&b[..12], b\"hello, world\");\n}\n\n#[rstest]\nfn stream_send_on_32bit_arch(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(2_u64.pow(32) + 5);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // In 32bit arch, send_capacity() should be min(2^32+5, cwnd),\n    // not min(5, cwnd)\n    assert_eq!(pipe.client.stream_send(4, b\"hello, world\", true), Ok(12));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(!pipe.server.stream_finished(4));\n}\n\n#[rstest]\nfn empty_stream_frame(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"aaaaa\", 0, false),\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(39));\n\n    let mut readable = pipe.server.readable();\n    assert_eq!(readable.next(), Some(4));\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 4),\n        Ok((5, false))\n    );\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"\", 5, true),\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(39));\n\n    let mut readable = pipe.server.readable();\n    assert_eq!(readable.next(), Some(4));\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 4),\n        Ok((0, true))\n    );\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"\", 15, true),\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::FinalSize)\n    );\n}\n\n#[rstest]\nfn update_key_request(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut b = [0; 15];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client sends message with key update request.\n    assert_eq!(pipe.client_update_key(), Ok(()));\n    assert_eq!(pipe.client.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Ensure server updates key and it correctly decrypts the message.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n    assert_eq!(pipe.server.stream_recv(4, &mut b), Ok((5, false)));\n    assert_eq!(&b[..5], b\"hello\");\n\n    // Ensure ACK for key update.\n    assert!(\n        pipe.server.crypto_ctx[packet::Epoch::Application]\n            .key_update\n            .as_ref()\n            .unwrap()\n            .update_acked\n    );\n\n    // Server sends message with the new key.\n    assert_eq!(pipe.server.stream_send(4, b\"world\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Ensure update key is completed and client can decrypt packet.\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n    assert_eq!(pipe.client.stream_recv(4, &mut b), Ok((5, false)));\n    assert_eq!(&b[..5], b\"world\");\n\n    // Server keeps sending packets to ensure encryption still works.\n    for _ in 0..10 {\n        assert_eq!(pipe.server.stream_send(4, b\"world\", false), Ok(5));\n        assert_eq!(pipe.advance(), Ok(()));\n\n        let mut r = pipe.client.readable();\n        assert_eq!(r.next(), Some(4));\n        assert_eq!(r.next(), None);\n        if discard {\n            assert_eq!(pipe.client.stream_discard(4, 5), Ok((5, false)));\n        } else {\n            assert_eq!(pipe.client.stream_recv(4, &mut b), Ok((5, false)));\n            assert_eq!(&b[..5], b\"world\");\n        }\n    }\n}\n\n#[rstest]\nfn update_key_request_twice_error(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"hello\", 0, false),\n    }];\n\n    // Client sends stream frame with key update request.\n    assert_eq!(pipe.client_update_key(), Ok(()));\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, Type::Short, &frames, &mut buf)\n            .unwrap();\n\n    // Server correctly decode with new key.\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n\n    // Client sends stream frame with another key update request before server\n    // ACK.\n    assert_eq!(pipe.client_update_key(), Ok(()));\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, Type::Short, &frames, &mut buf)\n            .unwrap();\n\n    // Check server correctly closes the connection with a key update error\n    // for the peer.\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Err(Error::KeyUpdate));\n}\n\n#[rstest]\n/// Tests that receiving a MAX_STREAM_DATA frame for a receive-only\n/// unidirectional stream is forbidden.\nfn max_stream_data_receive_uni(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client opens unidirectional stream.\n    assert_eq!(pipe.client.stream_send(2, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client sends MAX_STREAM_DATA on local unidirectional stream.\n    let frames = [frame::Frame::MaxStreamData {\n        stream_id: 2,\n        max: 1024,\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::InvalidStreamState(2)),\n    );\n}\n\n#[rstest]\nfn empty_payload(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Send a packet with no frames.\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &[], &mut buf),\n        Err(Error::InvalidPacket)\n    );\n}\n\n#[rstest]\nfn min_payload(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Send a non-ack-eliciting packet.\n    let frames = [frame::Frame::Padding { len: 4 }];\n\n    let pkt_type = Type::Initial;\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n\n    let initial_path = pipe\n        .server\n        .paths\n        .get_active()\n        .expect(\"initial path not found\");\n\n    assert_eq!(initial_path.max_send_bytes, 195);\n\n    // Force server to send a single PING frame.\n    pipe.server\n        .paths\n        .get_active_mut()\n        .expect(\"no active path\")\n        .recovery\n        .inc_loss_probes(packet::Epoch::Initial);\n\n    let initial_path = pipe\n        .server\n        .paths\n        .get_active_mut()\n        .expect(\"initial path not found\");\n\n    // Artificially limit the amount of bytes the server can send.\n    initial_path.max_send_bytes = 60;\n\n    assert_eq!(pipe.server.send(&mut buf), Err(Error::Done));\n}\n\n#[rstest]\nfn flow_control_limit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"aaaaaaaaaaaaaaa\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"aaaaaaaaaaaaaaa\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 8,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::FlowControl),\n    );\n}\n\n#[rstest]\nfn flow_control_limit_dup(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        // One byte less than stream limit.\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"aaaaaaaaaaaaaa\", 0, false),\n        },\n        // Same stream, but one byte more.\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"aaaaaaaaaaaaaaa\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 8,\n            data: <RangeBuf>::from(b\"aaaaaaaaaaaaaaa\", 0, false),\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n}\n\n#[rstest]\nfn flow_control_update(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"aaaaaaaaaaaaaaa\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n    ];\n\n    let pkt_type = Type::Short;\n\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    stream_recv_discard(&mut pipe.server, discard, 4).unwrap();\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"a\", 1, false),\n    }];\n\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    assert!(len > 0);\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let mut iter = frames.iter();\n\n    // Ignore ACK.\n    iter.next().unwrap();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::MaxStreamData {\n            stream_id: 0,\n            max: 30\n        })\n    );\n    assert_eq!(iter.next(), Some(&frame::Frame::MaxData { max: 61 }));\n}\n\n#[rstest]\n/// Tests that flow control is properly updated even when a stream is shut\n/// down.\nfn flow_control_drain(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65536];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    // Set large initial max_data so we don't have to deal with MAX_DATA\n    // or STREAM_MAX_DATA frames\n    config.set_initial_max_data(15_000);\n    config.set_initial_max_stream_data_bidi_local(15_000);\n    config.set_initial_max_stream_data_bidi_remote(15_000);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.set_max_idle_timeout(180_000);\n    config.verify_peer(false);\n    config.set_ack_delay_exponent(8);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client opens a stream and sends some data.\n    assert_eq!(pipe.client.stream_send(4, b\"aaaaa\", false), Ok(5));\n    // And also sends on a different stream\n    assert_eq!(pipe.client.stream_send(8, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.client.stream_send(8, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.client.stream_send(8, b\"aaaaa\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server receives data, without reading it.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), Some(8));\n    assert_eq!(r.next(), None);\n\n    // check flow control accounting\n    assert_eq!(pipe.server.rx_data, 20);\n    assert_eq!(pipe.server.flow_control.consumed(), 0);\n\n    // Helper function that sends STREAM frames to the server on stream 4\n    let mut send_frame_helper =\n        |pipe: &mut test_utils::Pipe, data: RangeBuf| -> Result<()> {\n            let frames = [frame::Frame::Stream { stream_id: 4, data }];\n            let written = test_utils::encode_pkt(\n                &mut pipe.client,\n                Type::Short,\n                &frames,\n                &mut buf,\n            )?;\n            assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n            Ok(())\n        };\n\n    // Client sends more data on stream 4, but with a gap.\n    // server now has [0..5] and [10..15]\n    send_frame_helper(&mut pipe, RangeBuf::from(&[1; 5], 10, false)).unwrap();\n\n    // check flow control accounting\n    assert_eq!(pipe.server.rx_data, 30);\n    assert_eq!(pipe.server.flow_control.consumed(), 0);\n\n    // Server shuts down one stream. We do not advance the pipe\n    assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Read, 42), Ok(()));\n\n    // check flow control accounting\n    // 15 bytes have been consumed, since that was the highest offset we have\n    // received.\n    assert_eq!(pipe.server.flow_control.consumed(), 15);\n    assert_eq!(pipe.server.rx_data, 30);\n\n    // client sends frame that partially closes the gap\n    // now we have [0..8] and [10..15]\n    // verify flow control. Should be no change.\n    send_frame_helper(&mut pipe, RangeBuf::from(&[1; 3], 5, false)).unwrap();\n    assert_eq!(pipe.server.rx_data, 30);\n    assert_eq!(pipe.server.flow_control.consumed(), 15);\n\n    // client sends partially overlapping data and partially new data\n    // now we have [0..8] and [10..20]\n    // verify flow control. we should account for an additional 5 bytes\n    send_frame_helper(&mut pipe, RangeBuf::from(&[1; 10], 10, false)).unwrap();\n    assert_eq!(pipe.server.rx_data, 35);\n    assert_eq!(pipe.server.flow_control.consumed(), 20);\n\n    // client sends a fin, but again with a gap\n    // this should add another 5 bytes to flow control\n    send_frame_helper(&mut pipe, RangeBuf::from(&[0; 0], 25, true)).unwrap();\n    assert_eq!(pipe.server.rx_data, 40);\n    assert_eq!(pipe.server.flow_control.consumed(), 25);\n}\n\n#[rstest]\n/// Tests that flow control is properly updated when a stream receives a RESET\nfn flow_control_reset_stream(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(\"reset\", \"fin\")] inconsistent_final_size_frame: &str,\n) {\n    let mut buf = [0; 65536];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    // Set large initial max_data so we don't have to deal with MAX_DATA\n    // or STREAM_MAX_DATA frames\n    config.set_initial_max_data(15_000);\n    config.set_initial_max_stream_data_bidi_local(15_000);\n    config.set_initial_max_stream_data_bidi_remote(15_000);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.set_max_idle_timeout(180_000);\n    config.verify_peer(false);\n    config.set_ack_delay_exponent(8);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client opens a stream and sends some data.\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server receives data, without reading it.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    // check flow control accounting\n    assert_eq!(pipe.server.rx_data, 5);\n    assert_eq!(pipe.server.flow_control.consumed(), 0);\n\n    // Helper function that sends STREAM frames to the server on stream 0\n    let send_frame_helper =\n        |pipe: &mut test_utils::Pipe, data: RangeBuf| -> Result<()> {\n            let mut buf = [0; 65536];\n            let frames = [frame::Frame::Stream { stream_id: 0, data }];\n            let written = test_utils::encode_pkt(\n                &mut pipe.client,\n                Type::Short,\n                &frames,\n                &mut buf,\n            )?;\n            assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n            Ok(())\n        };\n\n    // Client sends more data on stream 4, but with a gap.\n    // server now has [0..5] and [10..15]\n    send_frame_helper(&mut pipe, RangeBuf::from(&[1; 5], 10, false)).unwrap();\n\n    // check flow control accounting\n    assert_eq!(pipe.server.rx_data, 15);\n    assert_eq!(pipe.server.flow_control.consumed(), 0);\n\n    // Client sends a RESET_STREAM with final size 20\n    let frames = [frame::Frame::ResetStream {\n        stream_id: 0,\n        final_size: 20,\n        error_code: 42,\n    }];\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, Type::Short, &frames, &mut buf)\n            .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n\n    // check flow control accounting\n    // 20 bytes have been consumed, since that was the final_size\n    assert_eq!(pipe.server.flow_control.consumed(), 20);\n    assert_eq!(pipe.server.rx_data, 20);\n\n    // client sends more frames, some overlap, some new data\n    send_frame_helper(&mut pipe, RangeBuf::from(&[1; 3], 5, false)).unwrap();\n    send_frame_helper(&mut pipe, RangeBuf::from(&[1; 7], 10, false)).unwrap();\n    send_frame_helper(&mut pipe, RangeBuf::from(&[1; 3], 5, false)).unwrap();\n\n    // no change in flow control\n    assert_eq!(pipe.server.flow_control.consumed(), 20);\n    assert_eq!(pipe.server.rx_data, 20);\n\n    // Send the RESET again. Nothing happens\n    let frames = [frame::Frame::ResetStream {\n        stream_id: 0,\n        final_size: 20,\n        error_code: 42,\n    }];\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, Type::Short, &frames, &mut buf)\n            .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n\n    // no change in flow control\n    assert_eq!(pipe.server.flow_control.consumed(), 20);\n    assert_eq!(pipe.server.rx_data, 20);\n\n    if inconsistent_final_size_frame == \"reset\" {\n        // send another reset with inconsistent final size\n        // Send the RESET again. Nothing happens\n        let frames = [frame::Frame::ResetStream {\n            stream_id: 0,\n            final_size: 42,\n            error_code: 42,\n        }];\n        let written = test_utils::encode_pkt(\n            &mut pipe.client,\n            Type::Short,\n            &frames,\n            &mut buf,\n        )\n        .unwrap();\n        assert_eq!(pipe.server_recv(&mut buf[..written]), Err(Error::FinalSize));\n    } else if inconsistent_final_size_frame == \"fin\" {\n        let frames = [frame::Frame::Stream {\n            stream_id: 0,\n            data: RangeBuf::from(&[], 42, true),\n        }];\n        let written = test_utils::encode_pkt(\n            &mut pipe.client,\n            Type::Short,\n            &frames,\n            &mut buf,\n        )\n        .unwrap();\n        assert_eq!(pipe.server_recv(&mut buf[..written]), Err(Error::FinalSize));\n    } else {\n        panic!(\n            \"didn't expect inconsistent_final_size_frame to be `{}`\",\n            inconsistent_final_size_frame\n        );\n    }\n}\n\n#[rstest]\nfn stream_flow_control_limit_bidi(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"aaaaaaaaaaaaaaaa\", 0, true),\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::FlowControl),\n    );\n}\n\n#[rstest]\nfn stream_flow_control_limit_uni(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 2,\n        data: <RangeBuf>::from(b\"aaaaaaaaaaa\", 0, true),\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::FlowControl),\n    );\n}\n\n#[rstest]\nfn stream_flow_control_update(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"aaaaaaaaa\", 0, false),\n    }];\n\n    let pkt_type = Type::Short;\n\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    stream_recv_discard(&mut pipe.server, discard, 4).unwrap();\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"a\", 9, false),\n    }];\n\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    assert!(len > 0);\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let mut iter = frames.iter();\n\n    // Ignore ACK.\n    iter.next().unwrap();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::MaxStreamData {\n            stream_id: 4,\n            max: 24,\n        })\n    );\n}\n\n#[rstest]\nfn stream_left_bidi(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(3, pipe.client.peer_streams_left_bidi());\n    assert_eq!(3, pipe.server.peer_streams_left_bidi());\n\n    pipe.server.stream_send(1, b\"a\", false).ok();\n    assert_eq!(2, pipe.server.peer_streams_left_bidi());\n    pipe.server.stream_send(5, b\"a\", false).ok();\n    assert_eq!(1, pipe.server.peer_streams_left_bidi());\n\n    pipe.server.stream_send(9, b\"a\", false).ok();\n    assert_eq!(0, pipe.server.peer_streams_left_bidi());\n\n    let frames = [frame::Frame::MaxStreamsBidi { max: MAX_STREAM_ID }];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    assert_eq!(MAX_STREAM_ID - 3, pipe.server.peer_streams_left_bidi());\n}\n\n#[rstest]\nfn stream_left_uni(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(3, pipe.client.peer_streams_left_uni());\n    assert_eq!(3, pipe.server.peer_streams_left_uni());\n\n    pipe.server.stream_send(3, b\"a\", false).ok();\n    assert_eq!(2, pipe.server.peer_streams_left_uni());\n    pipe.server.stream_send(7, b\"a\", false).ok();\n    assert_eq!(1, pipe.server.peer_streams_left_uni());\n\n    pipe.server.stream_send(11, b\"a\", false).ok();\n    assert_eq!(0, pipe.server.peer_streams_left_uni());\n\n    let frames = [frame::Frame::MaxStreamsUni { max: MAX_STREAM_ID }];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    assert_eq!(MAX_STREAM_ID - 3, pipe.server.peer_streams_left_uni());\n}\n\n#[rstest]\nfn stream_limit_bidi(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 8,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 12,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 16,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 20,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 24,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 28,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::StreamLimit),\n    );\n}\n\n#[rstest]\nfn stream_limit_max_bidi(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::MaxStreamsBidi { max: MAX_STREAM_ID }];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    let frames = [frame::Frame::MaxStreamsBidi {\n        max: MAX_STREAM_ID + 1,\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::InvalidFrame),\n    );\n}\n\n#[rstest]\nfn stream_limit_uni(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        frame::Frame::Stream {\n            stream_id: 2,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 6,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 10,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 14,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 18,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 22,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 26,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::StreamLimit),\n    );\n}\n\n#[rstest]\nfn stream_limit_max_uni(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::MaxStreamsUni { max: MAX_STREAM_ID }];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    let frames = [frame::Frame::MaxStreamsUni {\n        max: MAX_STREAM_ID + 1,\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::InvalidFrame),\n    );\n}\n\n#[rstest]\nfn stream_left_reset_bidi(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(3, pipe.client.peer_streams_left_bidi());\n    assert_eq!(3, pipe.server.peer_streams_left_bidi());\n\n    pipe.client.stream_send(0, b\"a\", false).ok();\n    assert_eq!(2, pipe.client.peer_streams_left_bidi());\n    pipe.client.stream_send(4, b\"a\", false).ok();\n    assert_eq!(1, pipe.client.peer_streams_left_bidi());\n    pipe.client.stream_send(8, b\"a\", false).ok();\n    assert_eq!(0, pipe.client.peer_streams_left_bidi());\n\n    // Client resets the stream.\n    pipe.client\n        .stream_shutdown(0, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    assert_eq!(0, pipe.client.peer_streams_left_bidi());\n    let mut r = pipe.server.readable();\n    assert_eq!(Some(0), r.next());\n    assert_eq!(Some(4), r.next());\n    assert_eq!(Some(8), r.next());\n    assert_eq!(None, r.next());\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Err(Error::StreamReset(1001))\n    );\n\n    let mut r = pipe.server.readable();\n    assert_eq!(Some(4), r.next());\n    assert_eq!(Some(8), r.next());\n    assert_eq!(None, r.next());\n\n    // Server resets the stream in reaction.\n    pipe.server\n        .stream_shutdown(0, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    assert_eq!(1, pipe.client.peer_streams_left_bidi());\n\n    // Repeat for the other 2 streams\n    pipe.client\n        .stream_shutdown(4, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.client\n        .stream_shutdown(8, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    let mut r = pipe.server.readable();\n    assert_eq!(Some(4), r.next());\n    assert_eq!(Some(8), r.next());\n    assert_eq!(None, r.next());\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 4),\n        Err(Error::StreamReset(1001))\n    );\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 8),\n        Err(Error::StreamReset(1001))\n    );\n\n    let mut r = pipe.server.readable();\n    assert_eq!(None, r.next());\n\n    pipe.server\n        .stream_shutdown(4, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.server\n        .stream_shutdown(8, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    assert_eq!(3, pipe.client.peer_streams_left_bidi());\n}\n\n#[rstest]\nfn stream_reset_counts(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    pipe.client.stream_send(0, b\"a\", false).ok();\n    pipe.client.stream_send(2, b\"a\", false).ok();\n    pipe.client.stream_send(4, b\"a\", false).ok();\n    pipe.client.stream_send(8, b\"a\", false).ok();\n    pipe.advance().unwrap();\n\n    let stats = pipe.client.stats();\n    assert_eq!(stats.reset_stream_count_local, 0);\n\n    // Client resets the stream.\n    pipe.client\n        .stream_shutdown(0, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    let stats = pipe.client.stats();\n    assert_eq!(stats.reset_stream_count_local, 1);\n    assert_eq!(stats.reset_stream_count_remote, 0);\n    let stats = pipe.server.stats();\n    assert_eq!(stats.reset_stream_count_local, 0);\n    assert_eq!(stats.reset_stream_count_remote, 1);\n\n    // Server resets the stream in reaction.\n    pipe.server\n        .stream_shutdown(0, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    let stats = pipe.client.stats();\n    assert_eq!(stats.reset_stream_count_local, 1);\n    assert_eq!(stats.reset_stream_count_remote, 1);\n    let stats = pipe.server.stats();\n    assert_eq!(stats.reset_stream_count_local, 1);\n    assert_eq!(stats.reset_stream_count_remote, 1);\n\n    // Repeat for the other streams\n    pipe.client\n        .stream_shutdown(2, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.client\n        .stream_shutdown(4, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.client\n        .stream_shutdown(8, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    pipe.server\n        .stream_shutdown(4, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.server\n        .stream_shutdown(8, Shutdown::Write, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    let stats = pipe.client.stats();\n    assert_eq!(stats.reset_stream_count_local, 4);\n    assert_eq!(stats.reset_stream_count_remote, 3);\n    let stats = pipe.server.stats();\n    assert_eq!(stats.reset_stream_count_local, 3);\n    assert_eq!(stats.reset_stream_count_remote, 4);\n}\n\n#[rstest]\nfn stream_stop_counts(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    pipe.client.stream_send(0, b\"a\", false).ok();\n    pipe.client.stream_send(2, b\"a\", false).ok();\n    pipe.client.stream_send(4, b\"a\", false).ok();\n    pipe.client.stream_send(8, b\"a\", false).ok();\n    pipe.advance().unwrap();\n\n    let stats = pipe.client.stats();\n    assert_eq!(stats.reset_stream_count_local, 0);\n\n    // Server stops the stream and client automatically resets.\n    pipe.server\n        .stream_shutdown(0, Shutdown::Read, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    let stats = pipe.client.stats();\n    assert_eq!(stats.stopped_stream_count_local, 0);\n    assert_eq!(stats.stopped_stream_count_remote, 1);\n    assert_eq!(stats.reset_stream_count_local, 1);\n    assert_eq!(stats.reset_stream_count_remote, 0);\n\n    let stats = pipe.server.stats();\n    assert_eq!(stats.stopped_stream_count_local, 1);\n    assert_eq!(stats.stopped_stream_count_remote, 0);\n    assert_eq!(stats.reset_stream_count_local, 0);\n    assert_eq!(stats.reset_stream_count_remote, 1);\n\n    // Repeat for the other streams\n    pipe.server\n        .stream_shutdown(2, Shutdown::Read, 1001)\n        .unwrap();\n    pipe.server\n        .stream_shutdown(4, Shutdown::Read, 1001)\n        .unwrap();\n    pipe.server\n        .stream_shutdown(8, Shutdown::Read, 1001)\n        .unwrap();\n    pipe.advance().unwrap();\n\n    let stats = pipe.client.stats();\n    assert_eq!(stats.stopped_stream_count_local, 0);\n    assert_eq!(stats.stopped_stream_count_remote, 4);\n    assert_eq!(stats.reset_stream_count_local, 4);\n    assert_eq!(stats.reset_stream_count_remote, 0);\n\n    let stats = pipe.server.stats();\n    assert_eq!(stats.stopped_stream_count_local, 4);\n    assert_eq!(stats.stopped_stream_count_remote, 0);\n    assert_eq!(stats.reset_stream_count_local, 0);\n    assert_eq!(stats.reset_stream_count_remote, 4);\n}\n\n#[rstest]\nfn streams_blocked_max_bidi(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::StreamsBlockedBidi {\n        limit: MAX_STREAM_ID,\n    }];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    let frames = [frame::Frame::StreamsBlockedBidi {\n        limit: MAX_STREAM_ID + 1,\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::InvalidFrame),\n    );\n}\n\n#[rstest]\nfn streams_blocked_max_uni(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::StreamsBlockedUni {\n        limit: MAX_STREAM_ID,\n    }];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    let frames = [frame::Frame::StreamsBlockedUni {\n        limit: MAX_STREAM_ID + 1,\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::InvalidFrame),\n    );\n}\n\n#[rstest]\nfn streams_blocked_bidi_stat(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 0);\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 0);\n    assert_eq!(pipe.client.streams_blocked_bidi_recv_count, 0);\n    assert_eq!(pipe.client.streams_blocked_uni_recv_count, 0);\n\n    let frames = [frame::Frame::StreamsBlockedBidi {\n        limit: MAX_STREAM_ID,\n    }];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 1);\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 0);\n    assert_eq!(pipe.client.streams_blocked_bidi_recv_count, 0);\n    assert_eq!(pipe.client.streams_blocked_uni_recv_count, 0);\n\n    // Sending again increments further.\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 2);\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 0);\n}\n\n#[rstest]\nfn streams_blocked_uni_stat(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 0);\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 0);\n    assert_eq!(pipe.client.streams_blocked_bidi_recv_count, 0);\n    assert_eq!(pipe.client.streams_blocked_uni_recv_count, 0);\n\n    let frames = [frame::Frame::StreamsBlockedUni {\n        limit: MAX_STREAM_ID,\n    }];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 0);\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 1);\n    assert_eq!(pipe.client.streams_blocked_bidi_recv_count, 0);\n    assert_eq!(pipe.client.streams_blocked_uni_recv_count, 0);\n\n    // Sending again increments further.\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 0);\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 2);\n}\n\n/// Tests that a STREAMS_BLOCKED (bidi) frame is sent when the client tries to\n/// open a new bidirectional stream beyond the server's max_streams_bidi limit,\n/// that duplicate frames for the same limit are suppressed, and that a fresh\n/// frame is sent once the peer raises the limit and the endpoint becomes\n/// blocked again at the new, higher limit.\n#[rstest]\nfn streams_blocked_bidi_sent(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(false, true)] enable_send_streams_blocked: bool,\n) {\n    let mut config = test_utils::Pipe::default_config(cc_algorithm_name).unwrap();\n    config.set_enable_send_streams_blocked(enable_send_streams_blocked);\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // The default config sets initial_max_streams_bidi = 3 for the server,\n    // so the client may open streams 0, 4, 8 (sequence 0, 1, 2).\n    // Trying to open stream 12 (sequence 3) must be rejected.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Ok(1));\n    assert_eq!(pipe.client.stream_send(8, b\"a\", false), Ok(1));\n\n    // Attempting to open the 4th bidi stream should return StreamLimit and\n    // trigger a STREAMS_BLOCKED frame.\n    assert_eq!(\n        pipe.client.stream_send(12, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n\n    // Before advancing, the blocked state should be set but no frame sent yet.\n    if enable_send_streams_blocked {\n        assert!(pipe\n            .client\n            .streams_blocked_bidi_state\n            .has_pending_stream_blocked_frame());\n    } else {\n        assert!(!pipe\n            .client\n            .streams_blocked_bidi_state\n            .has_pending_stream_blocked_frame());\n    }\n\n    // Advance so the client flushes its pending frames to the server.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    if enable_send_streams_blocked {\n        // The client should have sent one STREAMS_BLOCKED (bidi) frame.\n        assert!(!pipe\n            .client\n            .streams_blocked_bidi_state\n            .has_pending_stream_blocked_frame());\n\n        // The server must have received our STREAMS_BLOCKED (bidi) frame.\n        assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 1);\n    } else {\n        // Frames not sent when feature disabled.\n        assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 0);\n    }\n\n    // A second attempt at the same stream must NOT produce another frame —\n    // the peer has already been notified about this limit.\n    assert_eq!(\n        pipe.client.stream_send(12, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n    if enable_send_streams_blocked {\n        assert_eq!(pipe.advance(), Ok(()));\n        assert!(!pipe\n            .client\n            .streams_blocked_bidi_state\n            .has_pending_stream_blocked_frame());\n    }\n\n    // Simulate the peer raising the bidi stream limit to 4. The client can\n    // now open stream 12 (sequence 3) but will be blocked at stream 16\n    // (sequence 4 > new limit of 4). That is a new limit, so a fresh\n    // STREAMS_BLOCKED frame must be armed and subsequently sent.\n    pipe.client.streams.update_peer_max_streams_bidi(4);\n    assert_eq!(pipe.client.stream_send(12, b\"a\", false), Ok(1));\n    assert_eq!(\n        pipe.client.stream_send(16, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n\n    if enable_send_streams_blocked {\n        // The blocked flag must be set for the new limit.\n        assert!(pipe\n            .client\n            .streams_blocked_bidi_state\n            .has_pending_stream_blocked_frame());\n    }\n\n    // Emit the client's outgoing flight and process it at the server.\n    let flight = test_utils::emit_flight(&mut pipe.client);\n    assert!(flight.is_ok());\n    assert_eq!(\n        test_utils::process_flight(&mut pipe.server, flight.unwrap()),\n        Err(Error::StreamLimit)\n    );\n    if enable_send_streams_blocked {\n        assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 2);\n        assert!(!pipe\n            .client\n            .streams_blocked_bidi_state\n            .has_pending_stream_blocked_frame());\n    } else {\n        assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 0);\n    }\n}\n\n/// Tests that a STREAMS_BLOCKED (uni) frame is sent when the client tries to\n/// open a new unidirectional stream beyond the server's max_streams_uni limit,\n/// that duplicate frames for the same limit are suppressed, and that a fresh\n/// frame is sent once the peer raises the limit and the endpoint becomes\n/// blocked again at the new, higher limit.\n#[rstest]\nfn streams_blocked_uni_sent(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(false, true)] enable_send_streams_blocked: bool,\n) {\n    let mut config = test_utils::Pipe::default_config(cc_algorithm_name).unwrap();\n    config.set_enable_send_streams_blocked(enable_send_streams_blocked);\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // The default config sets initial_max_streams_uni = 3 for the server,\n    // so the client may open streams 2, 6, 10 (sequence 0, 1, 2).\n    // Trying to open stream 14 (sequence 3) must be rejected.\n    assert_eq!(pipe.client.stream_send(2, b\"a\", false), Ok(1));\n    assert_eq!(pipe.client.stream_send(6, b\"a\", false), Ok(1));\n    assert_eq!(pipe.client.stream_send(10, b\"a\", false), Ok(1));\n\n    // Attempting to open the 4th uni stream should return StreamLimit and\n    // trigger a STREAMS_BLOCKED frame.\n    assert_eq!(\n        pipe.client.stream_send(14, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n\n    // Before advancing, the blocked state should be set but no frame sent yet.\n    if enable_send_streams_blocked {\n        assert!(pipe\n            .client\n            .streams_blocked_uni_state\n            .has_pending_stream_blocked_frame());\n    } else {\n        assert!(!pipe\n            .client\n            .streams_blocked_uni_state\n            .has_pending_stream_blocked_frame());\n    }\n\n    // Advance so the client flushes its pending frames to the server.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // The client should have sent one STREAMS_BLOCKED (uni) frame.\n    if enable_send_streams_blocked {\n        assert!(!pipe\n            .client\n            .streams_blocked_uni_state\n            .has_pending_stream_blocked_frame());\n\n        // The server must have received our STREAMS_BLOCKED (uni) frame.\n        assert_eq!(pipe.server.streams_blocked_uni_recv_count, 1);\n    } else {\n        // Frame not sent when feature disabled.\n        assert_eq!(pipe.server.streams_blocked_uni_recv_count, 0);\n    }\n\n    // A second attempt at the same stream must NOT produce another frame —\n    // the peer has already been notified about this limit.\n    assert_eq!(\n        pipe.client.stream_send(14, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(\n        pipe.server.streams_blocked_uni_recv_count,\n        if enable_send_streams_blocked { 1 } else { 0 }\n    );\n\n    // Simulate the peer raising the uni stream limit to 4. The client can\n    // now open stream 14 (sequence 3) but will be blocked at stream 18\n    // (sequence 4 > new limit of 4). That is a new limit, so a fresh\n    // STREAMS_BLOCKED frame must be armed and subsequently sent.\n    pipe.client.streams.update_peer_max_streams_uni(4);\n    assert_eq!(pipe.client.stream_send(14, b\"a\", false), Ok(1));\n    assert_eq!(\n        pipe.client.stream_send(18, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n    if enable_send_streams_blocked {\n        // The blocked flag must be set for the new limit.\n        assert!(pipe\n            .client\n            .streams_blocked_uni_state\n            .has_pending_stream_blocked_frame());\n    }\n    // Emit the client's outgoing flight and process it at the server.\n    let flight = test_utils::emit_flight(&mut pipe.client);\n    assert!(flight.is_ok());\n    assert_eq!(\n        test_utils::process_flight(&mut pipe.server, flight.unwrap()),\n        Err(Error::StreamLimit)\n    );\n    if enable_send_streams_blocked {\n        assert_eq!(pipe.server.streams_blocked_uni_recv_count, 2);\n        assert!(!pipe\n            .client\n            .streams_blocked_uni_state\n            .has_pending_stream_blocked_frame());\n    } else {\n        assert_eq!(pipe.server.streams_blocked_uni_recv_count, 0);\n    }\n}\n\n/// Tests that a lost STREAMS_BLOCKED (bidi) frame is retransmitted.\n///\n/// When the packet carrying the frame is declared lost via the PTO mechanism,\n/// `streams_blocked_bidi_state` must be notified so that the same limit can\n/// be sent again.  The server must ultimately receive the frame.\n#[rstest]\nfn streams_blocked_bidi_retransmit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = test_utils::Pipe::default_config(cc_algorithm_name).unwrap();\n    config.set_enable_send_streams_blocked(true);\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Exhaust the server's initial bidi stream limit (3) so the next attempt\n    // returns StreamLimit and arms a STREAMS_BLOCKED frame.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Ok(1));\n    assert_eq!(pipe.client.stream_send(8, b\"a\", false), Ok(1));\n    assert_eq!(\n        pipe.client.stream_send(12, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n\n    // Frame is armed but not yet sent.\n    assert!(pipe\n        .client\n        .streams_blocked_bidi_state\n        .has_pending_stream_blocked_frame());\n\n    // Emit the client's flight (stream data + STREAMS_BLOCKED) without\n    // delivering it to the server — the packet is \"lost\" from the server's\n    // perspective.\n    assert!(test_utils::emit_flight(&mut pipe.client).is_ok());\n\n    // After emission streams_blocked_bidi_state is updated and the sent counter\n    // advances.\n    assert!(!pipe\n        .client\n        .streams_blocked_bidi_state\n        .has_pending_stream_blocked_frame());\n\n    // The server has not received anything yet.\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 0);\n\n    // Trigger loss detection\n    test_utils::trigger_ack_based_loss(&mut pipe.client, &mut pipe.server);\n\n    // Trigger the lost-frames processing by calling send().  The loss handler\n    // is invoked at the start of each send_on_path() call to process any frames\n    // added to lost_frames by ack-based loss detection.\n    let (len, send_info) = pipe.client.send(&mut buf).unwrap();\n\n    // After the retransmit send the sequence is:\n    //   1. loss handler: clears `streams_blocked_bidi_state.blocked_sent` to\n    //      trigger retransmission\n    //   2. emit path:    sees\n    //      `streams_blocked_bidi_state.has_pending_stream_blocked_frame()`, emits\n    //      frame, increments sent_count → 2\n    assert!(!pipe\n        .client\n        .streams_blocked_bidi_state\n        .has_pending_stream_blocked_frame());\n\n    // The server has not received anything yet.\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 0);\n\n    // The PTO probe carries the retransmitted STREAMS_BLOCKED frame.\n    // Deliver it to the server.\n    let server_path = &pipe.server.paths.get_active().unwrap();\n    let info = RecvInfo {\n        to: server_path.local_addr(),\n        from: server_path.peer_addr(),\n    };\n    pipe.server.recv(&mut buf[..len], info).unwrap();\n\n    // The server must have received the retransmitted STREAMS_BLOCKED frame.\n    assert_eq!(pipe.server.streams_blocked_bidi_recv_count, 1);\n    let _ = send_info;\n}\n\n/// Tests that a lost STREAMS_BLOCKED (uni) frame is retransmitted.\n///\n/// When the packet carrying the frame is declared lost via the PTO mechanism,\n/// `streams_blocked_bidi_state` must be notified so that the same limit can\n/// be sent again.  The server must ultimately receive the frame.\n#[rstest]\nfn streams_blocked_uni_retransmit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = test_utils::Pipe::default_config(cc_algorithm_name).unwrap();\n    config.set_enable_send_streams_blocked(true);\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Exhaust the server's initial uni stream limit (3) so the next attempt\n    // returns StreamLimit and arms a STREAMS_BLOCKED frame.\n    assert_eq!(pipe.client.stream_send(2, b\"a\", false), Ok(1));\n    assert_eq!(pipe.client.stream_send(6, b\"a\", false), Ok(1));\n    assert_eq!(pipe.client.stream_send(10, b\"a\", false), Ok(1));\n    assert_eq!(\n        pipe.client.stream_send(14, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n\n    // Frame is armed but not yet sent.\n    assert!(pipe\n        .client\n        .streams_blocked_uni_state\n        .has_pending_stream_blocked_frame());\n\n    // Emit the client's flight (stream data + STREAMS_BLOCKED) without\n    // delivering it to the server — the packet is \"lost\" from the server's\n    // perspective.\n    assert!(test_utils::emit_flight(&mut pipe.client).is_ok());\n\n    // After emission the pending slot is cleared and the sent counter advances.\n    assert!(!pipe\n        .client\n        .streams_blocked_uni_state\n        .has_pending_stream_blocked_frame());\n\n    // The server has not received anything yet.\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 0);\n\n    // Trigger loss detection\n    test_utils::trigger_ack_based_loss(&mut pipe.client, &mut pipe.server);\n\n    // Trigger the lost-frames processing by calling send().  The loss handler\n    // is invoked at the start of each send_on_path() call to process any frames\n    // added to lost_frames by ack-based loss detection.\n    let (len, send_info) = pipe.client.send(&mut buf).unwrap();\n\n    assert!(!pipe\n        .client\n        .streams_blocked_uni_state\n        .has_pending_stream_blocked_frame());\n\n    // The server has not received anything yet.\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 0);\n\n    // Deliver the PTO probe (which carries the retransmitted STREAMS_BLOCKED\n    // frame) to the server.\n    let server_path = &pipe.server.paths.get_active().unwrap();\n    let info = RecvInfo {\n        to: server_path.local_addr(),\n        from: server_path.peer_addr(),\n    };\n    pipe.server.recv(&mut buf[..len], info).unwrap();\n\n    // The server must have received the retransmitted STREAMS_BLOCKED frame.\n    assert_eq!(pipe.server.streams_blocked_uni_recv_count, 1);\n    let _ = send_info;\n}\n\n#[rstest]\nfn stream_data_overlap(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"aaaaa\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"bbbbb\", 3, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"ccccc\", 6, false),\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    if discard {\n        pipe.server.stream_discard(0, 11).unwrap();\n    } else {\n        let mut b = [0; 15];\n        assert_eq!(pipe.server.stream_recv(0, &mut b), Ok((11, false)));\n        assert_eq!(&b[..11], b\"aaaaabbbccc\");\n    }\n    assert_eq!(pipe.server.flow_control.consumed(), pipe.server.rx_data);\n}\n\n#[rstest]\nfn stream_data_overlap_with_reordering(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"aaaaa\", 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"ccccc\", 6, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"bbbbb\", 3, false),\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    assert!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf).is_ok());\n\n    if discard {\n        pipe.server.stream_discard(0, 11).unwrap();\n    } else {\n        let mut b = [0; 15];\n        assert_eq!(pipe.server.stream_recv(0, &mut b), Ok((11, false)));\n        assert_eq!(&b[..11], b\"aaaaabccccc\");\n    }\n    assert_eq!(pipe.server.flow_control.consumed(), pipe.server.rx_data);\n}\n\n#[rstest]\n/// Tests that receiving a valid RESET_STREAM frame when all data has\n/// already been read, notifies the application.\nfn reset_stream_data_recvd(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data.\n    assert_eq!(pipe.client.stream_send(0, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server gets data and sends data back, closing stream.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((5, false))\n    );\n\n    assert!(!pipe.server.stream_finished(0));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.server.stream_send(0, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 0),\n        Ok((0, true))\n    );\n\n    assert!(pipe.client.stream_finished(0));\n\n    // Client sends RESET_STREAM, closing stream.\n    let frames = [frame::Frame::ResetStream {\n        stream_id: 0,\n        error_code: 42,\n        final_size: 5,\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(39));\n\n    // Server is notified of stream readability, due to reset.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Err(Error::StreamReset(42))\n    );\n\n    assert!(pipe.server.stream_finished(0));\n\n    // Sending RESET_STREAM again shouldn't make stream readable again.\n    pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.server.flow_control.consumed(), pipe.server.rx_data);\n    assert_eq!(pipe.server.flow_control.consumed(), 5);\n}\n\n#[rstest]\n/// Tests that receiving a valid RESET_STREAM frame when all data has _not_\n/// been read, discards all buffered data and notifies the application.\nfn reset_stream_data_not_recvd(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data.\n    assert_eq!(pipe.client.stream_send(0, b\"h\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server gets data and sends data back, closing stream.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((1, false))\n    );\n    assert!(!pipe.server.stream_finished(0));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.server.stream_send(0, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 0),\n        Ok((0, true))\n    );\n    assert!(pipe.client.stream_finished(0));\n\n    // Client sends RESET_STREAM, closing stream.\n    let frames = [frame::Frame::ResetStream {\n        stream_id: 0,\n        error_code: 42,\n        final_size: 5,\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(39));\n\n    // Server is notified of stream readability, due to reset.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Err(Error::StreamReset(42))\n    );\n\n    assert!(pipe.server.stream_finished(0));\n\n    // Sending RESET_STREAM again shouldn't make stream readable again.\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(39));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.server.flow_control.consumed(), pipe.server.rx_data);\n    assert_eq!(pipe.server.flow_control.consumed(), 5);\n}\n\n#[rstest]\n/// Tests that RESET_STREAM frames exceeding the connection-level flow\n/// control limit cause an error.\nfn reset_stream_flow_control(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(&[1; 15], 0, false),\n        },\n        frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::ResetStream {\n            stream_id: 4,\n            error_code: 0,\n            final_size: 15,\n        },\n        frame::Frame::Stream {\n            stream_id: 8,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::FlowControl),\n    );\n}\n\n#[rstest]\n/// Tests that RESET_STREAM frames exceeding the stream-level flow control\n/// limit cause an error.\nfn reset_stream_flow_control_stream(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [\n        frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        },\n        frame::Frame::ResetStream {\n            stream_id: 4,\n            error_code: 0,\n            final_size: 16, // Past stream's flow control limit.\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf),\n        Err(Error::FlowControl),\n    );\n}\n\n#[rstest]\nfn path_challenge(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::PathChallenge { data: [0xba; 8] }];\n\n    let pkt_type = Type::Short;\n\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    assert!(len > 0);\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let mut iter = frames.iter();\n\n    // Ignore ACK.\n    iter.next().unwrap();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::PathResponse { data: [0xba; 8] })\n    );\n}\n\n#[cfg(not(feature = \"openssl\"))] // 0-RTT not supported when using openssl/quictls\n#[rstest]\n/// Simulates reception of an early 1-RTT packet on the server, by\n/// delaying the client's Handshake packet that completes the handshake.\nfn early_1rtt_packet(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends initial flight\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    // Server sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Client sends Handshake packet.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    // Emulate handshake packet delay by not making server process client\n    // packet.\n    let delayed = flight;\n\n    test_utils::emit_flight(&mut pipe.server).ok();\n\n    assert!(pipe.client.is_established());\n\n    // Send 1-RTT packet #0.\n    let frames = [frame::Frame::Stream {\n        stream_id: 0,\n        data: <RangeBuf>::from(b\"hello, world\", 0, true),\n    }];\n\n    let pkt_type = Type::Short;\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n\n    // Send 1-RTT packet #1.\n    let frames = [frame::Frame::Stream {\n        stream_id: 4,\n        data: <RangeBuf>::from(b\"hello, world\", 0, true),\n    }];\n\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n\n    assert!(!pipe.server.is_established());\n\n    // Client sent 1-RTT packets 0 and 1, but server hasn't received them.\n    //\n    // Note that `largest_rx_pkt_num` is initialized to 0, so we need to\n    // send another 1-RTT packet to make this check meaningful.\n    assert_eq!(\n        pipe.server.pkt_num_spaces[packet::Epoch::Application].largest_rx_pkt_num,\n        0\n    );\n\n    // Process delayed packet.\n    test_utils::process_flight(&mut pipe.server, delayed).unwrap();\n\n    assert!(pipe.server.is_established());\n\n    assert_eq!(\n        pipe.server.pkt_num_spaces[packet::Epoch::Application].largest_rx_pkt_num,\n        0\n    );\n}\n\n#[rstest]\nfn stop_sending(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data, and closes stream.\n    assert_eq!(pipe.client.stream_send(0, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server gets data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((5, true))\n    );\n    assert!(pipe.server.stream_finished(0));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Server sends data, until blocked.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    while pipe.server.stream_send(0, b\"world\", false) != Err(Error::Done) {\n        assert_eq!(pipe.advance(), Ok(()));\n    }\n\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), None);\n\n    // Client sends STOP_SENDING.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // Server sent a RESET_STREAM frame in response.\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ResetStream {\n            stream_id: 0,\n            error_code: 42,\n            final_size: 15,\n        })\n    );\n\n    // Stream is writable, but writing returns an error.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        pipe.server.stream_send(0, b\"world\", true),\n        Err(Error::StreamStopped(42)),\n    );\n\n    // Returning `StreamStopped` causes the stream to be collected.\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    // Client acks RESET_STREAM frame.\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(pipe.server.next_pkt_num - 5..pipe.server.next_pkt_num);\n\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(0));\n\n    // Sending STOP_SENDING again shouldn't trigger RESET_STREAM again.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    assert_eq!(frames.len(), 1);\n\n    match frames.first() {\n        Some(frame::Frame::ACK { .. }) => (),\n\n        f => panic!(\"expected ACK frame, got {f:?}\"),\n    };\n\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), None);\n}\n\n#[rstest]\nfn stop_sending_fin(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data, and closes stream.\n    assert_eq!(pipe.client.stream_send(4, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server gets data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 4),\n        Ok((5, true))\n    );\n    assert!(pipe.server.stream_finished(4));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Server sends data...\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.server.stream_send(4, b\"world\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // ...and buffers more, and closes stream.\n    assert_eq!(pipe.server.stream_send(4, b\"world\", true), Ok(5));\n\n    // Client sends STOP_SENDING before server flushes stream.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 4,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // Server sent a RESET_STREAM frame in response.\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ResetStream {\n            stream_id: 4,\n            error_code: 42,\n            final_size: 5,\n        })\n    );\n\n    // No more frames are sent by the server.\n    assert_eq!(iter.next(), None);\n}\n\n#[rstest]\n/// Tests that resetting a stream restores flow control for unsent data.\nfn stop_sending_unsent_tx_cap(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(15);\n    config.set_initial_max_stream_data_bidi_local(30);\n    config.set_initial_max_stream_data_bidi_remote(30);\n    config.set_initial_max_stream_data_uni(30);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data.\n    assert_eq!(pipe.client.stream_send(4, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 4),\n        Ok((5, true))\n    );\n\n    // Server sends some data.\n    assert_eq!(pipe.server.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server buffers some data, until send capacity limit reached.\n    assert_eq!(pipe.server.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.server.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(\n        pipe.server.stream_send(4, b\"hello\", false),\n        Err(Error::Done)\n    );\n\n    // Client sends STOP_SENDING.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 4,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n    pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // Server can now send more data (on a different stream).\n    assert_eq!(pipe.client.stream_send(8, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.stream_send(8, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.server.stream_send(8, b\"hello\", false), Ok(5));\n    assert_eq!(\n        pipe.server.stream_send(8, b\"hello\", false),\n        Err(Error::Done)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n}\n\n#[rstest]\n/// Tests that the `StreamStopped` error is propagated even if the RESET_STREAM\n/// in response to STOP_SENDING is acked before the application processes\n/// writable streams.\nfn stop_sending_ack_race(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data, and closes stream.\n    assert_eq!(pipe.client.stream_send(0, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server gets data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((5, true))\n    );\n    assert!(pipe.server.stream_finished(0));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Server sends data, until blocked.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    while pipe.server.stream_send(0, b\"world\", false) != Err(Error::Done) {\n        assert_eq!(pipe.advance(), Ok(()));\n    }\n\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), None);\n\n    // Client sends STOP_SENDING.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // Server sent a RESET_STREAM frame in response.\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ResetStream {\n            stream_id: 0,\n            error_code: 42,\n            final_size: 15,\n        })\n    );\n\n    // Client acks RESET_STREAM frame *before* server calls `writable()`.\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(pipe.server.next_pkt_num - 5..pipe.server.next_pkt_num);\n\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(0));\n\n    assert_eq!(pipe.server.streams.len(), 1);\n\n    // Stream is writable, but writing returns an error.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        pipe.server.stream_send(0, b\"world\", true),\n        Err(Error::StreamStopped(42)),\n    );\n\n    // Stream is collected on the server after `StreamStopped` is returned.\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    // Sending STOP_SENDING again shouldn't trigger RESET_STREAM again.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    assert_eq!(frames.len(), 1);\n\n    match frames.first() {\n        Some(frame::Frame::ACK { .. }) => (),\n\n        f => panic!(\"expected ACK frame, got {f:?}\"),\n    };\n\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), None);\n}\n\n#[rstest]\n/// Tests that the `StreamStopped` error is propagated even if a STREAM frame\n/// is acked before the application processes writable streams.\nfn stop_sending_stream_ack_race(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data, and closes stream.\n    assert_eq!(pipe.client.stream_send(0, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server gets data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((5, true))\n    );\n    assert!(pipe.server.stream_finished(0));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Server sends data and finishes the stream.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.server.stream_send(0, b\"world\", true), Ok(5));\n\n    // Client receives STREAM frame but doesn't ack yet.\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Client sends STOP_SENDING.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // Server sent a RESET_STREAM frame in response.\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ResetStream {\n            stream_id: 0,\n            error_code: 42,\n            final_size: 5,\n        })\n    );\n\n    // Client acks RESET_STREAM and STREAM frames *before* server calls\n    // `writable()`.\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(pipe.server.next_pkt_num - 5..pipe.server.next_pkt_num);\n\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(0));\n\n    assert_eq!(pipe.server.streams.len(), 1);\n\n    // Stream is writable, but writing returns an error.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        pipe.server.stream_send(0, b\"world\", true),\n        Err(Error::StreamStopped(42)),\n    );\n\n    // Stream is collected on the server after `StreamStopped` is returned.\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    // Sending STOP_SENDING again shouldn't trigger RESET_STREAM again.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    assert_eq!(frames.len(), 1);\n\n    match frames.first() {\n        Some(frame::Frame::ACK { .. }) => (),\n\n        f => panic!(\"expected ACK frame, got {f:?}\"),\n    };\n\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), None);\n}\n\n#[rstest]\nfn stream_shutdown_read(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data.\n    assert_eq!(pipe.client.stream_send(4, b\"hello, world\", false), Ok(12));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.client.streams.len(), 1);\n    assert_eq!(pipe.server.streams.len(), 1);\n\n    // Server shuts down stream.\n    assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Read, 42), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n\n    let mut dummy = buf[..len].to_vec();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut dummy[..len]).unwrap();\n    // make sure the pkt contains the expected StopSending frame\n    assert!(frames.iter().any(|f| {\n        *f == frame::Frame::StopSending {\n            stream_id: 4,\n            error_code: 42,\n        }\n    }));\n\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Sending more data is forbidden.\n    let mut r = pipe.client.writable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        pipe.client.stream_send(4, b\"bye\", false),\n        Err(Error::StreamStopped(42))\n    );\n\n    // Server sends some data, without reading the incoming data, and closes\n    // the stream.\n    assert_eq!(pipe.server.stream_send(4, b\"hello, world\", true), Ok(12));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client reads the data.\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 4),\n        Ok((12, true))\n    );\n\n    // Stream is collected on both sides.\n    assert_eq!(pipe.client.streams.len(), 0);\n    assert_eq!(pipe.server.streams.len(), 0);\n    assert_eq!(pipe.client.flow_control.consumed(), pipe.client.rx_data);\n    assert_eq!(pipe.server.flow_control.consumed(), pipe.server.rx_data);\n\n    assert_eq!(\n        pipe.server.stream_shutdown(4, Shutdown::Read, 0),\n        Err(Error::Done)\n    );\n}\n\n#[rstest]\nfn stream_shutdown_read_after_fin(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data and a FIN.\n    assert_eq!(pipe.client.stream_send(4, b\"hello, world\", true), Ok(12));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.client.streams.len(), 1);\n    assert_eq!(pipe.server.streams.len(), 1);\n\n    // Server shuts down stream.\n    assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Read, 42), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Server sends a flow control update, but it does NOT send\n    // STOP_SENDING frame, since it has already received a FIN from\n    // the client.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let mut dummy = buf[..len].to_vec();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut dummy[..len]).unwrap();\n    for f in frames {\n        assert!(!matches!(f, frame::Frame::StopSending { .. }));\n    }\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends some data, without reading the incoming data, and closes\n    // the stream.\n    assert_eq!(pipe.server.stream_send(4, b\"hello, world\", true), Ok(12));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client reads the data.\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 4),\n        Ok((12, true))\n    );\n\n    // Stream is collected on both sides.\n    assert_eq!(pipe.client.streams.len(), 0);\n    assert_eq!(pipe.server.streams.len(), 0);\n    assert_eq!(pipe.client.flow_control.consumed(), pipe.client.rx_data);\n    assert_eq!(pipe.server.flow_control.consumed(), pipe.server.rx_data);\n\n    assert_eq!(\n        pipe.server.stream_shutdown(4, Shutdown::Read, 0),\n        Err(Error::Done)\n    );\n}\n\n#[rstest]\nfn stream_shutdown_read_update_max_data(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(10000);\n    config.set_initial_max_stream_data_bidi_remote(10000);\n    config.set_initial_max_streams_bidi(10);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((1, false))\n    );\n\n    // Client sends data that the server does not read before it shuts down\n    // the read direction\n    assert_eq!(pipe.client.stream_send(0, &buf[0..20], false), Ok(20));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server has received 21 bytes, but only 1 have been read/consumed\n    assert_eq!(pipe.server.rx_data, 21);\n    assert_eq!(pipe.server.flow_control.consumed(), 1);\n\n    assert_eq!(pipe.client.stream_send(0, &buf, false), Ok(9));\n    // Connection level flow control limit reached\n    assert_eq!(pipe.client.stream_send(0, &[0], false), Err(Error::Done));\n    assert_eq!(pipe.client.stream_send(4, &[0], false), Err(Error::Done));\n\n    // Shutting down the read side, returns buffered data to flow control budget\n    // (but we are *not advancing* the pipe yet, so client limit is not increased)\n    assert_eq!(pipe.server.stream_shutdown(0, Shutdown::Read, 123), Ok(()));\n\n    assert!(!pipe.server.stream_readable(0)); // nothing can be consumed\n\n    assert_eq!(pipe.server.rx_data, 21);\n    // all bytes in the read buffer have been marked as consumed\n    assert_eq!(pipe.server.flow_control.consumed(), 21);\n    assert_eq!(pipe.client.tx_data, 30);\n    assert_eq!(pipe.client.max_tx_data, 30);\n\n    // Client is still blocked\n    assert_eq!(pipe.client.stream_send(0, &[0], false), Err(Error::Done));\n    assert_eq!(pipe.client.stream_send(4, &[0], false), Err(Error::Done));\n\n    // Send a flight of packet from server -> client. We only send in this\n    // direction so ensure that the server sends a MAX_DATA frame on its own,\n    // if we advance the pipe, the client would respond with RESET and that\n    // would increase the window\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // The client has dropped the 9 unset bytes in its buffer\n    assert_eq!(pipe.client.tx_data, 21);\n    assert_eq!(pipe.server.rx_data, 21);\n    assert_eq!(pipe.server.flow_control.consumed(), 21);\n    // default window is 1.5 * initial_max_data, so 45\n    assert_eq!(\n        pipe.client.tx_cap,\n        pipe.server.flow_control.window() as usize\n    );\n    assert_eq!(pipe.client.tx_cap, 45);\n\n    assert_eq!(\n        pipe.client.stream_send(0, b\"hello, world\", false),\n        Err(Error::StreamStopped(123))\n    );\n\n    // fully advance pipe\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(pipe.client.tx_data, 21);\n    assert_eq!(pipe.server.rx_data, 21);\n    assert!(!pipe.server.stream_readable(0)); // nothing can be consumed\n\n    // Server sends fin to fully close the stream.\n    assert_eq!(pipe.server.stream_send(0, &[], true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 0),\n        Ok((0, true))\n    );\n\n    assert!(pipe.server.streams.is_collected(0));\n    assert!(pipe.client.streams.is_collected(0));\n}\n\n/// Tests that sending a reset should drop the receive buffer on the peer and\n/// return flow control credit.\n#[rstest]\nfn stream_shutdown_write_update_max_data(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(10000);\n    config.set_initial_max_stream_data_bidi_remote(10000);\n    config.set_initial_max_streams_bidi(10);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((1, false))\n    );\n\n    // Client sends data until blocked that the server does not read\n    while pipe.client.stream_send(0, b\"world\", false) != Err(Error::Done) {\n        assert_eq!(pipe.advance(), Ok(()));\n    }\n    // sending on a different stream is blocked by flow control\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Err(Error::Done));\n    assert_eq!(pipe.client.max_tx_data, 30);\n    assert_eq!(pipe.client.tx_data, 30);\n\n    // Server has received 30 bytes, but only 1 has been read/consumed\n    assert_eq!(pipe.server.rx_data, 30);\n    assert_eq!(pipe.server.flow_control.consumed(), 1);\n\n    // The client shuts down its write and sends a reset\n    assert_eq!(pipe.client.stream_shutdown(0, Shutdown::Write, 42), Ok(()));\n    pipe.advance().unwrap();\n\n    // Receiving the reset drops the receive buffer and returns flow control\n    // credit\n    assert_eq!(pipe.server.rx_data, 30);\n    assert_eq!(pipe.server.flow_control.consumed(), 30);\n    assert_eq!(pipe.client.tx_data, 30);\n    // default window is 1.5 * initial_max_data, so 45\n    assert_eq!(pipe.client.tx_cap, 45);\n\n    // client can send again on a different stream\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Ok(1));\n}\n\n#[rstest]\nfn stream_shutdown_uni(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Exchange some data on uni streams.\n    assert_eq!(pipe.client.stream_send(2, b\"hello, world\", false), Ok(10));\n    assert_eq!(pipe.server.stream_send(3, b\"hello, world\", false), Ok(10));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Test local and remote shutdown.\n    assert_eq!(pipe.client.stream_shutdown(2, Shutdown::Write, 42), Ok(()));\n    assert_eq!(\n        pipe.client.stream_shutdown(2, Shutdown::Read, 42),\n        Err(Error::InvalidStreamState(2))\n    );\n\n    assert_eq!(\n        pipe.client.stream_shutdown(3, Shutdown::Write, 42),\n        Err(Error::InvalidStreamState(3))\n    );\n    assert_eq!(pipe.client.stream_shutdown(3, Shutdown::Read, 42), Ok(()));\n}\n\n#[rstest]\nfn stream_shutdown_write(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data.\n    assert_eq!(pipe.client.stream_send(4, b\"hello, world\", false), Ok(12));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.client.streams.len(), 1);\n    assert_eq!(pipe.server.streams.len(), 1);\n\n    // Server sends some data.\n    assert_eq!(pipe.server.stream_send(4, b\"goodbye, world\", false), Ok(14));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server shuts down stream.\n    assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Write, 42), Ok(()));\n\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), None);\n\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n\n    let mut dummy = buf[..len].to_vec();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut dummy[..len]).unwrap();\n    let mut iter = frames.iter();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ResetStream {\n            stream_id: 4,\n            error_code: 42,\n            final_size: 14,\n        })\n    );\n\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Sending more data is forbidden.\n    assert_eq!(\n        pipe.server.stream_send(4, b\"bye\", false),\n        Err(Error::FinalSize)\n    );\n\n    // Client sends some data and closes the stream.\n    assert_eq!(pipe.client.stream_send(4, b\"bye\", true), Ok(3));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads the data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 4),\n        Ok((15, true))\n    );\n\n    // Client processes readable streams.\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 4),\n        Err(Error::StreamReset(42))\n    );\n\n    // Stream is collected on both sides.\n    assert_eq!(pipe.client.streams.len(), 0);\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    assert_eq!(\n        pipe.server.stream_shutdown(4, Shutdown::Write, 0),\n        Err(Error::Done)\n    );\n}\n\n#[rstest]\n/// Tests that shutting down a stream restores flow control for unsent data.\nfn stream_shutdown_write_unsent_tx_cap(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(15);\n    config.set_initial_max_stream_data_bidi_local(30);\n    config.set_initial_max_stream_data_bidi_remote(30);\n    config.set_initial_max_stream_data_uni(30);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data.\n    assert_eq!(pipe.client.stream_send(4, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 4),\n        Ok((5, true))\n    );\n\n    // Server sends some data.\n    assert_eq!(pipe.server.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server buffers some data, until send capacity limit reached.\n    assert_eq!(pipe.server.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.server.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(\n        pipe.server.stream_send(4, b\"hello\", false),\n        Err(Error::Done)\n    );\n    assert_eq!(pipe.server.tx_data, 15);\n\n    // Client shouldn't update flow control.\n    assert!(!pipe.client.flow_control.should_update_max_data());\n\n    // Server shuts down stream.\n    assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Write, 42), Ok(()));\n    // Unsend data is dropped and returned to flow control credit\n    assert_eq!(pipe.server.tx_data, 5);\n\n    // Server can now send more data (on a different stream).\n    assert_eq!(pipe.client.stream_send(8, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.stream_send(8, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.server.stream_send(8, b\"hello\", false), Ok(5));\n    assert_eq!(\n        pipe.server.stream_send(8, b\"hello\", false),\n        Err(Error::Done)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n}\n\n#[rstest]\n/// Tests that the order of flushable streams scheduled on the wire is the\n/// same as the order of `stream_send()` calls done by the application.\nfn stream_round_robin(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(8, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.client.stream_send(4, b\"aaaaa\", false), Ok(5));\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::Stream {\n            stream_id: 8,\n            data: <RangeBuf>::from(b\"aaaaa\", 0, false),\n        })\n    );\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    assert_eq!(\n        frames.first(),\n        Some(&frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"aaaaa\", 0, false),\n        })\n    );\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    assert_eq!(\n        frames.first(),\n        Some(&frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"aaaaa\", 0, false),\n        })\n    );\n}\n\n#[rstest]\n/// Tests the readable iterator.\nfn stream_readable(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // No readable streams.\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaa\", false), Ok(5));\n\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), None);\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server received stream.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        pipe.server.stream_send(0, b\"aaaaaaaaaaaaaaa\", false),\n        Ok(15)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    // Client drains stream.\n    stream_recv_discard(&mut pipe.client, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), None);\n\n    // Server shuts down stream.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.server.stream_shutdown(0, Shutdown::Read, 0), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Client creates multiple streams.\n    assert_eq!(pipe.client.stream_send(4, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(8, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.len(), 2);\n\n    assert!(r.next().is_some());\n    assert!(r.next().is_some());\n    assert!(r.next().is_none());\n\n    assert_eq!(r.len(), 0);\n}\n\n#[rstest]\n/// Tests the writable iterator.\nfn stream_writable(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // No writable streams.\n    let mut w = pipe.client.writable();\n    assert_eq!(w.next(), None);\n\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaa\", false), Ok(5));\n\n    // Client created stream.\n    let mut w = pipe.client.writable();\n    assert_eq!(w.next(), Some(0));\n    assert_eq!(w.next(), None);\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server created stream.\n    let mut w = pipe.server.writable();\n    assert_eq!(w.next(), Some(0));\n    assert_eq!(w.next(), None);\n\n    assert_eq!(\n        pipe.server.stream_send(0, b\"aaaaaaaaaaaaaaa\", false),\n        Ok(15)\n    );\n\n    // Server stream is full.\n    let mut w = pipe.server.writable();\n    assert_eq!(w.next(), None);\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client drains stream.\n    stream_recv_discard(&mut pipe.client, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server stream is writable again.\n    let mut w = pipe.server.writable();\n    assert_eq!(w.next(), Some(0));\n    assert_eq!(w.next(), None);\n\n    // Server shuts down stream.\n    assert_eq!(pipe.server.stream_shutdown(0, Shutdown::Write, 0), Ok(()));\n\n    let mut w = pipe.server.writable();\n    assert_eq!(w.next(), None);\n\n    // Client creates multiple streams.\n    assert_eq!(pipe.client.stream_send(4, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(8, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut w = pipe.server.writable();\n    assert_eq!(w.len(), 2);\n\n    assert!(w.next().is_some());\n    assert!(w.next().is_some());\n    assert!(w.next().is_none());\n\n    assert_eq!(w.len(), 0);\n\n    // Server finishes stream.\n    assert_eq!(pipe.server.stream_send(8, b\"aaaaa\", true), Ok(5));\n\n    let mut w = pipe.server.writable();\n    assert_eq!(w.next(), Some(4));\n    assert_eq!(w.next(), None);\n}\n\n#[rstest]\nfn stream_writable_blocked(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config.set_application_protos(&[b\"h3\"]).unwrap();\n    config.set_initial_max_data(70);\n    config.set_initial_max_stream_data_bidi_local(150000);\n    config.set_initial_max_stream_data_bidi_remote(150000);\n    config.set_initial_max_stream_data_uni(150000);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(5);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client creates stream and sends some data.\n    let send_buf = [0; 35];\n    assert_eq!(pipe.client.stream_send(0, &send_buf, false), Ok(35));\n\n    // Stream is still writable as it still has capacity.\n    assert_eq!(pipe.client.stream_writable_next(), Some(0));\n    assert_eq!(pipe.client.stream_writable_next(), None);\n\n    // Client fills stream, which becomes unwritable due to connection\n    // capacity.\n    let send_buf = [0; 36];\n    assert_eq!(pipe.client.stream_send(0, &send_buf, false), Ok(35));\n\n    assert_eq!(pipe.client.stream_writable_next(), None);\n\n    assert_eq!(pipe.client.tx_cap, 0);\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // The connection capacity has increased and the stream is now writable\n    // again.\n    assert_ne!(pipe.client.tx_cap, 0);\n\n    assert_eq!(pipe.client.stream_writable_next(), Some(0));\n    assert_eq!(pipe.client.stream_writable_next(), None);\n}\n\n#[rstest]\n/// Tests that we don't exceed the per-connection flow control limit set by\n/// the peer.\nfn flow_control_limit_send(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(\n        pipe.client.stream_send(0, b\"aaaaaaaaaaaaaaa\", false),\n        Ok(15)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(\n        pipe.client.stream_send(4, b\"aaaaaaaaaaaaaaa\", false),\n        Ok(15)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(pipe.client.stream_send(8, b\"a\", false), Err(Error::Done));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert!(r.next().is_some());\n    assert!(r.next().is_some());\n    assert!(r.next().is_none());\n\n    assert_eq!(pipe.server.data_blocked_sent_count, 0);\n    assert_eq!(pipe.server.stream_data_blocked_sent_count, 0);\n    assert_eq!(pipe.server.data_blocked_recv_count, 1);\n    assert_eq!(pipe.server.stream_data_blocked_recv_count, 0);\n\n    assert_eq!(pipe.client.data_blocked_sent_count, 1);\n    assert_eq!(pipe.client.stream_data_blocked_sent_count, 0);\n    assert_eq!(pipe.client.data_blocked_recv_count, 0);\n    assert_eq!(pipe.client.stream_data_blocked_recv_count, 0);\n}\n\n#[rstest]\n/// Tests that invalid packets received before any other valid ones cause\n/// the server to close the connection immediately.\nfn invalid_initial_server(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    let frames = [frame::Frame::Padding { len: 10 }];\n\n    let written = test_utils::encode_pkt(\n        &mut pipe.client,\n        Type::Initial,\n        &frames,\n        &mut buf,\n    )\n    .unwrap();\n\n    // Corrupt the packets's last byte to make decryption fail (the last\n    // byte is part of the AEAD tag, so changing it means that the packet\n    // cannot be authenticated during decryption).\n    buf[written - 1] = !buf[written - 1];\n\n    assert_eq!(pipe.server.timeout(), None);\n\n    assert_eq!(\n        pipe.server_recv(&mut buf[..written]),\n        Err(Error::CryptoFail)\n    );\n\n    assert!(pipe.server.is_closed());\n}\n\n#[rstest]\n/// Tests that invalid Initial packets received to cause\n/// the client to close the connection immediately.\nfn invalid_initial_client(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends initial flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server sends initial flight.\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(1200));\n\n    let frames = [frame::Frame::Padding { len: 10 }];\n\n    let written = test_utils::encode_pkt(\n        &mut pipe.server,\n        Type::Initial,\n        &frames,\n        &mut buf,\n    )\n    .unwrap();\n\n    // Corrupt the packets's last byte to make decryption fail (the last\n    // byte is part of the AEAD tag, so changing it means that the packet\n    // cannot be authenticated during decryption).\n    buf[written - 1] = !buf[written - 1];\n\n    // Client will ignore invalid packet.\n    assert_eq!(pipe.client_recv(&mut buf[..written]), Ok(71));\n\n    // The connection should be alive...\n    assert!(!pipe.client.is_closed());\n\n    // ...and the idle timeout should be armed.\n    assert!(pipe.client.idle_timer.is_some());\n}\n\n#[rstest]\n/// Tests that packets with invalid payload length received before any other\n/// valid packet cause the server to close the connection immediately.\nfn invalid_initial_payload(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    let mut b = octets::OctetsMut::with_slice(&mut buf);\n\n    let epoch = Type::Initial.to_epoch().unwrap();\n\n    let pn = 0;\n    let pn_len = packet::pkt_num_len(pn, 0);\n\n    let dcid = pipe.client.destination_id();\n    let scid = pipe.client.source_id();\n\n    let hdr = Header {\n        ty: Type::Initial,\n        version: pipe.client.version,\n        dcid: ConnectionId::from_ref(&dcid),\n        scid: ConnectionId::from_ref(&scid),\n        pkt_num: 0,\n        pkt_num_len: pn_len,\n        token: pipe.client.token.clone(),\n        versions: None,\n        key_phase: false,\n    };\n\n    hdr.to_bytes(&mut b).unwrap();\n\n    // Payload length is invalid!!!\n    let payload_len = 4096;\n\n    let len = pn_len + payload_len;\n    b.put_varint(len as u64).unwrap();\n\n    packet::encode_pkt_num(pn, pn_len, &mut b).unwrap();\n\n    let payload_offset = b.off();\n\n    let frames = [frame::Frame::Padding { len: 10 }];\n\n    for frame in &frames {\n        frame.to_bytes(&mut b).unwrap();\n    }\n\n    let crypto_ctx = &mut pipe.client.crypto_ctx[epoch];\n\n    // Use correct payload length when encrypting the packet.\n    let payload_len = frames.iter().fold(0, |acc, x| acc + x.wire_len());\n\n    let aead = crypto_ctx.crypto_seal.as_mut().unwrap();\n\n    let written = packet::encrypt_pkt(\n        &mut b,\n        pn,\n        pn_len,\n        payload_len,\n        payload_offset,\n        None,\n        aead,\n    )\n    .unwrap();\n\n    assert_eq!(pipe.server.timeout(), None);\n\n    assert_eq!(\n        pipe.server_recv(&mut buf[..written]),\n        Err(Error::InvalidPacket)\n    );\n\n    assert!(pipe.server.is_closed());\n}\n\n#[rstest]\n/// Tests that invalid packets don't cause the connection to be closed.\nfn invalid_packet(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = [frame::Frame::Padding { len: 10 }];\n\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, Type::Short, &frames, &mut buf)\n            .unwrap();\n\n    // Corrupt the packets's last byte to make decryption fail (the last\n    // byte is part of the AEAD tag, so changing it means that the packet\n    // cannot be authenticated during decryption).\n    buf[written - 1] = !buf[written - 1];\n\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n\n    // Corrupt the packets's first byte to make the header fail decoding.\n    buf[0] = 255;\n\n    assert_eq!(pipe.server_recv(&mut buf[..written]), Ok(written));\n}\n\n#[rstest]\nfn recv_empty_buffer(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.server_recv(&mut buf[..0]), Err(Error::BufferTooShort));\n}\n\n#[rstest]\nfn stop_sending_before_flushed_packets(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data, and closes stream.\n    assert_eq!(pipe.client.stream_send(0, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server gets data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((5, true))\n    );\n    assert!(pipe.server.stream_finished(0));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Server sends data, until blocked.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    while pipe.server.stream_send(0, b\"world\", false) != Err(Error::Done) {}\n\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), None);\n\n    // Client sends STOP_SENDING.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // Server sent a RESET_STREAM frame in response.\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ResetStream {\n            stream_id: 0,\n            error_code: 42,\n            final_size: 0,\n        })\n    );\n\n    // Stream is writable, but writing returns an error.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        pipe.server.stream_send(0, b\"world\", true),\n        Err(Error::StreamStopped(42)),\n    );\n\n    // Returning `StreamStopped` causes the stream to be collected.\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    // Client acks RESET_STREAM frame.\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(0..6);\n\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(0));\n}\n\n#[rstest]\nfn reset_before_flushed_packets(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(5);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_streams_bidi(3);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data, and closes stream.\n    assert_eq!(pipe.client.stream_send(0, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server gets data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((5, true))\n    );\n    assert!(pipe.server.stream_finished(0));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Server sends data and is blocked by small stream flow control.\n    let mut r = pipe.server.writable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    assert_eq!(pipe.server.stream_send(0, b\"helloworld\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client reads to give flow control back.\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 0),\n        Ok((5, false))\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server writes stream data and resets the stream before sending a\n    // packet.\n    assert_eq!(pipe.server.stream_send(0, b\"world\", false), Ok(5));\n    pipe.server.stream_shutdown(0, Shutdown::Write, 42).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client has ACK'd the RESET_STREAM so the stream is collected.\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    assert_eq!(pipe.server.data_blocked_sent_count, 0);\n    assert_eq!(pipe.server.stream_data_blocked_sent_count, 1);\n    assert_eq!(pipe.server.data_blocked_recv_count, 0);\n    assert_eq!(pipe.server.stream_data_blocked_recv_count, 0);\n\n    assert_eq!(pipe.client.data_blocked_sent_count, 0);\n    assert_eq!(pipe.client.stream_data_blocked_sent_count, 0);\n    assert_eq!(pipe.client.data_blocked_recv_count, 0);\n    assert_eq!(pipe.client.stream_data_blocked_recv_count, 1);\n}\n\n#[rstest]\n/// Tests that the MAX_STREAMS frame is sent for bidirectional streams.\nfn stream_limit_update_bidi(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"b\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"b\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    stream_recv_discard(&mut pipe.server, discard, 4).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends stream data, with fin.\n    assert_eq!(pipe.server.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.stream_send(4, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.stream_send(4, b\"b\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.stream_send(0, b\"b\", true), Ok(1));\n\n    // Server sends MAX_STREAMS.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client tries to create new streams.\n    assert_eq!(pipe.client.stream_send(8, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(12, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(16, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        pipe.client.stream_send(20, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n\n    assert_eq!(pipe.server.readable().len(), 3);\n}\n\n#[rstest]\n/// Tests that the MAX_STREAMS frame is sent for unidirectional streams.\nfn stream_limit_update_uni(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(0);\n    config.set_initial_max_streams_uni(3);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(2, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(6, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(6, b\"b\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(2, b\"b\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 2).unwrap();\n    stream_recv_discard(&mut pipe.server, discard, 6).unwrap();\n\n    // Server sends MAX_STREAMS.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client tries to create new streams.\n    assert_eq!(pipe.client.stream_send(10, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(14, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(18, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        pipe.client.stream_send(22, b\"a\", false),\n        Err(Error::StreamLimit)\n    );\n\n    assert_eq!(pipe.server.readable().len(), 3);\n}\n\n#[rstest]\n/// Tests that MAX_STREAMS is correctly sent only when available capacity\n/// reaches the threshold (50% of initial).\nfn max_streams_sent_only_when_at_threshold(\n    #[values(\"cubic\", \"bbr2\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(1000);\n    config.set_initial_max_stream_data_bidi_local(100);\n    config.set_initial_max_stream_data_bidi_remote(100);\n    config.set_initial_max_streams_bidi(6);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let mut buf = [0; 100];\n\n    // Test aged connection behavior: initial max_streams_bidi = 6, threshold = 3\n    // Complete 10 batches of 6 streams (60 total) to simulate aged connection\n    // This will increase next to 66\n    for batch in 0..=9 {\n        // Client side: send 6 streams with fin\n        for i in 0..6 {\n            let stream_id = (batch * 6 + i) * 4;\n            pipe.client.stream_send(stream_id, b\"a\", true).ok();\n        }\n        pipe.advance().ok();\n\n        // Server side: receive and send back with fin\n        for i in 0..6 {\n            let stream_id = (batch * 6 + i) * 4;\n            pipe.server.stream_recv(stream_id, &mut buf).ok();\n            pipe.server.stream_send(stream_id, b\"a\", true).ok();\n        }\n        pipe.advance().ok();\n\n        // Client side: receive to complete\n        for i in 0..6 {\n            let stream_id = (batch * 6 + i) * 4;\n            pipe.client.stream_recv(stream_id, &mut buf).ok();\n        }\n        pipe.advance().ok();\n    }\n\n    // At this point: next = 66, completed = 60, available = 6\n    // Complete 2 more streams → available = 4 (> 3)\n    // MAX_STREAMS should NOT be sent\n    assert_eq!(pipe.server.streams.max_streams_bidi_next(), 66);\n    assert_eq!(pipe.client.streams.peer_streams_left_bidi(), 6);\n    pipe.client.stream_send(240, b\"a\", true).ok();\n    pipe.client.stream_send(244, b\"a\", true).ok();\n    pipe.advance().ok();\n\n    pipe.server.stream_recv(240, &mut buf).ok();\n    pipe.server.stream_recv(244, &mut buf).ok();\n    pipe.server.stream_send(240, b\"a\", true).ok();\n    pipe.server.stream_send(244, b\"a\", true).ok();\n    pipe.advance().ok();\n\n    pipe.client.stream_recv(240, &mut buf).ok();\n    pipe.client.stream_recv(244, &mut buf).ok();\n    pipe.advance().ok();\n\n    // Verify MAX_STREAMS was NOT sent (4 > 3 threshold)\n    assert_eq!(pipe.client.streams.peer_streams_left_bidi(), 4);\n\n    // Complete 1 more stream → available = 3 (== 3)\n    // MAX_STREAMS should be sent (new limit: 72)\n    pipe.client.stream_send(248, b\"a\", true).ok();\n    pipe.advance().ok();\n\n    pipe.server.stream_recv(248, &mut buf).ok();\n    pipe.server.stream_send(248, b\"a\", true).ok();\n    pipe.advance().ok();\n\n    pipe.client.stream_recv(248, &mut buf).ok();\n    pipe.advance().ok();\n\n    // Verify MAX_STREAMS was sent (limit increased from 66)\n    let left_after = pipe.client.streams.peer_streams_left_bidi();\n    assert!(\n        left_after > 4,\n        \"MAX_STREAMS should have been sent, expected > 4 streams left, got {}\",\n        left_after\n    );\n}\n\n#[rstest]\n/// Tests that applications maintaining high concurrent stream usage\n/// can reliably recreate streams after aging the connection.\n///\n/// This exercises the feedback scenario: an app that maintains 9 concurrent\n/// streams with a limit of 10. After aging the connection, when streams\n/// complete, the app should be able to recreate them without starvation.\nfn high_utilization_maintains_streams_in_aged_connection(\n    #[values(\"cubic\", \"bbr2\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(100000);\n    config.set_initial_max_stream_data_bidi_local(10000);\n    config.set_initial_max_stream_data_bidi_remote(10000);\n    config.set_initial_max_streams_bidi(10);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let mut buf = [0; 100];\n\n    // Age the connection by completing batches of streams\n    // Complete 5 batches of 10 streams = 50 total streams\n    for batch in 0..5 {\n        for i in 0..10 {\n            let stream_id = (batch * 10 + i) * 4;\n\n            // Client opens stream and sends data with FIN\n            assert_eq!(\n                pipe.client.stream_send(stream_id, b\"request\", true),\n                Ok(7)\n            );\n        }\n        assert_eq!(pipe.advance(), Ok(()));\n\n        // Server receives and responds with FIN\n        for i in 0..10 {\n            let stream_id = (batch * 10 + i) * 4;\n            pipe.server.stream_recv(stream_id, &mut buf).ok();\n            assert_eq!(\n                pipe.server.stream_send(stream_id, b\"response\", true),\n                Ok(8)\n            );\n        }\n        assert_eq!(pipe.advance(), Ok(()));\n\n        // Client receives responses to complete bidirectional exchange\n        for i in 0..10 {\n            let stream_id = (batch * 10 + i) * 4;\n            pipe.client.stream_recv(stream_id, &mut buf).ok();\n        }\n        assert_eq!(pipe.advance(), Ok(()));\n    }\n\n    // Verify connection is aged: server's max should have grown from 10 to 60\n    assert_eq!(pipe.server.streams.max_streams_bidi(), 60);\n    assert_eq!(pipe.server.streams.max_streams_bidi_next(), 60);\n\n    // Now open 9 concurrent streams (high utilization)\n    for i in 0..9 {\n        let stream_id = (50 + i) * 4; // Continue from stream 50\n        assert_eq!(pipe.client.stream_send(stream_id, b\"data\", false), Ok(4));\n    }\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server receives the 9 streams\n    for i in 0..9 {\n        let stream_id = (50 + i) * 4;\n        pipe.server.stream_recv(stream_id, &mut buf).ok();\n    }\n\n    // Check available capacity from client perspective\n    let available_before = pipe.client.streams.peer_streams_left_bidi();\n    assert_eq!(available_before, 1, \"Should have 1 stream slot available\");\n\n    // Now complete 2 of the 9 active streams (bidirectionally)\n    let stream_1 = 50 * 4;\n    let stream_2 = 51 * 4;\n\n    // Client sends FIN\n    assert_eq!(pipe.client.stream_send(stream_1, b\"\", true), Ok(0));\n    assert_eq!(pipe.client.stream_send(stream_2, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server receives FIN and responds with FIN\n    pipe.server.stream_recv(stream_1, &mut buf).ok();\n    pipe.server.stream_recv(stream_2, &mut buf).ok();\n    assert_eq!(pipe.server.stream_send(stream_1, b\"resp\", true), Ok(4));\n    assert_eq!(pipe.server.stream_send(stream_2, b\"resp\", true), Ok(4));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client receives responses to complete the streams\n    pipe.client.stream_recv(stream_1, &mut buf).ok();\n    pipe.client.stream_recv(stream_2, &mut buf).ok();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Verify streams were collected on server side\n    assert_eq!(\n        pipe.server.streams.max_streams_bidi_next(),\n        62,\n        \"Server should have incremented next by 2\"\n    );\n\n    // Check the threshold logic with current fix (initial/2 = 5)\n    // available = max - peer_opened = 60 - 59 = 1\n    // Check: (62 != 60) AND (1 <= 5) → TRUE, should send MAX_STREAMS\n\n    // Verify MAX_STREAMS was sent by checking if client's available increased\n    let available_after = pipe.client.streams.peer_streams_left_bidi();\n    assert!(\n        available_after == 3,\n        \"After completing 2 streams, client should have capacity for at 3 streams \\\n         (1 original + 2 reclaimed). Got {} available. This indicates MAX_STREAMS was sent.\",\n        available_after\n    );\n}\n\n#[rstest]\n/// Tests that the stream's fin flag is properly flushed even if there's no\n/// data in the buffer, and that the buffer becomes readable on the other\n/// side.\nfn stream_zero_length_fin(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(\n        pipe.client.stream_send(0, b\"aaaaaaaaaaaaaaa\", false),\n        Ok(15)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert!(r.next().is_none());\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client sends zero-length frame.\n    assert_eq!(pipe.client.stream_send(0, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Stream should be readable on the server after receiving empty fin.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert!(r.next().is_none());\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client sends zero-length frame (again).\n    assert_eq!(pipe.client.stream_send(0, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Stream should _not_ be readable on the server after receiving empty\n    // fin, because it was already finished.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n}\n\n#[rstest]\n/// Tests that the stream's fin flag is properly flushed even if there's no\n/// data in the buffer, that the buffer becomes readable on the other\n/// side and stays readable even if the stream is fin'd locally.\nfn stream_zero_length_fin_deferred_collection(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(\n        pipe.client.stream_send(0, b\"aaaaaaaaaaaaaaa\", false),\n        Ok(15)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert!(r.next().is_none());\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client sends zero-length frame.\n    assert_eq!(pipe.client.stream_send(0, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends zero-length frame.\n    assert_eq!(pipe.server.stream_send(0, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Stream should be readable on the server after receiving empty fin.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert!(r.next().is_none());\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client sends zero-length frame (again).\n    assert_eq!(pipe.client.stream_send(0, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Stream should _not_ be readable on the server after receiving empty\n    // fin, because it was already finished.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), None);\n\n    // Stream _is_readable on the client side.\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(0));\n\n    stream_recv_discard(&mut pipe.client, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Stream is completed and _is not_ readable.\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), None);\n}\n\n#[rstest]\n/// Tests that the stream gets created with stream_send() even if there's\n/// no data in the buffer and the fin flag is not set.\nfn stream_zero_length_non_fin(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"\", false), Ok(0));\n\n    // The stream now should have been created.\n    assert_eq!(pipe.client.streams.len(), 1);\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Sending an empty non-fin should not change any stream state on the\n    // other side.\n    let mut r = pipe.server.readable();\n    assert!(r.next().is_none());\n}\n\n#[rstest]\n/// Tests that completed streams are garbage collected.\nfn collect_streams(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.streams.len(), 0);\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaa\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(!pipe.client.stream_finished(0));\n    assert!(!pipe.server.stream_finished(0));\n\n    assert_eq!(pipe.client.streams.len(), 1);\n    assert_eq!(pipe.server.streams.len(), 1);\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.stream_send(0, b\"aaaaa\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(!pipe.client.stream_finished(0));\n    assert!(pipe.server.stream_finished(0));\n\n    assert_eq!(pipe.client.streams.len(), 1);\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    stream_recv_discard(&mut pipe.client, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.streams.len(), 0);\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    assert!(pipe.client.stream_finished(0));\n    assert!(pipe.server.stream_finished(0));\n\n    assert_eq!(pipe.client.stream_send(0, b\"\", true), Err(Error::Done));\n\n    let frames = [frame::Frame::Stream {\n        stream_id: 0,\n        data: <RangeBuf>::from(b\"aa\", 0, false),\n    }];\n\n    let pkt_type = Type::Short;\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(39));\n}\n\n#[test]\nfn config_set_cc_algorithm_name() {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n\n    assert_eq!(config.set_cc_algorithm_name(\"reno\"), Ok(()));\n\n    // Unknown name.\n    assert_eq!(\n        config.set_cc_algorithm_name(\"???\"),\n        Err(Error::CongestionControl)\n    );\n}\n\n#[rstest]\nfn peer_cert(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    match pipe.client.peer_cert() {\n        Some(c) => assert_eq!(c.len(), 753),\n\n        None => panic!(\"missing server certificate\"),\n    }\n}\n\n#[rstest]\nfn peer_cert_chain(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert-big.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    match pipe.client.peer_cert_chain() {\n        Some(c) => assert_eq!(c.len(), 5),\n\n        None => panic!(\"missing server certificate chain\"),\n    }\n}\n\n#[rstest]\nfn retry(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    // Client sends initial flight.\n    let (mut len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server sends Retry packet.\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n\n    let odcid = hdr.dcid.clone();\n\n    let mut scid = [0; MAX_CONN_ID_LEN];\n    rand::rand_bytes(&mut scid[..]);\n    let scid = ConnectionId::from_ref(&scid);\n\n    let token = b\"quiche test retry token\";\n\n    len =\n        packet::retry(&hdr.scid, &hdr.dcid, &scid, token, hdr.version, &mut buf)\n            .unwrap();\n\n    // Client receives Retry and sends new Initial.\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    let (len, send_info) = pipe.client.send(&mut buf).unwrap();\n\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n    assert_eq!(&hdr.token.unwrap(), token);\n\n    // Server accepts connection.\n    pipe.server = accept(\n        &scid,\n        Some(&odcid),\n        test_utils::Pipe::server_addr(),\n        send_info.from,\n        &mut config,\n    )\n    .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.server.is_established());\n}\n\n#[rstest]\nfn retry_with_pto(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    // Client sends initial flight.\n    let (mut len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server sends Retry packet.\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n\n    let odcid = hdr.dcid.clone();\n\n    let mut scid = [0; MAX_CONN_ID_LEN];\n    rand::rand_bytes(&mut scid[..]);\n    let scid = ConnectionId::from_ref(&scid);\n\n    let token = b\"quiche test retry token\";\n\n    len =\n        packet::retry(&hdr.scid, &hdr.dcid, &scid, token, hdr.version, &mut buf)\n            .unwrap();\n\n    // Client receives Retry and sends new Initial.\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    let (len, send_info) = pipe.client.send(&mut buf).unwrap();\n\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n    assert_eq!(&hdr.token.unwrap(), token);\n\n    // Server accepts connection.\n    pipe.server = accept(\n        &scid,\n        Some(&odcid),\n        test_utils::Pipe::server_addr(),\n        send_info.from,\n        &mut config,\n    )\n    .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    // Wait for the client's PTO so it will try to send an Initial again.\n    let timer = pipe.client.timeout().unwrap();\n    std::thread::sleep(timer + Duration::from_millis(1));\n    pipe.client.on_timeout();\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.server.is_established());\n}\n\n#[rstest]\nfn retry_missing_original_destination_connection_id(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    // Client sends initial flight.\n    let (mut len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server sends Retry packet.\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n\n    let mut scid = [0; MAX_CONN_ID_LEN];\n    rand::rand_bytes(&mut scid[..]);\n    let scid = ConnectionId::from_ref(&scid);\n\n    let token = b\"quiche test retry token\";\n\n    len =\n        packet::retry(&hdr.scid, &hdr.dcid, &scid, token, hdr.version, &mut buf)\n            .unwrap();\n\n    // Client receives Retry and sends new Initial.\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server accepts connection and send first flight. But original\n    // destination connection ID is ignored.\n    let from = \"127.0.0.1:1234\".parse().unwrap();\n    pipe.server = accept(\n        &scid,\n        None,\n        test_utils::Pipe::server_addr(),\n        from,\n        &mut config,\n    )\n    .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    assert_eq!(\n        test_utils::process_flight(&mut pipe.client, flight),\n        Err(Error::InvalidTransportParam)\n    );\n}\n\n#[rstest]\nfn retry_invalid_original_destination_connection_id(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    // Client sends initial flight.\n    let (mut len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server sends Retry packet.\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n\n    let mut scid = [0; MAX_CONN_ID_LEN];\n    rand::rand_bytes(&mut scid[..]);\n    let scid = ConnectionId::from_ref(&scid);\n\n    let token = b\"quiche test retry token\";\n\n    len =\n        packet::retry(&hdr.scid, &hdr.dcid, &scid, token, hdr.version, &mut buf)\n            .unwrap();\n\n    // Client receives Retry and sends new Initial.\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server accepts connection and send first flight. But original\n    // destination connection ID is invalid.\n    let from = \"127.0.0.1:1234\".parse().unwrap();\n    let odcid = ConnectionId::from_ref(b\"bogus value\");\n    pipe.server = accept(\n        &scid,\n        Some(&odcid),\n        test_utils::Pipe::server_addr(),\n        from,\n        &mut config,\n    )\n    .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    assert_eq!(\n        test_utils::process_flight(&mut pipe.client, flight),\n        Err(Error::InvalidTransportParam)\n    );\n}\n\n#[rstest]\nfn retry_separate_source_connection_id(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    // Client sends initial flight.\n    let (mut len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server sends Retry packet.\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n\n    let odcid = hdr.dcid.clone();\n    let (retry_scid, _) = test_utils::create_cid_and_reset_token(MAX_CONN_ID_LEN);\n    let token = b\"quiche test retry token\";\n\n    len = packet::retry(\n        &hdr.scid,\n        &hdr.dcid,\n        &retry_scid,\n        token,\n        hdr.version,\n        &mut buf,\n    )\n    .unwrap();\n\n    // Client receives Retry and sends new Initial.\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    let (len, send_info) = pipe.client.send(&mut buf).unwrap();\n\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n    assert_eq!(&hdr.token.unwrap(), token);\n\n    // Server accepts connection.\n    let (scid, _) = test_utils::create_cid_and_reset_token(MAX_CONN_ID_LEN);\n    let retry_cids = RetryConnectionIds {\n        original_destination_cid: &odcid,\n        retry_source_cid: &retry_scid,\n    };\n\n    pipe.server = accept_with_retry(\n        &scid,\n        retry_cids,\n        test_utils::Pipe::server_addr(),\n        send_info.from,\n        &mut config,\n    )\n    .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.server.is_established());\n}\n\n#[rstest]\nfn retry_invalid_source_connection_id(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    // Client sends initial flight.\n    let (mut len, _) = pipe.client.send(&mut buf).unwrap();\n\n    // Server sends Retry packet.\n    let hdr = Header::from_slice(&mut buf[..len], MAX_CONN_ID_LEN).unwrap();\n\n    let odcid = hdr.dcid.clone();\n    let (scid, _) = test_utils::create_cid_and_reset_token(MAX_CONN_ID_LEN);\n    let token = b\"quiche test retry token\";\n\n    len =\n        packet::retry(&hdr.scid, &hdr.dcid, &scid, token, hdr.version, &mut buf)\n            .unwrap();\n\n    // Client receives Retry and sends new Initial.\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    let (len, send_info) = pipe.client.send(&mut buf).unwrap();\n\n    // Server accepts connection and send first flight. But retry source\n    // connection ID is invalid.\n    let retry_scid = ConnectionId::from_ref(b\"bogus value\");\n    let retry_cids = RetryConnectionIds {\n        original_destination_cid: &odcid,\n        retry_source_cid: &retry_scid,\n    };\n\n    pipe.server = accept_with_retry(\n        &scid,\n        retry_cids,\n        test_utils::Pipe::server_addr(),\n        send_info.from,\n        &mut config,\n    )\n    .unwrap();\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    assert_eq!(\n        test_utils::process_flight(&mut pipe.client, flight),\n        Err(Error::InvalidTransportParam)\n    );\n}\n\n#[rstest]\n/// Tests that a zero-length NEW_TOKEN frame is detected as an error.\nfn zero_length_new_token(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = vec![frame::Frame::NewToken { token: vec![] }];\n\n    let pkt_type = Type::Short;\n\n    let written =\n        test_utils::encode_pkt(&mut pipe.server, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    assert_eq!(\n        pipe.client_recv(&mut buf[..written]),\n        Err(Error::InvalidFrame)\n    );\n}\n\n#[rstest]\n/// Tests that a NEW_TOKEN frame sent by client is detected as an error.\nfn client_sent_new_token(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let frames = vec![frame::Frame::NewToken {\n        token: vec![1, 2, 3],\n    }];\n\n    let pkt_type = Type::Short;\n\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    assert_eq!(\n        pipe.server_recv(&mut buf[..written]),\n        Err(Error::InvalidPacket)\n    );\n}\n\nfn check_send(_: &mut impl Send) {}\n\n#[rstest]\nfn config_must_be_send(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    check_send(&mut config);\n}\n\n#[rstest]\nfn connection_must_be_send(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    check_send(&mut pipe.client);\n}\n\nfn check_sync(_: &mut impl Sync) {}\n\n#[rstest]\nfn config_must_be_sync(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    check_sync(&mut config);\n}\n\n#[rstest]\nfn connection_must_be_sync(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    check_sync(&mut pipe.client);\n}\n\n#[rstest]\nfn data_blocked(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaaaaaaa\", false), Ok(10));\n    assert_eq!(pipe.client.blocked_limit, None);\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"aaaaaaaaaa\", false), Ok(10));\n    assert_eq!(pipe.client.blocked_limit, None);\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(8, b\"aaaaaaaaaaa\", false), Ok(10));\n    assert_eq!(pipe.client.blocked_limit, Some(30));\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(pipe.client.blocked_limit, None);\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    assert_eq!(iter.next(), Some(&frame::Frame::DataBlocked { limit: 30 }));\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::Stream {\n            stream_id: 8,\n            data: <RangeBuf>::from(b\"aaaaaaaaaa\", 0, false),\n        })\n    );\n\n    assert_eq!(iter.next(), None);\n}\n\n#[rstest]\nfn stream_data_blocked(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n\n    assert_eq!(pipe.client.stream_send(0, b\"aaaaaa\", false), Ok(5));\n    assert_eq!(pipe.client.streams.blocked().len(), 1);\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::StreamDataBlocked {\n            stream_id: 0,\n            limit: 15,\n        })\n    );\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"aaaaaaaaaaaaaaa\", 0, false),\n        })\n    );\n\n    assert_eq!(iter.next(), None);\n\n    // Send from another stream, make sure we don't send STREAM_DATA_BLOCKED\n    // again.\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Ok(1));\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"a\", 0, false),\n        })\n    );\n\n    assert_eq!(iter.next(), None);\n\n    // Send again from blocked stream and make sure it is not marked as\n    // blocked again.\n    assert_eq!(\n        pipe.client.stream_send(0, b\"aaaaaa\", false),\n        Err(Error::Done)\n    );\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n    assert_eq!(pipe.client.send(&mut buf), Err(Error::Done));\n}\n\n#[rstest]\nfn stream_data_blocked_unblocked_flow_control(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(\n        pipe.client.stream_send(0, b\"aaaaaaaaaaaaaaah\", false),\n        Ok(15)\n    );\n    assert_eq!(pipe.client.streams.blocked().len(), 1);\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n\n    // Send again on blocked stream. It's blocked at the same offset as\n    // previously, so it should not be marked as blocked again.\n    assert_eq!(pipe.client.stream_send(0, b\"h\", false), Err(Error::Done));\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n\n    // No matter how many times we try to write stream data tried, no\n    // packets containing STREAM_BLOCKED should be emitted.\n    assert_eq!(pipe.client.stream_send(0, b\"h\", false), Err(Error::Done));\n    assert_eq!(pipe.client.send(&mut buf), Err(Error::Done));\n\n    assert_eq!(pipe.client.stream_send(0, b\"h\", false), Err(Error::Done));\n    assert_eq!(pipe.client.send(&mut buf), Err(Error::Done));\n\n    assert_eq!(pipe.client.stream_send(0, b\"h\", false), Err(Error::Done));\n    assert_eq!(pipe.client.send(&mut buf), Err(Error::Done));\n\n    // Now read some data at the server to release flow control.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), None);\n\n    if discard {\n        pipe.server.stream_discard(0, 10).unwrap();\n    } else {\n        let mut b = [0; 10];\n        assert_eq!(pipe.server.stream_recv(0, &mut b), Ok((10, false)));\n        assert_eq!(&b[..10], b\"aaaaaaaaaa\");\n    }\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"hhhhhhhhhh!\", false), Ok(10));\n    assert_eq!(pipe.client.streams.blocked().len(), 1);\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::StreamDataBlocked {\n            stream_id: 0,\n            limit: 25,\n        })\n    );\n\n    // don't care about remaining received frames\n\n    assert_eq!(pipe.client.stream_send(0, b\"!\", false), Err(Error::Done));\n    assert_eq!(pipe.client.streams.blocked().len(), 0);\n    assert_eq!(pipe.client.send(&mut buf), Err(Error::Done));\n}\n\n#[rstest]\nfn app_limited_true(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(50000);\n    config.set_initial_max_stream_data_bidi_remote(50000);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends stream data smaller than cwnd.\n    let send_buf = [0; 10000];\n    assert_eq!(pipe.server.stream_send(0, &send_buf, false), Ok(10000));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // app_limited should be true because we send less than cwnd.\n    assert!(pipe\n        .server\n        .paths\n        .get_active()\n        .expect(\"no active\")\n        .recovery\n        .app_limited());\n}\n\n#[rstest]\nfn app_limited_false(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(50000);\n    config.set_initial_max_stream_data_bidi_remote(50000);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends stream data bigger than cwnd.\n    let send_buf1 = [0; 20000];\n    assert_eq!(pipe.server.stream_send(0, &send_buf1, false), Ok(12000));\n\n    test_utils::emit_flight(&mut pipe.server).ok();\n\n    // We can't create a new packet header because there is no room by cwnd.\n    // app_limited should be false because we can't send more by cwnd.\n    assert!(!pipe\n        .server\n        .paths\n        .get_active()\n        .expect(\"no active\")\n        .recovery\n        .app_limited());\n}\n\n#[rstest]\nfn tx_cap_factor(#[values(true, false)] discard: bool) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(12000);\n    config.set_initial_max_stream_data_bidi_remote(12000);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    config.set_send_capacity_factor(2.0);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", true), Ok(1));\n    assert_eq!(pipe.client.stream_send(4, b\"a\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends stream data bigger than cwnd.\n    let send_buf = [0; 50000];\n    assert_eq!(pipe.server.stream_send(0, &send_buf, false), Ok(12000));\n    assert_eq!(pipe.server.stream_send(4, &send_buf, false), Ok(12000));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.client.readable();\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 0),\n        Ok((12000, false))\n    );\n\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(\n        stream_recv_discard(&mut pipe.client, discard, 4),\n        Ok((12000, false))\n    );\n\n    assert_eq!(r.next(), None);\n}\n\n#[rstest]\nfn client_rst_stream_while_bytes_in_flight(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(false, true)] use_stop_sending: bool,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(120000);\n    config.set_initial_max_stream_data_bidi_remote(120000);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", true), Ok(1));\n    // Send FIN if we want to exercise the case of the client sending\n    // STOP_SENDING instead of RESET_STREAM.\n    assert_eq!(pipe.client.stream_send(4, b\"a\", use_stop_sending), Ok(1));\n    assert_eq!(pipe.client.stream_send(8, b\"a\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    stream_recv_discard(&mut pipe.server, discard, 4).unwrap();\n    stream_recv_discard(&mut pipe.server, discard, 8).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends stream data bigger than cwnd.\n    let send_buf = [0; 50000];\n    assert_eq!(\n        pipe.server.stream_send(4, &send_buf, false),\n        if cc_algorithm_name == \"cubic\" {\n            Ok(12000)\n        } else if cfg!(feature = \"openssl\") {\n            Ok(13964)\n        } else {\n            Ok(13878)\n        }\n    );\n    let server_flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // And generate a stop sending or reset at the client.\n    assert_eq!(pipe.client.stream_shutdown(4, Shutdown::Read, 42), Ok(()));\n    if !use_stop_sending {\n        // Client did not send a FIN on stream 4, shutdown both sides to send a\n        // RESET_STREAM.\n        assert_eq!(pipe.client.stream_shutdown(4, Shutdown::Write, 42), Ok(()));\n    }\n    let client_flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    test_utils::process_flight(&mut pipe.server, client_flight).unwrap();\n    test_utils::process_flight(&mut pipe.client, server_flight).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // tx_buffered goes down to 0 after the reset and acks are\n    // processed.  A full cwnd's worth of packets can be sent.\n    let expected_cwnd = match cc_algorithm_name {\n        \"bbr2\" | \"bbr2_gcongestion\" =>\n            if cfg!(feature = \"openssl\") {\n                27928\n            } else {\n                27756\n            },\n        _ => 24000,\n    };\n\n    assert_eq!(pipe.server.tx_buffered, 0);\n    assert_eq!(\n        pipe.server.stream_send(8, &send_buf, false),\n        Ok(expected_cwnd)\n    );\n    assert_eq!(pipe.server.tx_buffered, expected_cwnd);\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .cwnd(),\n        expected_cwnd\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(pipe.server.tx_buffered_state, TxBufferTrackingState::Ok);\n}\n\n#[rstest]\nfn client_rst_stream_while_bytes_in_flight_with_packet_loss(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(120000);\n    config.set_initial_max_stream_data_bidi_remote(120000);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", true), Ok(1));\n    assert_eq!(pipe.client.stream_send(4, b\"a\", true), Ok(1));\n    assert_eq!(pipe.client.stream_send(8, b\"a\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    stream_recv_discard(&mut pipe.server, discard, 4).unwrap();\n    stream_recv_discard(&mut pipe.server, discard, 8).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends stream data bigger than cwnd.\n    let send_buf = [0; 50000];\n    assert_eq!(\n        pipe.server.stream_send(4, &send_buf, false),\n        if cc_algorithm_name == \"cubic\" {\n            Ok(12000)\n        } else if cfg!(feature = \"openssl\") {\n            Ok(13964)\n        } else {\n            Ok(13878)\n        }\n    );\n    let mut server_flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // And generate a STOP_SENDING at the client.\n    assert_eq!(pipe.client.stream_shutdown(4, Shutdown::Read, 42), Ok(()));\n    let client_flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    // Lose the first packet of the server flight.\n    server_flight.remove(0);\n\n    test_utils::process_flight(&mut pipe.server, client_flight).unwrap();\n    test_utils::process_flight(&mut pipe.client, server_flight).unwrap();\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // tx_buffered goes down to 0 after the reset and acks are\n    // processed.  A full cwnd's worth of packets can be sent.\n    let expected_cwnd = match cc_algorithm_name {\n        \"bbr2\" | \"bbr2_gcongestion\" =>\n            if cfg!(feature = \"openssl\") {\n                26728\n            } else {\n                26556\n            },\n        _ => 8400,\n    };\n\n    assert_eq!(pipe.server.tx_buffered, 0);\n\n    let send_result = pipe.server.stream_send(8, &send_buf, false).unwrap();\n    if cc_algorithm_name != \"cubic\" {\n        assert_eq!(send_result, expected_cwnd);\n    } else {\n        // cubic adjusts the congestion window downwards due to the\n        // lost packet.  The exact send size varies.\n        assert!((15000..17000).contains(&send_result));\n    }\n    assert_eq!(pipe.server.tx_buffered, send_result);\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .cwnd(),\n        expected_cwnd\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(pipe.server.tx_buffered_state, TxBufferTrackingState::Ok);\n}\n\n#[rstest]\nfn sends_ack_only_pkt_when_full_cwnd_and_ack_elicited(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(50000);\n    config.set_initial_max_stream_data_bidi_remote(50000);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data bigger than cwnd (it will never arrive to the\n    // server).\n    let send_buf1 = [0; 20000];\n    assert_eq!(\n        pipe.client.stream_send(0, &send_buf1, false),\n        if cc_algorithm_name == \"cubic\" {\n            Ok(12000)\n        } else if cfg!(feature = \"openssl\") {\n            Ok(12345)\n        } else {\n            Ok(12299)\n        }\n    );\n\n    test_utils::emit_flight(&mut pipe.client).ok();\n\n    // Server sends some stream data that will need ACKs.\n    assert_eq!(\n        pipe.server.stream_send(1, &send_buf1[..500], false),\n        Ok(500)\n    );\n\n    test_utils::process_flight(\n        &mut pipe.client,\n        test_utils::emit_flight(&mut pipe.server).unwrap(),\n    )\n    .unwrap();\n\n    let mut buf = [0; 2000];\n\n    let ret = pipe.client.send(&mut buf);\n\n    assert_eq!(pipe.client.tx_cap, 0);\n\n    assert!(matches!(ret, Ok((_, _))), \"the client should at least send one packet to acknowledge the newly received data\");\n\n    let (sent, _) = ret.unwrap();\n\n    assert_ne!(sent, 0, \"the client should at least send a pure ACK packet\");\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..sent]).unwrap();\n    assert_eq!(1, frames.len());\n    assert!(\n        matches!(frames[0], frame::Frame::ACK { .. }),\n        \"the packet sent by the client must be an ACK only packet\"\n    );\n}\n\n/// Like sends_ack_only_pkt_when_full_cwnd_and_ack_elicited, but when\n/// ack_eliciting is explicitly requested.\n#[rstest]\nfn sends_ack_only_pkt_when_full_cwnd_and_ack_elicited_despite_max_unacknowledging(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(50000);\n    config.set_initial_max_stream_data_bidi_remote(50000);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data bigger than cwnd (it will never arrive to the\n    // server). This exhausts the congestion window.\n    let send_buf1 = [0; 20000];\n    assert_eq!(\n        pipe.client.stream_send(0, &send_buf1, false),\n        if cc_algorithm_name == \"cubic\" {\n            Ok(12000)\n        } else if cfg!(feature = \"openssl\") {\n            Ok(12345)\n        } else {\n            Ok(12299)\n        }\n    );\n\n    test_utils::emit_flight(&mut pipe.client).ok();\n\n    // Client gets PING frames from server, which elicit ACK\n    let mut buf = [0; 2000];\n    for _ in 0..recovery::MAX_OUTSTANDING_NON_ACK_ELICITING {\n        let written = test_utils::encode_pkt(\n            &mut pipe.server,\n            Type::Short,\n            &[frame::Frame::Ping { mtu_probe: None }],\n            &mut buf,\n        )\n        .unwrap();\n\n        pipe.client_recv(&mut buf[..written])\n            .expect(\"client recv ping\");\n\n        // Client acknowledges despite a full congestion window\n        let ret = pipe.client.send(&mut buf);\n\n        assert!(matches!(ret, Ok((_, _))), \"the client should at least send one packet to acknowledge the newly received data\");\n\n        let (sent, _) = ret.unwrap();\n\n        assert_ne!(sent, 0, \"the client should at least send a pure ACK packet\");\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.server, &mut buf[..sent]).unwrap();\n\n        assert_eq!(1, frames.len());\n\n        assert!(\n            matches!(frames[0], frame::Frame::ACK { .. }),\n            \"the packet sent by the client must be an ACK only packet\"\n        );\n    }\n\n    // The client shouldn't need to send any more packets after the ACK only\n    // packet it just sent.\n    assert_eq!(\n        pipe.client.send(&mut buf),\n        Err(Error::Done),\n        \"nothing for client to send after ACK-only packet\"\n    );\n}\n\n#[rstest]\nfn validate_peer_sent_ack_range(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config.set_cc_algorithm_name(cc_algorithm_name).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(30);\n    config.set_initial_max_stream_data_bidi_remote(30);\n    config.set_initial_max_stream_data_uni(30);\n    config.set_initial_max_streams_bidi(10);\n    config.set_initial_max_streams_uni(10);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    pipe.handshake().unwrap();\n\n    let mut buf = [0; 2000];\n    let epoch = packet::Epoch::Application;\n    let pkt_type = Type::Short;\n\n    // Elicit client to send an ACK to the server\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    let expected_max_active_pkt_sent = 3;\n    let recovery = &pipe.server.paths.get_active().unwrap().recovery;\n    assert_eq!(\n        recovery.largest_sent_pkt_num_on_path(epoch).unwrap(),\n        expected_max_active_pkt_sent\n    );\n    assert_eq!(recovery.get_largest_acked_on_epoch(epoch).unwrap(), 3);\n    assert_eq!(recovery.sent_packets_len(epoch), 0);\n    // Verify largest sent on the connection\n    assert_eq!(\n        pipe.server.pkt_num_spaces[epoch]\n            .largest_tx_pkt_num\n            .unwrap(),\n        expected_max_active_pkt_sent\n    );\n\n    // Elicit server to send a packet(ACK) by sending it a packet first. This\n    // will result in Server sent packets that require an ACK\n    let frames = [frame::Frame::Stream {\n        stream_id: 0,\n        data: <RangeBuf>::from(b\"aa\", 0, false),\n    }];\n    pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n    let recovery = &pipe.server.paths.get_active().unwrap().recovery;\n    assert_eq!(recovery.largest_sent_pkt_num_on_path(epoch).unwrap(), 4);\n    assert_eq!(recovery.get_largest_acked_on_epoch(epoch).unwrap(), 3);\n    assert_eq!(recovery.sent_packets_len(epoch), 1);\n\n    // Send an invalid ACK range to the server and expect server error\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(0..10);\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n            .unwrap_err(),\n        Error::InvalidAckRange\n    );\n\n    // https://www.rfc-editor.org/rfc/rfc9000#section-13.1\n    // An endpoint SHOULD treat receipt of an acknowledgment for a packet it\n    // did not send as a connection error of type PROTOCOL_VIOLATION\n    assert_eq!(\n        pipe.server.local_error.unwrap().error_code,\n        WireErrorCode::ProtocolViolation as u64\n    );\n}\n\n#[rstest]\nfn validate_peer_sent_ack_range_for_multi_path(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n    let probed_pid =\n        pipe.client.probe_path(client_addr_2, server_addr).unwrap() as usize;\n\n    // Exchange path challenge/response and establish the second path\n    pipe.advance().unwrap();\n    assert_eq!(pipe.server.paths.len(), 2);\n\n    let mut buf = [0; 2000];\n    let epoch = packet::Epoch::Application;\n    let pkt_type = Type::Short;\n\n    // active path\n    let expected_max_active_pkt_sent = 7;\n    let active_path = &pipe.server.paths.get_mut(0).unwrap();\n    let p1_recovery = &active_path.recovery;\n    assert_eq!(\n        p1_recovery.largest_sent_pkt_num_on_path(epoch).unwrap(),\n        expected_max_active_pkt_sent\n    );\n    assert_eq!(p1_recovery.get_largest_acked_on_epoch(epoch).unwrap(), 6);\n    assert_eq!(p1_recovery.sent_packets_len(epoch), 1);\n\n    // non-active path\n    let expected_max_second_pkt_sent = 5;\n    let second_path = &pipe.server.paths.get_mut(probed_pid).unwrap();\n    let p2_recovery = &second_path.recovery;\n    assert_eq!(\n        p2_recovery.largest_sent_pkt_num_on_path(epoch).unwrap(),\n        expected_max_second_pkt_sent\n    );\n    assert_eq!(p2_recovery.get_largest_acked_on_epoch(epoch).unwrap(), 5);\n    assert_eq!(p2_recovery.sent_packets_len(epoch), 0);\n\n    // Verify largest sent on the connection is the max of the two paths\n    let global_max_sent = pipe.server.pkt_num_spaces[epoch]\n        .largest_tx_pkt_num\n        .unwrap();\n    assert_eq!(\n        global_max_sent,\n        expected_max_active_pkt_sent.max(expected_max_second_pkt_sent)\n    );\n\n    // Send a valid ACK range based on the global max.  Range is not inclusive\n    // so +1 to include global_max_sent pkt\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(0..global_max_sent + 1);\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n    pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // active path\n    let active_path = &pipe.server.paths.get_mut(0).unwrap();\n    assert!(active_path.active());\n    let p1_recovery = &active_path.recovery;\n    assert_eq!(p1_recovery.largest_sent_pkt_num_on_path(epoch).unwrap(), 7);\n    assert_eq!(p1_recovery.get_largest_acked_on_epoch(epoch).unwrap(), 7);\n    assert_eq!(p1_recovery.sent_packets_len(epoch), 0);\n\n    // non-active path\n    let second_path = &pipe.server.paths.get_mut(probed_pid).unwrap();\n    let p2_recovery = &second_path.recovery;\n    assert_eq!(p2_recovery.largest_sent_pkt_num_on_path(epoch).unwrap(), 5);\n    assert_eq!(p2_recovery.get_largest_acked_on_epoch(epoch).unwrap(), 5);\n    assert_eq!(p2_recovery.sent_packets_len(epoch), 0);\n\n    // Send a large invalid ACK range to the server. Range is not inclusive so\n    // +2 to include a packet greater than global_max_sent pkt\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(0..global_max_sent + 2);\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n            .unwrap_err(),\n        Error::InvalidAckRange\n    );\n\n    // https://www.rfc-editor.org/rfc/rfc9000#section-13.1\n    // An endpoint SHOULD treat receipt of an acknowledgment for a packet it\n    // did not send as a connection error of type PROTOCOL_VIOLATION\n    assert_eq!(\n        pipe.server.local_error.unwrap().error_code,\n        WireErrorCode::ProtocolViolation as u64\n    );\n}\n\n// Both Client and Server should skip pn to prevent an optimistic ack attack\n#[rstest]\nfn optimistic_ack_mitigation_via_skip_pn(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config.set_cc_algorithm_name(cc_algorithm_name).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(100_0000);\n    config.set_initial_max_stream_data_bidi_local(100_000);\n    config.set_initial_max_stream_data_bidi_remote(100_000);\n    config.set_initial_max_streams_bidi(10);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    pipe.handshake().unwrap();\n\n    let mut server_skip_pn = None;\n    let mut client_skip_pn = None;\n    let buf = [42; 100];\n    while server_skip_pn.is_none() || client_skip_pn.is_none() {\n        // Server should send some data\n        assert_eq!(pipe.server.stream_send(1, &buf, false).unwrap(), 100);\n\n        // Advance server tx and client rx\n        let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n        test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n        // Check if server skipped a pn\n        let server_num_manager = &pipe.server.pkt_num_manager;\n        if let Some(skip_pn) = server_num_manager.skip_pn() {\n            server_skip_pn = Some(skip_pn);\n        }\n\n        // Advance client tx and server rx\n        let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n        test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n        // Check if client skipped a pn\n        let client_num_manager = &pipe.client.pkt_num_manager;\n        if let Some(skip_pn) = client_num_manager.skip_pn() {\n            client_skip_pn = Some(skip_pn);\n        }\n    }\n\n    // Confirm both server and client skip pn\n    assert!(server_skip_pn.is_some() && client_skip_pn.is_some());\n}\n\n// Connection should validate skip pn to prevent an optimistic ack attack\n#[rstest]\nfn prevent_optimistic_ack(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config.set_cc_algorithm_name(cc_algorithm_name).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(100_0000);\n    config.set_initial_max_stream_data_bidi_local(100_000);\n    config.set_initial_max_stream_data_bidi_remote(100_000);\n    config.set_initial_max_streams_bidi(10);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    pipe.handshake().unwrap();\n\n    let mut server_skip_pn = None;\n    let buf = [42; 100];\n    while server_skip_pn.is_none() {\n        // Server should send some data\n        pipe.server.stream_send(1, &buf, false).unwrap();\n\n        // Advance server tx and client rx\n        let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n        test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n        // Check if server skipped a pn\n        if let Some(skip_pn) = pipe.server.pkt_num_manager.skip_pn() {\n            server_skip_pn = Some(skip_pn);\n        }\n    }\n\n    let pkt_type = Type::Short;\n    let mut buf = [0; 2000];\n\n    // Construct an ACK with the skip_pn to send to the server\n    let skip_pn = server_skip_pn.unwrap();\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(skip_pn..skip_pn + 1);\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n\n    // Receiving an ACK for the skip_pn results in an error\n    assert_eq!(\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n            .err()\n            .unwrap(),\n        Error::OptimisticAckDetected\n    );\n\n    // https://www.rfc-editor.org/rfc/rfc9000#section-13.1\n    // An endpoint SHOULD treat receipt of an acknowledgment for a packet it\n    // did not send as a connection error of type PROTOCOL_VIOLATION\n    assert_eq!(\n        pipe.server.local_error.unwrap().error_code,\n        WireErrorCode::ProtocolViolation as u64\n    );\n}\n\n#[rstest]\nfn app_limited_false_no_frame(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(50000);\n    config.set_initial_max_stream_data_bidi_remote(50000);\n    config.set_max_recv_udp_payload_size(1405);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends stream data bigger than cwnd.\n    let send_buf1 = [0; 20000];\n    assert_eq!(pipe.server.stream_send(0, &send_buf1, false), Ok(12000));\n\n    test_utils::emit_flight(&mut pipe.server).ok();\n\n    // We can't create a new packet header because there is no room by cwnd.\n    // app_limited should be false because we can't send more by cwnd.\n    assert!(!pipe\n        .server\n        .paths\n        .get_active()\n        .expect(\"no active\")\n        .recovery\n        .app_limited());\n}\n\n#[rstest]\nfn app_limited_false_no_header(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(50000);\n    config.set_initial_max_stream_data_bidi_remote(50000);\n    config.set_max_recv_udp_payload_size(1406);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server sends stream data bigger than cwnd.\n    let send_buf1 = [0; 20000];\n    assert_eq!(pipe.server.stream_send(0, &send_buf1, false), Ok(12000));\n\n    test_utils::emit_flight(&mut pipe.server).ok();\n\n    // We can't create a new frame because there is no room by cwnd.\n    // app_limited should be false because we can't send more by cwnd.\n    assert!(!pipe\n        .server\n        .paths\n        .get_active()\n        .expect(\"no active\")\n        .recovery\n        .app_limited());\n}\n\n#[rstest]\nfn app_limited_not_changed_on_no_new_frames(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(50000);\n    config.set_initial_max_stream_data_bidi_local(50000);\n    config.set_initial_max_stream_data_bidi_remote(50000);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client's app_limited is true because its bytes-in-flight\n    // is much smaller than the current cwnd.\n    assert!(pipe\n        .client\n        .paths\n        .get_active()\n        .expect(\"no active\")\n        .recovery\n        .app_limited());\n\n    // Client has no new frames to send - returns Done.\n    assert_eq!(test_utils::emit_flight(&mut pipe.client), Err(Error::Done));\n\n    // Client's app_limited should remain the same.\n    assert!(pipe\n        .client\n        .paths\n        .get_active()\n        .expect(\"no active\")\n        .recovery\n        .app_limited());\n}\n\n#[rstest]\nfn limit_ack_ranges(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let epoch = packet::Epoch::Application;\n\n    assert_eq!(pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.len(), 0);\n\n    let frames = [\n        frame::Frame::Ping { mtu_probe: None },\n        frame::Frame::Padding { len: 3 },\n    ];\n\n    let pkt_type = Type::Short;\n\n    let mut last_packet_sent = 0;\n\n    for _ in 0..512 {\n        let recv_count = pipe.server.recv_count;\n\n        last_packet_sent = pipe.client.next_pkt_num;\n\n        pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n            .unwrap();\n\n        assert_eq!(pipe.server.recv_count, recv_count + 1);\n\n        // Skip packet number.\n        pipe.client.next_pkt_num += 1;\n    }\n\n    assert_eq!(\n        pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.len(),\n        MAX_ACK_RANGES\n    );\n\n    assert_eq!(\n        pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.first(),\n        Some(last_packet_sent - ((MAX_ACK_RANGES as u64) - 1) * 2)\n    );\n\n    assert_eq!(\n        pipe.server.pkt_num_spaces[epoch].recv_pkt_need_ack.last(),\n        Some(last_packet_sent)\n    );\n}\n\n#[rstest]\n/// Tests that streams are correctly scheduled based on their priority.\nfn stream_priority(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    // Limit 1-RTT packet size to avoid congestion control interference.\n    const MAX_TEST_PACKET_SIZE: usize = 540;\n\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(1_000_000);\n    config.set_initial_max_stream_data_bidi_local(1_000_000);\n    config.set_initial_max_stream_data_bidi_remote(1_000_000);\n    config.set_initial_max_stream_data_uni(0);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(8, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(12, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(16, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(20, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let out = [b'b'; 500];\n\n    // Server prioritizes streams as follows:\n    //  * Stream 8 and 16 have the same priority but are non-incremental.\n    //  * Stream 4, 12 and 20 have the same priority but 20 is non-incremental and\n    //    4 and 12 are incremental.\n    //  * Stream 0 is on its own.\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.server.stream_priority(0, 255, true), Ok(()));\n    pipe.server.stream_send(0, &out, false).unwrap();\n    pipe.server.stream_send(0, &out, false).unwrap();\n    pipe.server.stream_send(0, &out, false).unwrap();\n\n    stream_recv_discard(&mut pipe.server, discard, 12).unwrap();\n    assert_eq!(pipe.server.stream_priority(12, 42, true), Ok(()));\n    pipe.server.stream_send(12, &out, false).unwrap();\n    pipe.server.stream_send(12, &out, false).unwrap();\n    pipe.server.stream_send(12, &out, false).unwrap();\n\n    stream_recv_discard(&mut pipe.server, discard, 16).unwrap();\n    assert_eq!(pipe.server.stream_priority(16, 10, false), Ok(()));\n    pipe.server.stream_send(16, &out, false).unwrap();\n    pipe.server.stream_send(16, &out, false).unwrap();\n    pipe.server.stream_send(16, &out, false).unwrap();\n\n    stream_recv_discard(&mut pipe.server, discard, 4).unwrap();\n    assert_eq!(pipe.server.stream_priority(4, 42, true), Ok(()));\n    pipe.server.stream_send(4, &out, false).unwrap();\n    pipe.server.stream_send(4, &out, false).unwrap();\n    pipe.server.stream_send(4, &out, false).unwrap();\n\n    stream_recv_discard(&mut pipe.server, discard, 8).unwrap();\n    assert_eq!(pipe.server.stream_priority(8, 10, false), Ok(()));\n    pipe.server.stream_send(8, &out, false).unwrap();\n    pipe.server.stream_send(8, &out, false).unwrap();\n    pipe.server.stream_send(8, &out, false).unwrap();\n\n    stream_recv_discard(&mut pipe.server, discard, 20).unwrap();\n    assert_eq!(pipe.server.stream_priority(20, 42, false), Ok(()));\n    pipe.server.stream_send(20, &out, false).unwrap();\n    pipe.server.stream_send(20, &out, false).unwrap();\n    pipe.server.stream_send(20, &out, false).unwrap();\n\n    // First is stream 8.\n    let mut off = 0;\n\n    for _ in 1..=3 {\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        let stream = frames.first().unwrap();\n\n        assert_eq!(stream, &frame::Frame::Stream {\n            stream_id: 8,\n            data: <RangeBuf>::from(&out, off, false),\n        });\n\n        off = match stream {\n            frame::Frame::Stream { data, .. } => data.max_off(),\n\n            _ => unreachable!(),\n        };\n    }\n\n    // Then is stream 16.\n    let mut off = 0;\n\n    for _ in 1..=3 {\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        let stream = frames.first().unwrap();\n\n        assert_eq!(stream, &frame::Frame::Stream {\n            stream_id: 16,\n            data: <RangeBuf>::from(&out, off, false),\n        });\n\n        off = match stream {\n            frame::Frame::Stream { data, .. } => data.max_off(),\n\n            _ => unreachable!(),\n        };\n    }\n\n    // Then is stream 20.\n    let mut off = 0;\n\n    for _ in 1..=3 {\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        let stream = frames.first().unwrap();\n\n        assert_eq!(stream, &frame::Frame::Stream {\n            stream_id: 20,\n            data: <RangeBuf>::from(&out, off, false),\n        });\n\n        off = match stream {\n            frame::Frame::Stream { data, .. } => data.max_off(),\n\n            _ => unreachable!(),\n        };\n    }\n\n    // Then are stream 12 and 4, with the same priority, incrementally.\n    let mut off = 0;\n\n    for _ in 1..=3 {\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n        assert_eq!(\n            frames.first(),\n            Some(&frame::Frame::Stream {\n                stream_id: 12,\n                data: <RangeBuf>::from(&out, off, false),\n            })\n        );\n\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n        let stream = frames.first().unwrap();\n\n        assert_eq!(stream, &frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(&out, off, false),\n        });\n\n        off = match stream {\n            frame::Frame::Stream { data, .. } => data.max_off(),\n\n            _ => unreachable!(),\n        };\n    }\n\n    // Final is stream 0.\n    let mut off = 0;\n\n    for _ in 1..=3 {\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        let stream = frames.first().unwrap();\n\n        assert_eq!(stream, &frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(&out, off, false),\n        });\n\n        off = match stream {\n            frame::Frame::Stream { data, .. } => data.max_off(),\n\n            _ => unreachable!(),\n        };\n    }\n\n    assert_eq!(pipe.server.send(&mut buf), Err(Error::Done));\n}\n\n#[rstest]\n/// Tests that changing a stream's priority is correctly propagated.\nfn stream_reprioritize(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(0);\n    config.set_initial_max_streams_bidi(5);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(8, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(12, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.server.stream_priority(0, 255, true), Ok(()));\n    pipe.server.stream_send(0, b\"b\", false).unwrap();\n\n    stream_recv_discard(&mut pipe.server, discard, 12).unwrap();\n    assert_eq!(pipe.server.stream_priority(12, 42, true), Ok(()));\n    pipe.server.stream_send(12, b\"b\", false).unwrap();\n\n    stream_recv_discard(&mut pipe.server, discard, 8).unwrap();\n    assert_eq!(pipe.server.stream_priority(8, 10, true), Ok(()));\n    pipe.server.stream_send(8, b\"b\", false).unwrap();\n\n    stream_recv_discard(&mut pipe.server, discard, 4).unwrap();\n    assert_eq!(pipe.server.stream_priority(4, 42, true), Ok(()));\n    pipe.server.stream_send(4, b\"b\", false).unwrap();\n\n    // Stream 0 is re-prioritized!!!\n    assert_eq!(pipe.server.stream_priority(0, 20, true), Ok(()));\n\n    // First is stream 8.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    assert_eq!(\n        frames.first(),\n        Some(&frame::Frame::Stream {\n            stream_id: 8,\n            data: <RangeBuf>::from(b\"b\", 0, false),\n        })\n    );\n\n    // Then is stream 0.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    assert_eq!(\n        frames.first(),\n        Some(&frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(b\"b\", 0, false),\n        })\n    );\n\n    // Then are stream 12 and 4, with the same priority.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    assert_eq!(\n        frames.first(),\n        Some(&frame::Frame::Stream {\n            stream_id: 12,\n            data: <RangeBuf>::from(b\"b\", 0, false),\n        })\n    );\n\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    assert_eq!(\n        frames.first(),\n        Some(&frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"b\", 0, false),\n        })\n    );\n\n    assert_eq!(pipe.server.send(&mut buf), Err(Error::Done));\n}\n\n#[rstest]\n/// Tests that streams and datagrams are correctly scheduled.\nfn stream_datagram_priority(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    // Limit 1-RTT packet size to avoid congestion control interference.\n    const MAX_TEST_PACKET_SIZE: usize = 540;\n\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(1_000_000);\n    config.set_initial_max_stream_data_bidi_local(1_000_000);\n    config.set_initial_max_stream_data_bidi_remote(1_000_000);\n    config.set_initial_max_stream_data_uni(0);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(0);\n    config.enable_dgram(true, 10, 10);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let out = [b'b'; 500];\n\n    // Server prioritizes Stream 0 and 4 with the same urgency with\n    // incremental, meaning the frames should be sent in round-robin\n    // fashion. It also sends DATAGRAMS which are always interleaved with\n    // STREAM frames. So we'll expect a mix of frame types regardless\n    // of the order that the application writes things in.\n\n    stream_recv_discard(&mut pipe.server, discard, 0).unwrap();\n    assert_eq!(pipe.server.stream_priority(0, 255, true), Ok(()));\n    pipe.server.stream_send(0, &out, false).unwrap();\n    pipe.server.stream_send(0, &out, false).unwrap();\n    pipe.server.stream_send(0, &out, false).unwrap();\n\n    assert_eq!(pipe.server.stream_priority(4, 255, true), Ok(()));\n    pipe.server.stream_send(4, &out, false).unwrap();\n    pipe.server.stream_send(4, &out, false).unwrap();\n    pipe.server.stream_send(4, &out, false).unwrap();\n\n    for _ in 1..=6 {\n        assert_eq!(pipe.server.dgram_send(&out), Ok(()));\n    }\n\n    let mut off_0 = 0;\n    let mut off_4 = 0;\n\n    for _ in 1..=3 {\n        // DATAGRAM\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        let mut frame_iter = frames.iter();\n\n        assert_eq!(frame_iter.next().unwrap(), &frame::Frame::Datagram {\n            data: out.into()\n        });\n        assert_eq!(frame_iter.next(), None);\n\n        // STREAM 0\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        let mut frame_iter = frames.iter();\n        let stream = frame_iter.next().unwrap();\n\n        assert_eq!(stream, &frame::Frame::Stream {\n            stream_id: 0,\n            data: <RangeBuf>::from(&out, off_0, false),\n        });\n\n        off_0 = match stream {\n            frame::Frame::Stream { data, .. } => data.max_off(),\n\n            _ => unreachable!(),\n        };\n        assert_eq!(frame_iter.next(), None);\n\n        // DATAGRAM\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        let mut frame_iter = frames.iter();\n\n        assert_eq!(frame_iter.next().unwrap(), &frame::Frame::Datagram {\n            data: out.into()\n        });\n        assert_eq!(frame_iter.next(), None);\n\n        // STREAM 4\n        let (len, _) =\n            pipe.server.send(&mut buf[..MAX_TEST_PACKET_SIZE]).unwrap();\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        let mut frame_iter = frames.iter();\n        let stream = frame_iter.next().unwrap();\n\n        assert_eq!(stream, &frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(&out, off_4, false),\n        });\n\n        off_4 = match stream {\n            frame::Frame::Stream { data, .. } => data.max_off(),\n\n            _ => unreachable!(),\n        };\n        assert_eq!(frame_iter.next(), None);\n    }\n}\n\n#[rstest]\n/// Tests that old data is retransmitted on PTO.\nfn early_retransmit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, b\"a\", false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client sends more stream data, but packet is lost\n    assert_eq!(pipe.client.stream_send(4, b\"b\", false), Ok(1));\n    assert!(pipe.client.send(&mut buf).is_ok());\n\n    // Wait until PTO expires. Since the RTT is very low, wait a bit more.\n    let timer = pipe.client.timeout().unwrap();\n    std::thread::sleep(timer + Duration::from_millis(1));\n\n    pipe.client.on_timeout();\n\n    let epoch = packet::Epoch::Application;\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .loss_probes(epoch),\n        1,\n    );\n\n    // Client retransmits stream data in PTO probe.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .loss_probes(epoch),\n        0,\n    );\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::Stream {\n            stream_id: 4,\n            data: <RangeBuf>::from(b\"b\", 0, false),\n        })\n    );\n    assert_eq!(pipe.client.stats().retrans, 1);\n}\n\n#[rstest]\n/// Tests that MAX_DATA, STREAM_MAX_DATA frames are retransmitted if lost\nfn max_data_frames_retransmit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = test_utils::Pipe::default_config(cc_algorithm_name).unwrap();\n    config.set_initial_max_stream_data_bidi_local(30);\n    config.set_initial_max_stream_data_bidi_remote(30);\n    config.set_initial_max_data(30);\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n    assert_eq!(pipe.client.max_tx_data, 30);\n    assert_eq!(pipe.server.max_rx_data(), 30);\n\n    // Client sends stream data.\n    assert_eq!(pipe.client.stream_send(0, &[42u8; 30], false), Ok(30));\n    // out of flow-control\n    assert_eq!(\n        pipe.client.stream_send(0, &[42u8; 30], false),\n        Err(Error::Done)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.stream_recv(0, &mut buf), Ok((30, false)));\n    // Client's max_tx_data is still the same.\n    assert_eq!(pipe.client.max_tx_data, 30);\n    // So is the servers's max_rx_data, since it hasn't sent a MAX_DATA frame yet\n    assert_eq!(pipe.server.max_rx_data(), 30);\n\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    // Make sure the server actually tried to send MAX_DATA and STREAM_MAX_DATA\n    // frames\n    let mut max_data_seen = false;\n    let mut max_stream_data_seen = false;\n    for frame in frames {\n        match frame {\n            frame::Frame::MaxData { max } => {\n                assert_eq!(max, 75);\n                max_data_seen = true\n            },\n            frame::Frame::MaxStreamData { stream_id, max } => {\n                assert_eq!(stream_id, 0);\n                assert_eq!(max, 60);\n                max_stream_data_seen = true;\n            },\n            _ => (),\n        }\n    }\n    assert!(max_data_seen);\n    assert!(max_stream_data_seen);\n\n    // flow control on client is unchanged\n    assert_eq!(pipe.client.max_tx_data, 30);\n    assert_eq!(pipe.client.stream_capacity(0), Ok(0));\n    // the server sent MAX_DATA (even though it got lost, so it has updated\n    // its max_rx_data\n    assert_eq!(pipe.server.max_rx_data(), 75);\n\n    // Trigger loss detection\n    test_utils::trigger_ack_based_loss(&mut pipe.server, &mut pipe.client);\n\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    // Make sure the server retransmitted MAX_DATA and STREAM_MAX_DATA\n    // frames\n    let mut max_data_seen = false;\n    let mut max_stream_data_seen = false;\n    for frame in frames {\n        match frame {\n            frame::Frame::MaxData { max } => {\n                assert_eq!(max, 75);\n                max_data_seen = true\n            },\n            frame::Frame::MaxStreamData { stream_id, max } => {\n                assert_eq!(stream_id, 0);\n                assert_eq!(max, 60);\n                max_stream_data_seen = true;\n            },\n            _ => (),\n        }\n    }\n    assert!(max_data_seen);\n    assert!(max_stream_data_seen);\n    // only stream_data and crypto frames increment retrans\n    assert_eq!(pipe.client.stats().retrans, 0);\n}\n\n#[rstest]\n/// Tests that PTO probe packets are not coalesced together.\nfn dont_coalesce_probes(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends Initial packet.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(len, 1200);\n    assert_eq!(pipe.client.path_stats().next().unwrap().total_pto_count, 0);\n\n    // Wait for PTO to expire.\n    let timer = pipe.client.timeout().unwrap();\n    std::thread::sleep(timer + Duration::from_millis(1));\n\n    pipe.client.on_timeout();\n    assert_eq!(pipe.client.path_stats().next().unwrap().total_pto_count, 1);\n\n    let epoch = packet::Epoch::Initial;\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .loss_probes(epoch),\n        1,\n    );\n\n    // Client sends PTO probe.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(len, 1200);\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .loss_probes(epoch),\n        0,\n    );\n\n    // Wait for PTO to expire.\n    let timer = pipe.client.timeout().unwrap();\n    std::thread::sleep(timer + Duration::from_millis(1));\n\n    pipe.client.on_timeout();\n    assert_eq!(pipe.client.path_stats().next().unwrap().total_pto_count, 2);\n\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .loss_probes(epoch),\n        2,\n    );\n\n    // Client sends first PTO probe.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(len, 1200);\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .loss_probes(epoch),\n        1,\n    );\n\n    // Client sends second PTO probe.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(len, 1200);\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .loss_probes(epoch),\n        0,\n    );\n}\n\n#[rstest]\nfn coalesce_padding_short(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends first flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(len, MIN_CLIENT_INITIAL_LEN);\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    // Server sends first flight.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    assert_eq!(len, MIN_CLIENT_INITIAL_LEN);\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    assert_eq!(pipe.client_recv(&mut buf[..len]), Ok(len));\n\n    // Client sends stream data.\n    assert!(pipe.client.is_established());\n    assert_eq!(pipe.client.stream_send(4, b\"hello\", true), Ok(5));\n\n    // Client sends second flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(len, MIN_CLIENT_INITIAL_LEN);\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    // None of the sent packets should have been dropped.\n    assert_eq!(pipe.client.sent_count, pipe.server.recv_count);\n    assert_eq!(pipe.server.sent_count, pipe.client.recv_count);\n}\n\n#[rstest]\n/// Tests that client avoids handshake deadlock by arming PTO.\nfn handshake_anti_deadlock(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert-big.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let mut pipe = test_utils::Pipe::with_server_config(&mut config).unwrap();\n\n    assert!(!pipe.client.handshake_status().has_handshake_keys);\n    assert!(!pipe.client.handshake_status().peer_verified_address);\n    assert!(!pipe.server.handshake_status().has_handshake_keys);\n    assert!(pipe.server.handshake_status().peer_verified_address);\n\n    // Client sends padded Initial.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(len, 1200);\n\n    // Server receives client's Initial and sends own Initial and Handshake\n    // until it's blocked by the anti-amplification limit.\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    assert!(!pipe.client.handshake_status().has_handshake_keys);\n    assert!(!pipe.client.handshake_status().peer_verified_address);\n    assert!(pipe.server.handshake_status().has_handshake_keys);\n    assert!(pipe.server.handshake_status().peer_verified_address);\n\n    // Client receives the server flight and sends Handshake ACK, but it is\n    // lost.\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n    test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    assert!(pipe.client.handshake_status().has_handshake_keys);\n    assert!(!pipe.client.handshake_status().peer_verified_address);\n    assert!(pipe.server.handshake_status().has_handshake_keys);\n    assert!(pipe.server.handshake_status().peer_verified_address);\n\n    // Make sure client's PTO timer is armed.\n    assert!(pipe.client.timeout().is_some());\n}\n\n#[rstest]\n/// Tests that packets with corrupted type (from Handshake to Initial) are\n/// properly ignored.\nfn handshake_packet_type_corruption(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends padded Initial.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    assert_eq!(len, 1200);\n\n    // Server receives client's Initial and sends own Initial and Handshake.\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Client sends Initial packet with ACK.\n    let active_pid = pipe.client.paths.get_active_path_id().expect(\"no active\");\n    let (ty, len) = pipe\n        .client\n        .send_single(&mut buf, active_pid, false, Instant::now())\n        .unwrap();\n    assert_eq!(ty, Type::Initial);\n\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    // Client sends Handshake packet.\n    let (ty, len) = pipe\n        .client\n        .send_single(&mut buf, active_pid, false, Instant::now())\n        .unwrap();\n    assert_eq!(ty, Type::Handshake);\n\n    // Packet type is corrupted to Initial.\n    buf[0] &= !(0x20);\n\n    let hdr = Header::from_slice(&mut buf[..len], 0).unwrap();\n    assert_eq!(hdr.ty, Type::Initial);\n\n    // Server receives corrupted packet without returning an error.\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n}\n\n#[rstest]\nfn dgram_send_fails_invalidstate(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(\n        pipe.client.dgram_send(b\"hello, world\"),\n        Err(Error::InvalidState)\n    );\n}\n\n#[rstest]\nfn dgram_send_app_limited(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n    let send_buf = [0xcf; 1000];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.enable_dgram(true, 1000, 1000);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    for _ in 0..1000 {\n        assert_eq!(pipe.client.dgram_send(&send_buf), Ok(()));\n    }\n\n    // bbr2_gcongestion uses different logic to set app_limited\n    // TODO fix\n    let should_be_app_limited =\n        cc_algorithm_name == \"cubic\" || cc_algorithm_name == \"reno\";\n    assert_eq!(\n        !pipe\n            .client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .app_limited(),\n        should_be_app_limited\n    );\n    assert_eq!(pipe.client.dgram_send_queue.byte_size(), 1_000_000);\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    assert_ne!(pipe.client.dgram_send_queue.byte_size(), 0);\n    assert_ne!(pipe.client.dgram_send_queue.byte_size(), 1_000_000);\n    assert_eq!(\n        !pipe\n            .client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .app_limited(),\n        should_be_app_limited\n    );\n\n    assert_eq!(pipe.server_recv(&mut buf[..len]), Ok(len));\n\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    assert_ne!(pipe.client.dgram_send_queue.byte_size(), 0);\n    assert_ne!(pipe.client.dgram_send_queue.byte_size(), 1_000_000);\n\n    assert_eq!(\n        !pipe\n            .client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .app_limited(),\n        should_be_app_limited\n    );\n}\n\n#[rstest]\nfn dgram_single_datagram(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.enable_dgram(true, 10, 10);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.dgram_send(b\"hello, world\"), Ok(()));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let result1 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result1, Ok(12));\n\n    let result2 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result2, Err(Error::Done));\n}\n\n#[rstest]\nfn dgram_multiple_datagrams(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.enable_dgram(true, 2, 3);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.dgram_send_queue_len(), 0);\n    assert_eq!(pipe.client.dgram_send_queue_byte_size(), 0);\n\n    assert_eq!(pipe.client.dgram_send(b\"hello, world\"), Ok(()));\n    assert_eq!(pipe.client.dgram_send(b\"ciao, mondo\"), Ok(()));\n    assert_eq!(pipe.client.dgram_send(b\"hola, mundo\"), Ok(()));\n    assert!(pipe.client.is_dgram_send_queue_full());\n\n    assert_eq!(pipe.client.dgram_send_queue_byte_size(), 34);\n\n    pipe.client\n        .dgram_purge_outgoing(|d: &[u8]| -> bool { d[0] == b'c' });\n\n    assert_eq!(pipe.client.dgram_send_queue_len(), 2);\n    assert_eq!(pipe.client.dgram_send_queue_byte_size(), 23);\n    assert!(!pipe.client.is_dgram_send_queue_full());\n\n    // Before packets exchanged, no dgrams on server receive side.\n    assert_eq!(pipe.server.dgram_recv_queue_len(), 0);\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // After packets exchanged, no dgrams on client send side.\n    assert_eq!(pipe.client.dgram_send_queue_len(), 0);\n    assert_eq!(pipe.client.dgram_send_queue_byte_size(), 0);\n\n    assert_eq!(pipe.server.dgram_recv_queue_len(), 2);\n    assert_eq!(pipe.server.dgram_recv_queue_byte_size(), 23);\n    assert!(pipe.server.is_dgram_recv_queue_full());\n\n    let result1 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result1, Ok(12));\n    assert_eq!(buf[0], b'h');\n    assert_eq!(buf[1], b'e');\n    assert!(!pipe.server.is_dgram_recv_queue_full());\n\n    let result2 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result2, Ok(11));\n    assert_eq!(buf[0], b'h');\n    assert_eq!(buf[1], b'o');\n\n    let result3 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result3, Err(Error::Done));\n\n    assert_eq!(pipe.server.dgram_recv_queue_len(), 0);\n    assert_eq!(pipe.server.dgram_recv_queue_byte_size(), 0);\n}\n\n#[rstest]\nfn dgram_send_queue_overflow(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.enable_dgram(true, 10, 2);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.dgram_send(b\"hello, world\"), Ok(()));\n    assert_eq!(pipe.client.dgram_send(b\"ciao, mondo\"), Ok(()));\n    assert_eq!(pipe.client.dgram_send(b\"hola, mundo\"), Err(Error::Done));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let result1 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result1, Ok(12));\n    assert_eq!(buf[0], b'h');\n    assert_eq!(buf[1], b'e');\n\n    let result2 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result2, Ok(11));\n    assert_eq!(buf[0], b'c');\n    assert_eq!(buf[1], b'i');\n\n    let result3 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result3, Err(Error::Done));\n}\n\n#[rstest]\nfn dgram_recv_queue_overflow(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.enable_dgram(true, 2, 10);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.dgram_send(b\"hello, world\"), Ok(()));\n    assert_eq!(pipe.client.dgram_send(b\"ciao, mondo\"), Ok(()));\n    assert_eq!(pipe.client.dgram_send(b\"hola, mundo\"), Ok(()));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let result1 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result1, Ok(11));\n    assert_eq!(buf[0], b'c');\n    assert_eq!(buf[1], b'i');\n\n    let result2 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result2, Ok(11));\n    assert_eq!(buf[0], b'h');\n    assert_eq!(buf[1], b'o');\n\n    let result3 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result3, Err(Error::Done));\n}\n\n#[rstest]\nfn dgram_send_max_size(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; MAX_DGRAM_FRAME_SIZE as usize];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.enable_dgram(true, 10, 10);\n    config.set_max_recv_udp_payload_size(1452);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n\n    // Before handshake (before peer settings) we don't know max dgram size\n    assert_eq!(pipe.client.dgram_max_writable_len(), None);\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let max_dgram_size = pipe.client.dgram_max_writable_len().unwrap();\n\n    // Tests use a 16-byte connection ID, so the max datagram frame payload\n    // size is (1200 byte-long packet - 40 bytes overhead)\n    assert_eq!(max_dgram_size, 1160);\n\n    let dgram_packet: Vec<u8> = vec![42; max_dgram_size];\n\n    assert_eq!(pipe.client.dgram_send(&dgram_packet), Ok(()));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let result1 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result1, Ok(max_dgram_size));\n\n    let result2 = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(result2, Err(Error::Done));\n}\n\n#[rstest]\n/// Tests is_readable check.\nfn is_readable(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.enable_dgram(true, 10, 10);\n    config.set_max_recv_udp_payload_size(1452);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // No readable data.\n    assert!(!pipe.client.is_readable());\n    assert!(!pipe.server.is_readable());\n\n    assert_eq!(pipe.client.stream_send(4, b\"aaaaa\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server received stream.\n    assert!(!pipe.client.is_readable());\n    assert!(pipe.server.is_readable());\n\n    assert_eq!(\n        pipe.server.stream_send(4, b\"aaaaaaaaaaaaaaa\", false),\n        Ok(15)\n    );\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Client received stream.\n    assert!(pipe.client.is_readable());\n    assert!(pipe.server.is_readable());\n\n    // Client drains stream.\n    stream_recv_discard(&mut pipe.client, discard, 4).unwrap();\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(!pipe.client.is_readable());\n    assert!(pipe.server.is_readable());\n\n    // Server shuts down stream.\n    assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Read, 0), Ok(()));\n    assert!(!pipe.server.is_readable());\n\n    // Server received dgram.\n    assert_eq!(pipe.client.dgram_send(b\"dddddddddddddd\"), Ok(()));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(!pipe.client.is_readable());\n    assert!(pipe.server.is_readable());\n\n    // Client received dgram.\n    assert_eq!(pipe.server.dgram_send(b\"dddddddddddddd\"), Ok(()));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert!(pipe.client.is_readable());\n    assert!(pipe.server.is_readable());\n\n    // Drain the dgram queues.\n    let r = pipe.server.dgram_recv(&mut buf);\n    assert_eq!(r, Ok(14));\n    assert!(!pipe.server.is_readable());\n\n    let r = pipe.client.dgram_recv(&mut buf);\n    assert_eq!(r, Ok(14));\n    assert!(!pipe.client.is_readable());\n}\n\n#[rstest]\nfn close(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.close(false, 0x1234, b\"hello?\"), Ok(()));\n\n    assert_eq!(\n        pipe.client.close(false, 0x4321, b\"hello?\"),\n        Err(Error::Done)\n    );\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    assert_eq!(\n        frames.first(),\n        Some(&frame::Frame::ConnectionClose {\n            error_code: 0x1234,\n            frame_type: 0,\n            reason: b\"hello?\".to_vec(),\n        })\n    );\n}\n\n#[rstest]\nfn app_close_by_client(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.close(true, 0x1234, b\"hello!\"), Ok(()));\n\n    assert_eq!(pipe.client.close(true, 0x4321, b\"hello!\"), Err(Error::Done));\n\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.server, &mut buf[..len]).unwrap();\n\n    assert_eq!(\n        frames.first(),\n        Some(&frame::Frame::ApplicationClose {\n            error_code: 0x1234,\n            reason: b\"hello!\".to_vec(),\n        })\n    );\n}\n\n// OpenSSL does not provide a straightforward interface to deal with custom\n// off-load key signing.\n#[cfg(not(feature = \"openssl\"))]\n#[rstest]\nfn app_close_by_server_during_handshake_private_key_failure(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    pipe.server.handshake.set_failing_private_key_method();\n\n    // Client sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    assert_eq!(\n        test_utils::process_flight(&mut pipe.server, flight),\n        Err(Error::TlsFail)\n    );\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // Both connections are not established.\n    assert!(!pipe.server.is_established());\n    assert!(!pipe.client.is_established());\n\n    // Connection should already be closed due the failure during key signing.\n    assert_eq!(\n        pipe.server.close(true, 123, b\"fail whale\"),\n        Err(Error::Done)\n    );\n\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Connection should already be closed due the failure during key signing.\n    assert_eq!(\n        pipe.client.close(true, 123, b\"fail whale\"),\n        Err(Error::Done)\n    );\n\n    // Connection is not established on the server / client (and never\n    // will be)\n    assert!(!pipe.server.is_established());\n    assert!(!pipe.client.is_established());\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        pipe.server.local_error(),\n        Some(&ConnectionError {\n            is_app: false,\n            error_code: 0x01,\n            reason: vec![],\n        })\n    );\n    assert_eq!(\n        pipe.client.peer_error(),\n        Some(&ConnectionError {\n            is_app: false,\n            error_code: 0x01,\n            reason: vec![],\n        })\n    );\n}\n\n#[rstest]\nfn app_close_by_server_during_handshake_not_established(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // Both connections are not established.\n    assert!(!pipe.client.is_established() && !pipe.server.is_established());\n\n    // Server closes before connection is established.\n    pipe.server.close(true, 123, b\"fail whale\").unwrap();\n\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Connection is established on the client.\n    assert!(pipe.client.is_established());\n\n    // Client sends after connection is established.\n    pipe.client.stream_send(0, b\"badauthtoken\", true).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    // Connection is not established on the server (and never will be)\n    assert!(!pipe.server.is_established());\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        pipe.server.local_error(),\n        Some(&ConnectionError {\n            is_app: false,\n            error_code: 0x0c,\n            reason: vec![],\n        })\n    );\n    assert_eq!(\n        pipe.client.peer_error(),\n        Some(&ConnectionError {\n            is_app: false,\n            error_code: 0x0c,\n            reason: vec![],\n        })\n    );\n}\n\n#[rstest]\nfn app_close_by_server_during_handshake_established(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // Both connections are not established.\n    assert!(!pipe.client.is_established() && !pipe.server.is_established());\n\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Connection is established on the client.\n    assert!(pipe.client.is_established());\n\n    // Client sends after connection is established.\n    pipe.client.stream_send(0, b\"badauthtoken\", true).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    // Connection is established on the server but the Handshake ACK has not\n    // been sent yet.\n    assert!(pipe.server.is_established());\n\n    // Server closes after connection is established.\n    pipe.server\n        .close(true, 123, b\"Invalid authentication\")\n        .unwrap();\n\n    // Server sends Handshake ACK and then 1RTT CONNECTION_CLOSE.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        pipe.server.local_error(),\n        Some(&ConnectionError {\n            is_app: true,\n            error_code: 123,\n            reason: b\"Invalid authentication\".to_vec()\n        })\n    );\n    assert_eq!(\n        pipe.client.peer_error(),\n        Some(&ConnectionError {\n            is_app: true,\n            error_code: 123,\n            reason: b\"Invalid authentication\".to_vec()\n        })\n    );\n}\n\n#[rstest]\nfn transport_close_by_client_during_handshake_established(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n\n    // Client sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // Both connections are not established.\n    assert!(!pipe.client.is_established() && !pipe.server.is_established());\n\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Connection is established on the client.\n    assert!(pipe.client.is_established());\n\n    // Client sends after connection is established.\n    pipe.client.close(false, 123, b\"connection close\").unwrap();\n\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    assert_eq!(\n        pipe.server.peer_error(),\n        Some(&ConnectionError {\n            is_app: false,\n            error_code: 123,\n            reason: b\"connection close\".to_vec()\n        })\n    );\n    assert_eq!(\n        pipe.client.local_error(),\n        Some(&ConnectionError {\n            is_app: false,\n            error_code: 123,\n            reason: b\"connection close\".to_vec()\n        })\n    );\n}\n\n#[rstest]\nfn peer_error(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.server.close(false, 0x1234, b\"hello?\"), Ok(()));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        pipe.client.peer_error(),\n        Some(&ConnectionError {\n            is_app: false,\n            error_code: 0x1234u64,\n            reason: b\"hello?\".to_vec()\n        })\n    );\n}\n\n#[rstest]\nfn app_peer_error(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.server.close(true, 0x1234, b\"hello!\"), Ok(()));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        pipe.client.peer_error(),\n        Some(&ConnectionError {\n            is_app: true,\n            error_code: 0x1234u64,\n            reason: b\"hello!\".to_vec()\n        })\n    );\n}\n\n#[rstest]\nfn local_error(#[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str) {\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.server.local_error(), None);\n\n    assert_eq!(pipe.server.close(true, 0x1234, b\"hello!\"), Ok(()));\n\n    assert_eq!(\n        pipe.server.local_error(),\n        Some(&ConnectionError {\n            is_app: true,\n            error_code: 0x1234u64,\n            reason: b\"hello!\".to_vec()\n        })\n    );\n}\n\n#[rstest]\nfn update_max_datagram_size(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut client_scid = [0; 16];\n    rand::rand_bytes(&mut client_scid[..]);\n    let client_scid = ConnectionId::from_ref(&client_scid);\n    let client_addr = \"127.0.0.1:1234\".parse().unwrap();\n\n    let mut server_scid = [0; 16];\n    rand::rand_bytes(&mut server_scid[..]);\n    let server_scid = ConnectionId::from_ref(&server_scid);\n    let server_addr = \"127.0.0.1:4321\".parse().unwrap();\n\n    let mut client_config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(\n        client_config.set_cc_algorithm_name(cc_algorithm_name),\n        Ok(())\n    );\n    client_config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    client_config.set_max_recv_udp_payload_size(1200);\n\n    let mut server_config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(\n        server_config.set_cc_algorithm_name(cc_algorithm_name),\n        Ok(())\n    );\n    server_config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    server_config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    server_config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    server_config.verify_peer(false);\n    server_config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    // Larger than the client\n    server_config.set_max_send_udp_payload_size(1500);\n\n    let mut pipe = test_utils::Pipe {\n        client: connect(\n            Some(\"quic.tech\"),\n            &client_scid,\n            client_addr,\n            server_addr,\n            &mut client_config,\n        )\n        .unwrap(),\n        server: accept(\n            &server_scid,\n            None,\n            server_addr,\n            client_addr,\n            &mut server_config,\n        )\n        .unwrap(),\n    };\n\n    // Before handshake\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .max_datagram_size(),\n        1500,\n    );\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // After handshake, max_datagram_size should match to client's\n    // max_recv_udp_payload_size which is smaller\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .max_datagram_size(),\n        1200,\n    );\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .recovery\n            .cwnd(),\n        if cc_algorithm_name == \"cubic\" {\n            12000\n        } else if cfg!(feature = \"openssl\") {\n            13437\n        } else {\n            13421\n        },\n    );\n}\n\n#[rstest]\n/// Tests that connection-level send capacity decreases as more stream data\n/// is buffered.\nfn send_capacity(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(100000);\n    config.set_initial_max_stream_data_bidi_local(10000);\n    config.set_initial_max_stream_data_bidi_remote(10000);\n    config.set_initial_max_streams_bidi(10);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(0, b\"hello!\", true), Ok(6));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(4, b\"hello!\", true), Ok(6));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(8, b\"hello!\", true), Ok(6));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.stream_send(12, b\"hello!\", true), Ok(6));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut r = pipe.server.readable().collect::<Vec<u64>>();\n    assert_eq!(r.len(), 4);\n\n    r.sort();\n\n    assert_eq!(r, [0, 4, 8, 12]);\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((6, true))\n    );\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 4),\n        Ok((6, true))\n    );\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 8),\n        Ok((6, true))\n    );\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 12),\n        Ok((6, true))\n    );\n\n    assert_eq!(\n        pipe.server.tx_cap,\n        if cc_algorithm_name == \"cubic\" {\n            12000\n        } else if cfg!(feature = \"openssl\") {\n            13959\n        } else {\n            13873\n        }\n    );\n\n    assert_eq!(pipe.server.stream_send(0, &buf[..5000], false), Ok(5000));\n    assert_eq!(pipe.server.stream_send(4, &buf[..5000], false), Ok(5000));\n    assert_eq!(\n        pipe.server.stream_send(8, &buf[..5000], false),\n        if cc_algorithm_name == \"cubic\" {\n            Ok(2000)\n        } else if cfg!(feature = \"openssl\") {\n            Ok(3959)\n        } else {\n            Ok(3873)\n        }\n    );\n\n    // No more connection send capacity.\n    assert_eq!(\n        pipe.server.stream_send(12, &buf[..5000], false),\n        Err(Error::Done)\n    );\n    assert_eq!(pipe.server.tx_cap, 0);\n\n    assert_eq!(pipe.advance(), Ok(()));\n}\n\n#[cfg(feature = \"boringssl-boring-crate\")]\n#[rstest]\nfn user_provided_boring_ctx(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) -> Result<()> {\n    // Manually construct boring ssl ctx for server\n    let mut server_tls_ctx_builder =\n        boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls())\n            .unwrap();\n    server_tls_ctx_builder\n        .set_certificate_chain_file(\"examples/cert.crt\")\n        .unwrap();\n    server_tls_ctx_builder\n        .set_private_key_file(\"examples/cert.key\", boring::ssl::SslFiletype::PEM)\n        .unwrap();\n\n    let mut server_config = Config::with_boring_ssl_ctx_builder(\n        PROTOCOL_VERSION,\n        server_tls_ctx_builder,\n    )?;\n    let mut client_config = Config::new(PROTOCOL_VERSION)?;\n    assert_eq!(\n        client_config.set_cc_algorithm_name(cc_algorithm_name),\n        Ok(())\n    );\n    client_config.load_cert_chain_from_pem_file(\"examples/cert.crt\")?;\n    client_config.load_priv_key_from_pem_file(\"examples/cert.key\")?;\n\n    for config in [&mut client_config, &mut server_config] {\n        config.set_application_protos(&[b\"proto1\", b\"proto2\"])?;\n        config.set_initial_max_data(30);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.set_max_idle_timeout(180_000);\n        config.verify_peer(false);\n        config.set_ack_delay_exponent(8);\n    }\n\n    let mut pipe = test_utils::Pipe::with_client_and_server_config(\n        &mut client_config,\n        &mut server_config,\n    )?;\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    Ok(())\n}\n\n#[cfg(feature = \"boringssl-boring-crate\")]\n#[rstest]\nfn in_handshake_config(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) -> Result<()> {\n    let mut buf = [0; 65535];\n\n    const CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS: usize = 30;\n    const CUSTOM_INITIAL_MAX_STREAMS_BIDI: u64 = 30;\n    const CUSTOM_MAX_IDLE_TIMEOUT: Duration = Duration::from_secs(3);\n\n    // Manually construct `SslContextBuilder` for the server so we can modify\n    // CWND during the handshake.\n    let mut server_tls_ctx_builder =\n        boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls())\n            .unwrap();\n    server_tls_ctx_builder\n        .set_certificate_chain_file(\"examples/cert.crt\")\n        .unwrap();\n    server_tls_ctx_builder\n        .set_private_key_file(\"examples/cert.key\", boring::ssl::SslFiletype::PEM)\n        .unwrap();\n    server_tls_ctx_builder.set_select_certificate_callback(|mut hello| {\n        <Connection>::set_initial_congestion_window_packets_in_handshake(\n            hello.ssl_mut(),\n            CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS,\n        )\n        .unwrap();\n\n        <Connection>::set_max_idle_timeout_in_handshake(\n            hello.ssl_mut(),\n            CUSTOM_MAX_IDLE_TIMEOUT.as_millis() as u64,\n        )\n        .unwrap();\n\n        <Connection>::set_initial_max_streams_bidi_in_handshake(\n            hello.ssl_mut(),\n            CUSTOM_INITIAL_MAX_STREAMS_BIDI,\n        )\n        .unwrap();\n\n        Ok(())\n    });\n\n    let mut server_config = Config::with_boring_ssl_ctx_builder(\n        PROTOCOL_VERSION,\n        server_tls_ctx_builder,\n    )?;\n    assert_eq!(\n        server_config.set_cc_algorithm_name(cc_algorithm_name),\n        Ok(())\n    );\n\n    let mut client_config = Config::new(PROTOCOL_VERSION)?;\n    client_config.load_cert_chain_from_pem_file(\"examples/cert.crt\")?;\n    client_config.load_priv_key_from_pem_file(\"examples/cert.key\")?;\n\n    for config in [&mut client_config, &mut server_config] {\n        config.set_application_protos(&[b\"proto1\", b\"proto2\"])?;\n        config.set_initial_max_data(1000000);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.set_max_idle_timeout(180_000);\n        config.verify_peer(false);\n        config.set_ack_delay_exponent(8);\n    }\n\n    let mut pipe = test_utils::Pipe::with_client_and_server_config(\n        &mut client_config,\n        &mut server_config,\n    )?;\n\n    // Client sends initial flight.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n\n    assert_eq!(pipe.server.tx_cap, 0);\n\n    // Server receives client's initial flight and updates its config.\n    pipe.server_recv(&mut buf[..len]).unwrap();\n\n    assert_eq!(\n        pipe.server.tx_cap,\n        CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS * 1200\n    );\n\n    assert_eq!(pipe.server.idle_timeout(), Some(CUSTOM_MAX_IDLE_TIMEOUT));\n\n    // Server sends initial flight.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    pipe.client_recv(&mut buf[..len]).unwrap();\n\n    // Ensure the client received the new transport parameters.\n    assert_eq!(pipe.client.idle_timeout(), Some(CUSTOM_MAX_IDLE_TIMEOUT));\n\n    assert_eq!(\n        pipe.client.peer_streams_left_bidi(),\n        CUSTOM_INITIAL_MAX_STREAMS_BIDI\n    );\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    Ok(())\n}\n\n#[cfg(feature = \"boringssl-boring-crate\")]\n#[rstest]\n/// Tests that MAX_STREAMS threshold is correctly calculated after changing\n/// initial_max_streams_bidi via handshake callback.\nfn max_streams_threshold_after_handshake_callback_update(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    // Original config value - low value so threshold would be 4/2 = 2\n    const CONFIG_INITIAL_MAX_STREAMS_BIDI: u64 = 4;\n    // Value set during handshake callback - threshold should be 100/2 = 50\n    const CALLBACK_INITIAL_MAX_STREAMS_BIDI: u64 = 100;\n\n    // Setup server with handshake callback that changes initial_max_streams_bidi\n    let mut server_tls_ctx_builder = test_utils::Pipe::default_tls_ctx_builder();\n    server_tls_ctx_builder.set_select_certificate_callback(|mut hello| {\n        // Change initial_max_streams_bidi from 4 to 100 during handshake\n        <Connection>::set_initial_max_streams_bidi_in_handshake(\n            hello.ssl_mut(),\n            CALLBACK_INITIAL_MAX_STREAMS_BIDI,\n        )\n        .unwrap();\n\n        Ok(())\n    });\n\n    let mut server_config = Config::with_boring_ssl_ctx_builder(\n        PROTOCOL_VERSION,\n        server_tls_ctx_builder,\n    )\n    .unwrap();\n\n    let mut client_config =\n        test_utils::Pipe::default_config(cc_algorithm_name).unwrap();\n\n    for config in [&mut client_config, &mut server_config] {\n        config\n            .set_application_protos(&[b\"proto1\", b\"proto2\"])\n            .unwrap();\n        config.set_initial_max_data(1000000);\n        config.set_initial_max_stream_data_bidi_remote(1000);\n        // Server config uses CONFIG_INITIAL_MAX_STREAMS_BIDI, but callback\n        // changes it to CALLBACK_INITIAL_MAX_STREAMS_BIDI\n        config.set_initial_max_streams_bidi(CONFIG_INITIAL_MAX_STREAMS_BIDI);\n        config.set_cc_algorithm_name(cc_algorithm_name).unwrap();\n        config.verify_peer(false);\n    }\n\n    let mut pipe = test_utils::Pipe::with_client_and_server_config(\n        &mut client_config,\n        &mut server_config,\n    )\n    .unwrap();\n\n    // Complete handshake - server's callback changes max_streams to\n    // CALLBACK_INITIAL_MAX_STREAMS_BIDI so verify the client received that.\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    pipe.server_recv(&mut buf[..len]).unwrap();\n\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    pipe.client_recv(&mut buf[..len]).unwrap();\n\n    assert_eq!(\n        pipe.client.peer_streams_left_bidi(),\n        CALLBACK_INITIAL_MAX_STREAMS_BIDI\n    );\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let pre_threshold_streams = CALLBACK_INITIAL_MAX_STREAMS_BIDI / 2 - 1;\n    // Complete 49 streams to bring available down to 51\n    // With correct threshold (50), MAX_STREAMS should NOT be sent yet\n    // With buggy threshold (2), MAX_STREAMS would be sent way too early\n    for i in 0..pre_threshold_streams {\n        let stream_id = i * 4;\n        pipe.client.stream_send(stream_id, b\"a\", true).ok();\n    }\n    pipe.advance().ok();\n\n    for i in 0..pre_threshold_streams {\n        let stream_id = i * 4;\n        pipe.server.stream_recv(stream_id, &mut buf).ok();\n        pipe.server.stream_send(stream_id, b\"b\", true).ok();\n    }\n    pipe.advance().ok();\n\n    for i in 0..pre_threshold_streams {\n        let stream_id = i * 4;\n        pipe.client.stream_recv(stream_id, &mut buf).ok();\n    }\n    pipe.advance().ok();\n\n    assert_eq!(\n        pipe.server.streams.max_streams_bidi_next(),\n        CALLBACK_INITIAL_MAX_STREAMS_BIDI + pre_threshold_streams\n    );\n\n    // Verify MAX_STREAMS was NOT sent yet (available > threshold) With the bug\n    // (threshold = CONFIG_INITIAL_MAX_STREAMS_BIDI/2), MAX_STREAMS would have\n    // been sent when available dropped to it, and client would have more streams\n    // available.\n    assert_eq!(\n        pipe.client.streams.peer_streams_left_bidi(),\n        CALLBACK_INITIAL_MAX_STREAMS_BIDI - pre_threshold_streams,\n        \"MAX_STREAMS should NOT have been sent yet.\"\n    );\n\n    // Complete 1 more stream → available = 50 (== threshold of 50)\n    // MAX_STREAMS SHOULD be sent now\n    let stream_id = pre_threshold_streams * 4;\n    pipe.client.stream_send(stream_id, b\"a\", true).ok();\n    pipe.advance().ok();\n\n    pipe.server.stream_recv(stream_id, &mut buf).ok();\n    pipe.server.stream_send(stream_id, b\"b\", true).ok();\n    pipe.advance().ok();\n\n    pipe.client.stream_recv(stream_id, &mut buf).ok();\n    pipe.advance().ok();\n\n    // Server's next should have increased by 1\n    assert_eq!(\n        pipe.server.streams.max_streams_bidi_next(),\n        CALLBACK_INITIAL_MAX_STREAMS_BIDI + CALLBACK_INITIAL_MAX_STREAMS_BIDI / 2\n    );\n\n    // Verify MAX_STREAMS WAS sent (available <= threshold). With the bug,\n    // threshold = CONFIG_INITIAL/2 = 2, so MAX_STREAMS won't be sent until\n    // available <= 2, which we never reach in this test.\n    let streams_left = pipe.client.streams.peer_streams_left_bidi();\n    assert!(\n        streams_left > CALLBACK_INITIAL_MAX_STREAMS_BIDI / 2 + 1,\n        \"MAX_STREAMS was not sent when available hit the correct threshold (50)\"\n    );\n}\n\n#[rstest]\nfn initial_cwnd(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) -> Result<()> {\n    const CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS: usize = 30;\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config.set_initial_congestion_window_packets(\n        CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS,\n    );\n    // From Pipe::new()\n    config.load_cert_chain_from_pem_file(\"examples/cert.crt\")?;\n    config.load_priv_key_from_pem_file(\"examples/cert.key\")?;\n    config.set_application_protos(&[b\"proto1\", b\"proto2\"])?;\n    config.set_initial_max_data(1000000);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n    config.set_initial_max_streams_uni(3);\n    config.set_max_idle_timeout(180_000);\n    config.verify_peer(false);\n    config.set_ack_delay_exponent(8);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    if cc_algorithm_name == \"cubic\" {\n        assert_eq!(\n            pipe.server.tx_cap,\n            CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS * 1200\n        );\n    } else {\n        // TODO understand where these adjustments come from and why they vary\n        // by TLS implementation and OS target.\n        let expected = CUSTOM_INITIAL_CONGESTION_WINDOW_PACKETS * 1200 +\n            if cfg!(feature = \"openssl\") {\n                1463\n            } else {\n                1447\n            };\n\n        assert!(\n            pipe.server.tx_cap >= expected,\n            \"{} vs {}\",\n            pipe.server.tx_cap,\n            expected\n        );\n        assert!(\n            pipe.server.tx_cap <= expected + 1,\n            \"{} vs {}\",\n            pipe.server.tx_cap,\n            expected + 1\n        );\n    }\n\n    Ok(())\n}\n\n#[rstest]\n/// Tests that resetting a stream restores flow control for unsent data.\nfn last_tx_data_larger_than_tx_data(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(12000);\n    config.set_initial_max_stream_data_bidi_local(20000);\n    config.set_initial_max_stream_data_bidi_remote(20000);\n    config.set_max_recv_udp_payload_size(1200);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client opens stream 4 and 8.\n    assert_eq!(pipe.client.stream_send(4, b\"a\", true), Ok(1));\n    assert_eq!(pipe.client.stream_send(8, b\"b\", true), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server reads stream data.\n    stream_recv_discard(&mut pipe.server, discard, 4).unwrap();\n\n    // Server sends stream data close to cwnd (12000).\n    let buf = [0; 10000];\n    assert_eq!(pipe.server.stream_send(4, &buf, false), Ok(10000));\n\n    test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // Server buffers some data, until send capacity limit reached.\n    let mut buf = [0; 1200];\n    assert_eq!(pipe.server.stream_send(4, &buf, false), Ok(1200));\n    assert_eq!(pipe.server.stream_send(8, &buf, false), Ok(800));\n    assert_eq!(pipe.server.stream_send(4, &buf, false), Err(Error::Done));\n\n    // Wait for PTO to expire.\n    let timer = pipe.server.timeout().unwrap();\n    std::thread::sleep(timer + Duration::from_millis(1));\n\n    pipe.server.on_timeout();\n\n    // Server sends PTO probe (not limited to cwnd),\n    // to update last_tx_data.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    assert_eq!(len, 1200);\n\n    // Client sends STOP_SENDING to decrease tx_data\n    // by unsent data. It will make last_tx_data > tx_data\n    // and trigger #1232 bug.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 4,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n    pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n}\n\n/// Tests that when the client provides a new ConnectionId, it eventually\n/// reaches the server and notifies the application.\n#[rstest]\nfn send_connection_ids(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(3);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // So far, there should not have any QUIC event.\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.scids_left(), 2);\n\n    let (scid, reset_token) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(pipe.client.new_scid(&scid, reset_token, false), Ok(1));\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // At this point, the server should be notified that it has a new CID.\n    assert_eq!(pipe.server.available_dcids(), 1);\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.client.scids_left(), 1);\n\n    // Now, a second CID can be provided.\n    let (scid, reset_token) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(pipe.client.new_scid(&scid, reset_token, false), Ok(2));\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // At this point, the server should be notified that it has a new CID.\n    assert_eq!(pipe.server.available_dcids(), 2);\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.client.scids_left(), 0);\n\n    // If now the client tries to send another CID, it reports an error\n    // since it exceeds the limit of active CIDs.\n    let (scid, reset_token) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(\n        pipe.client.new_scid(&scid, reset_token, false),\n        Err(Error::IdLimit),\n    );\n}\n\n#[rstest]\n/// Tests that NEW_CONNECTION_ID with zero-length CID are rejected.\nfn connection_id_zero(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let mut frames = Vec::new();\n\n    // Client adds a CID that is too short.\n    let (scid, reset_token) = test_utils::create_cid_and_reset_token(0);\n\n    frames.push(frame::Frame::NewConnectionId {\n        seq_num: 1,\n        retire_prior_to: 0,\n        conn_id: scid.to_vec(),\n        reset_token: reset_token.to_be_bytes(),\n    });\n\n    let pkt_type = Type::Short;\n\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    let active_path = pipe.server.paths.get_active().unwrap();\n    let info = RecvInfo {\n        to: active_path.local_addr(),\n        from: active_path.peer_addr(),\n    };\n\n    assert_eq!(\n        pipe.server.recv(&mut buf[..written], info),\n        Err(Error::InvalidFrame)\n    );\n\n    let written = match pipe.server.send(&mut buf) {\n        Ok((write, _)) => write,\n\n        Err(_) => unreachable!(),\n    };\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..written]).unwrap();\n    let mut iter = frames.iter();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ConnectionClose {\n            error_code: 0x7,\n            frame_type: 0,\n            reason: Vec::new(),\n        })\n    );\n}\n\n#[rstest]\n/// Tests that NEW_CONNECTION_ID with too long CID are rejected.\nfn connection_id_invalid_max_len(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let mut frames = Vec::new();\n\n    // Client adds a CID that is too long.\n    let (scid, reset_token) =\n        test_utils::create_cid_and_reset_token(MAX_CONN_ID_LEN + 1);\n\n    frames.push(frame::Frame::NewConnectionId {\n        seq_num: 1,\n        retire_prior_to: 0,\n        conn_id: scid.to_vec(),\n        reset_token: reset_token.to_be_bytes(),\n    });\n\n    let pkt_type = Type::Short;\n\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    let active_path = pipe.server.paths.get_active().unwrap();\n    let info = RecvInfo {\n        to: active_path.local_addr(),\n        from: active_path.peer_addr(),\n    };\n\n    assert_eq!(\n        pipe.server.recv(&mut buf[..written], info),\n        Err(Error::InvalidFrame)\n    );\n\n    let written = match pipe.server.send(&mut buf) {\n        Ok((write, _)) => write,\n\n        Err(_) => unreachable!(),\n    };\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..written]).unwrap();\n    let mut iter = frames.iter();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ConnectionClose {\n            error_code: 0x7,\n            frame_type: 0,\n            reason: Vec::new(),\n        })\n    );\n}\n\n#[rstest]\n/// Exercises the handling of NEW_CONNECTION_ID and RETIRE_CONNECTION_ID\n/// frames.\nfn connection_id_handling(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // So far, there should not have any QUIC event.\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.scids_left(), 1);\n\n    let scid = pipe.client.source_id().into_owned();\n\n    let (scid_1, reset_token_1) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1));\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // At this point, the server should be notified that it has a new CID.\n    assert_eq!(pipe.server.available_dcids(), 1);\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.client.scids_left(), 0);\n\n    // Now we assume that the client wants to advertise more source\n    // Connection IDs than the advertised limit. This is valid if it\n    // requests its peer to retire enough Connection IDs to fit within the\n    // limits.\n\n    let (scid_2, reset_token_2) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(pipe.client.new_scid(&scid_2, reset_token_2, true), Ok(2));\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // At this point, the server still have a spare DCID.\n    assert_eq!(pipe.server.available_dcids(), 1);\n    assert_eq!(pipe.server.path_event_next(), None);\n\n    // Client should have received a retired notification.\n    assert_eq!(pipe.client.retired_scid_next(), Some(scid));\n    assert_eq!(pipe.client.retired_scid_next(), None);\n\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.client.scids_left(), 0);\n\n    // The active Destination Connection ID of the server should now be the\n    // one with sequence number 1.\n    assert_eq!(pipe.server.destination_id(), scid_1);\n\n    // Now tries to experience CID retirement. If the server tries to remove\n    // non-existing DCIDs, it fails.\n    assert_eq!(pipe.server.retire_dcid(0), Err(Error::InvalidState));\n    assert_eq!(pipe.server.retire_dcid(3), Err(Error::InvalidState));\n\n    // Now it removes DCID with sequence 1.\n    assert_eq!(pipe.server.retire_dcid(1), Ok(()));\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.retired_scid_next(), Some(scid_1));\n    assert_eq!(pipe.client.retired_scid_next(), None);\n\n    assert_eq!(pipe.server.destination_id(), scid_2);\n    assert_eq!(pipe.server.available_dcids(), 0);\n\n    // Trying to remove the last DCID triggers an error.\n    assert_eq!(pipe.server.retire_dcid(2), Err(Error::OutOfIdentifiers));\n}\n\n#[rstest]\nfn lost_connection_id_frames(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let scid = pipe.client.source_id().into_owned();\n\n    let (scid_1, reset_token_1) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1));\n\n    // Packets are sent, but never received.\n    test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    // Wait until timer expires. Since the RTT is very low, wait a bit more.\n    let timer = pipe.client.timeout().unwrap();\n    std::thread::sleep(timer + Duration::from_millis(1));\n\n    pipe.client.on_timeout();\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // At this point, the server should be notified that it has a new CID.\n    assert_eq!(pipe.server.available_dcids(), 1);\n\n    // Now the server retires the first Destination CID.\n    assert_eq!(pipe.server.retire_dcid(0), Ok(()));\n\n    // But the packet never reaches the client.\n    test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // Wait until timer expires. Since the RTT is very low, wait a bit more.\n    let timer = pipe.server.timeout().unwrap();\n    std::thread::sleep(timer + Duration::from_millis(1));\n\n    pipe.server.on_timeout();\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.client.retired_scid_next(), Some(scid));\n    assert_eq!(pipe.client.retired_scid_next(), None);\n}\n\n#[rstest]\nfn sending_duplicate_scids(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(3);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let (scid_1, reset_token_1) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Trying to send the same CID with a different reset token raises an\n    // InvalidState error.\n    let reset_token_2 = reset_token_1.wrapping_add(1);\n    assert_eq!(\n        pipe.client.new_scid(&scid_1, reset_token_2, false),\n        Err(Error::InvalidState),\n    );\n\n    // Retrying to send the exact same CID with the same token returns the\n    // previously assigned CID seq, but without sending anything.\n    assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1));\n    assert!(!pipe.client.ids.has_new_scids());\n\n    // Now retire this new CID.\n    assert_eq!(pipe.server.retire_dcid(1), Ok(()));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // It is up to the application to ensure that a given SCID is not reused\n    // later.\n    assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(2));\n}\n\n#[rstest]\n/// Tests the limit to retired DCID sequence numbers.\nfn connection_id_retire_limit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // So far, there should not have any QUIC event.\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.scids_left(), 1);\n\n    let (scid_1, reset_token_1) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(pipe.client.new_scid(&scid_1, reset_token_1, false), Ok(1));\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // At this point, the server should be notified that it has a new CID.\n    assert_eq!(pipe.server.available_dcids(), 1);\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.client.scids_left(), 0);\n\n    let mut frames = Vec::new();\n\n    // Client retires more than 3x the number of allowed active CIDs.\n    for i in 2..=7 {\n        let (scid, reset_token) = test_utils::create_cid_and_reset_token(16);\n\n        frames.push(frame::Frame::NewConnectionId {\n            seq_num: i,\n            retire_prior_to: i,\n            conn_id: scid.to_vec(),\n            reset_token: reset_token.to_be_bytes(),\n        });\n    }\n\n    let pkt_type = Type::Short;\n\n    let written =\n        test_utils::encode_pkt(&mut pipe.client, pkt_type, &frames, &mut buf)\n            .unwrap();\n\n    let active_path = pipe.server.paths.get_active().unwrap();\n    let info = RecvInfo {\n        to: active_path.local_addr(),\n        from: active_path.peer_addr(),\n    };\n\n    assert_eq!(\n        pipe.server.recv(&mut buf[..written], info),\n        Err(Error::IdLimit)\n    );\n\n    let written = match pipe.server.send(&mut buf) {\n        Ok((write, _)) => write,\n\n        Err(_) => unreachable!(),\n    };\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..written]).unwrap();\n    let mut iter = frames.iter();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ConnectionClose {\n            error_code: 0x9,\n            frame_type: 0,\n            reason: Vec::new(),\n        })\n    );\n}\n\n#[rstest]\nfn connection_id_retire_exotic_sequence(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_uni(3);\n    config.set_initial_max_streams_bidi(3);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Inject an exotic sequence of NEW_CONNECTION_ID frames, unbeknowst to\n    // quiche client connection object.\n    let frames = [\n        frame::Frame::NewConnectionId {\n            seq_num: 8,\n            retire_prior_to: 1,\n            conn_id: vec![0],\n            reset_token: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],\n        },\n        frame::Frame::NewConnectionId {\n            seq_num: 1,\n            retire_prior_to: 0,\n            conn_id: vec![2],\n            reset_token: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2],\n        },\n        frame::Frame::NewConnectionId {\n            seq_num: 6,\n            retire_prior_to: 6,\n            conn_id: vec![0x15],\n            reset_token: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3],\n        },\n        frame::Frame::NewConnectionId {\n            seq_num: 8,\n            retire_prior_to: 1,\n            conn_id: vec![0],\n            reset_token: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4],\n        },\n        frame::Frame::NewConnectionId {\n            seq_num: 48,\n            retire_prior_to: 8,\n            conn_id: vec![1],\n            reset_token: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5],\n        },\n    ];\n\n    let pkt_type = Type::Short;\n    pipe.send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // Ensure operations continue to be allowed.\n    assert_eq!(pipe.client.stream_send(0, b\"data\", true), Ok(4));\n    assert_eq!(pipe.server.stream_send(1, b\"data\", true), Ok(4));\n    assert_eq!(pipe.client.stream_send(2, b\"data\", true), Ok(4));\n    assert_eq!(pipe.server.stream_send(3, b\"data\", true), Ok(4));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((4, true))\n    );\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 2),\n        Ok((4, true))\n    );\n\n    // The exotic sequence insertion messes with the client object's\n    // worldview so we can't check its side of things.\n}\n\n// Utility function.\nfn pipe_with_exchanged_cids(\n    config: &mut Config, client_scid_len: usize, server_scid_len: usize,\n    additional_cids: usize,\n) -> test_utils::Pipe {\n    let mut pipe = test_utils::Pipe::with_config_and_scid_lengths(\n        config,\n        client_scid_len,\n        server_scid_len,\n    )\n    .unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let mut c_cids = Vec::new();\n    let mut c_reset_tokens = Vec::new();\n    let mut s_cids = Vec::new();\n    let mut s_reset_tokens = Vec::new();\n\n    for i in 0..additional_cids {\n        if client_scid_len > 0 {\n            let (c_cid, c_reset_token) =\n                test_utils::create_cid_and_reset_token(client_scid_len);\n            c_cids.push(c_cid);\n            c_reset_tokens.push(c_reset_token);\n\n            assert_eq!(\n                pipe.client.new_scid(&c_cids[i], c_reset_tokens[i], true),\n                Ok(i as u64 + 1)\n            );\n        }\n\n        if server_scid_len > 0 {\n            let (s_cid, s_reset_token) =\n                test_utils::create_cid_and_reset_token(server_scid_len);\n            s_cids.push(s_cid);\n            s_reset_tokens.push(s_reset_token);\n            assert_eq!(\n                pipe.server.new_scid(&s_cids[i], s_reset_tokens[i], true),\n                Ok(i as u64 + 1)\n            );\n        }\n    }\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    if client_scid_len > 0 {\n        assert_eq!(pipe.server.available_dcids(), additional_cids);\n    }\n\n    if server_scid_len > 0 {\n        assert_eq!(pipe.client.available_dcids(), additional_cids);\n    }\n\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.path_event_next(), None);\n\n    pipe\n}\n\n#[rstest]\nfn path_validation(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n\n    // We cannot probe a new path if there are not enough identifiers.\n    assert_eq!(\n        pipe.client.probe_path(client_addr_2, server_addr),\n        Err(Error::OutOfIdentifiers)\n    );\n\n    let (c_cid, c_reset_token) = test_utils::create_cid_and_reset_token(16);\n\n    assert_eq!(pipe.client.new_scid(&c_cid, c_reset_token, true), Ok(1));\n\n    let (s_cid, s_reset_token) = test_utils::create_cid_and_reset_token(16);\n    assert_eq!(pipe.server.new_scid(&s_cid, s_reset_token, true), Ok(1));\n\n    // We need to exchange the CIDs first.\n    assert_eq!(\n        pipe.client.probe_path(client_addr_2, server_addr),\n        Err(Error::OutOfIdentifiers)\n    );\n\n    // Let exchange packets over the connection.\n    assert_eq!(pipe.advance(), Ok(()));\n\n    assert_eq!(pipe.server.available_dcids(), 1);\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(pipe.client.available_dcids(), 1);\n    assert_eq!(pipe.client.path_event_next(), None);\n\n    // Now the path probing can work.\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n\n    // But the server cannot probe a yet-unseen path.\n    assert_eq!(\n        pipe.server.probe_path(server_addr, client_addr_2),\n        Err(Error::InvalidState),\n    );\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // The path should be validated at some point.\n    assert_eq!(\n        pipe.client.path_event_next(),\n        Some(PathEvent::Validated(client_addr_2, server_addr)),\n    );\n    assert_eq!(pipe.client.path_event_next(), None);\n\n    // The server should be notified of this new path.\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::New(server_addr, client_addr_2)),\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::Validated(server_addr, client_addr_2)),\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n\n    // The server can later probe the path again.\n    assert_eq!(pipe.server.probe_path(server_addr, client_addr_2), Ok(1));\n\n    // This should not trigger any event at client side.\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(pipe.server.path_event_next(), None);\n}\n\n#[rstest]\nfn losing_probing_packets(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n\n    // The client creates the PATH CHALLENGE, but it is lost.\n    test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    // Wait until probing timer expires. Since the RTT is very low,\n    // wait a bit more.\n    let probed_pid = pipe\n        .client\n        .paths\n        .path_id_from_addrs(&(client_addr_2, server_addr))\n        .unwrap();\n    let probe_instant = pipe\n        .client\n        .paths\n        .get(probed_pid)\n        .unwrap()\n        .recovery\n        .loss_detection_timer()\n        .unwrap();\n    let timer = probe_instant.duration_since(Instant::now());\n    std::thread::sleep(timer + Duration::from_millis(1));\n\n    pipe.client.on_timeout();\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // The path should be validated at some point.\n    assert_eq!(\n        pipe.client.path_event_next(),\n        Some(PathEvent::Validated(client_addr_2, server_addr))\n    );\n    assert_eq!(pipe.client.path_event_next(), None);\n\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::New(server_addr, client_addr_2))\n    );\n    // The path should be validated at some point.\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::Validated(server_addr, client_addr_2))\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n}\n\n#[rstest]\nfn failed_path_validation(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n\n    for _ in 0..MAX_PROBING_TIMEOUTS {\n        // The client creates the PATH CHALLENGE, but it is always lost.\n        test_utils::emit_flight(&mut pipe.client).unwrap();\n\n        // Wait until probing timer expires. Since the RTT is very low,\n        // wait a bit more.\n        let probed_pid = pipe\n            .client\n            .paths\n            .path_id_from_addrs(&(client_addr_2, server_addr))\n            .unwrap();\n        let probe_instant = pipe\n            .client\n            .paths\n            .get(probed_pid)\n            .unwrap()\n            .recovery\n            .loss_detection_timer()\n            .unwrap();\n        let timer = probe_instant.duration_since(Instant::now());\n        std::thread::sleep(timer + Duration::from_millis(1));\n\n        pipe.client.on_timeout();\n    }\n\n    assert_eq!(\n        pipe.client.path_event_next(),\n        Some(PathEvent::FailedValidation(client_addr_2, server_addr)),\n    );\n}\n\n#[rstest]\nfn client_discard_unknown_address(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_uni(3);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Server sends stream data.\n    assert_eq!(pipe.server.stream_send(3, b\"a\", true), Ok(1));\n\n    let mut flight =\n        test_utils::emit_flight(&mut pipe.server).expect(\"no packet\");\n    // Let's change the address info.\n    flight\n        .iter_mut()\n        .for_each(|(_, si)| si.from = \"127.0.0.1:9292\".parse().unwrap());\n    assert_eq!(test_utils::process_flight(&mut pipe.client, flight), Ok(()));\n    assert_eq!(pipe.client.paths.len(), 1);\n}\n\n#[rstest]\nfn path_validation_limited_mtu(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n    // Limited MTU of 1199 bytes for some reason.\n    test_utils::process_flight(\n        &mut pipe.server,\n        test_utils::emit_flight_with_max_buffer(\n            &mut pipe.client,\n            1199,\n            None,\n            None,\n        )\n        .expect(\"no packet\"),\n    )\n    .expect(\"error when processing client packets\");\n    test_utils::process_flight(\n        &mut pipe.client,\n        test_utils::emit_flight(&mut pipe.server).expect(\"no packet\"),\n    )\n    .expect(\"error when processing client packets\");\n    let probed_pid = pipe\n        .client\n        .paths\n        .path_id_from_addrs(&(client_addr_2, server_addr))\n        .unwrap();\n    assert!(!pipe.client.paths.get(probed_pid).unwrap().validated(),);\n    assert_eq!(pipe.client.path_event_next(), None);\n    // Now let the client probe at its MTU.\n    assert_eq!(pipe.advance(), Ok(()));\n    assert!(pipe.client.paths.get(probed_pid).unwrap().validated());\n    assert_eq!(\n        pipe.client.path_event_next(),\n        Some(PathEvent::Validated(client_addr_2, server_addr))\n    );\n}\n\n#[rstest]\nfn path_probing_dos(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // The path should be validated at some point.\n    assert_eq!(\n        pipe.client.path_event_next(),\n        Some(PathEvent::Validated(client_addr_2, server_addr))\n    );\n    assert_eq!(pipe.client.path_event_next(), None);\n\n    // The server should be notified of this new path.\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::New(server_addr, client_addr_2))\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::Validated(server_addr, client_addr_2))\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n\n    assert_eq!(pipe.server.paths.len(), 2);\n\n    // Now forge a packet reusing the unverified path's CID over another\n    // 4-tuple.\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n    let client_addr_3 = \"127.0.0.1:9012\".parse().unwrap();\n    let mut flight =\n        test_utils::emit_flight(&mut pipe.client).expect(\"no generated packet\");\n    flight\n        .iter_mut()\n        .for_each(|(_, si)| si.from = client_addr_3);\n    test_utils::process_flight(&mut pipe.server, flight)\n        .expect(\"failed to process\");\n    assert_eq!(pipe.server.paths.len(), 2);\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::ReusedSourceConnectionId(\n            1,\n            (server_addr, client_addr_2),\n            (server_addr, client_addr_3)\n        ))\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n}\n\n#[rstest]\nfn retiring_active_path_dcid(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n\n    assert_eq!(pipe.client.retire_dcid(0), Err(Error::OutOfIdentifiers));\n}\n\n#[rstest]\nfn send_on_path_test(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_initial_max_data(100000);\n    config.set_initial_max_stream_data_bidi_local(100000);\n    config.set_initial_max_stream_data_bidi_remote(100000);\n    config.set_initial_max_streams_bidi(2);\n    config.set_active_connection_id_limit(4);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 3);\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr = test_utils::Pipe::client_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n\n    let mut buf = [0; 65535];\n    // There is nothing to send on the initial path.\n    assert_eq!(\n        pipe.client\n            .send_on_path(&mut buf, Some(client_addr), Some(server_addr)),\n        Err(Error::Done)\n    );\n\n    // Client should send padded PATH_CHALLENGE.\n    let (sent, si) = pipe\n        .client\n        .send_on_path(&mut buf, Some(client_addr_2), Some(server_addr))\n        .expect(\"No error\");\n    assert_eq!(sent, MIN_CLIENT_INITIAL_LEN);\n    assert_eq!(si.from, client_addr_2);\n    assert_eq!(si.to, server_addr);\n\n    let ri = RecvInfo {\n        to: si.to,\n        from: si.from,\n    };\n    assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent));\n\n    let stats = pipe.server.stats();\n    assert_eq!(stats.path_challenge_rx_count, 1);\n\n    // A non-existing 4-tuple raises an InvalidState.\n    let client_addr_3 = \"127.0.0.1:9012\".parse().unwrap();\n    let server_addr_2 = \"127.0.0.1:9876\".parse().unwrap();\n    assert_eq!(\n        pipe.client.send_on_path(\n            &mut buf,\n            Some(client_addr_3),\n            Some(server_addr)\n        ),\n        Err(Error::InvalidState)\n    );\n    assert_eq!(\n        pipe.client.send_on_path(\n            &mut buf,\n            Some(client_addr),\n            Some(server_addr_2)\n        ),\n        Err(Error::InvalidState)\n    );\n\n    // Let's introduce some additional path challenges and data exchange.\n    assert_eq!(pipe.client.probe_path(client_addr, server_addr_2), Ok(2));\n    assert_eq!(pipe.client.probe_path(client_addr_3, server_addr), Ok(3));\n    // Just to fit in two packets.\n    assert_eq!(pipe.client.stream_send(0, &buf[..1201], true), Ok(1201));\n\n    // PATH_CHALLENGE\n    let (sent, si) = pipe\n        .client\n        .send_on_path(&mut buf, Some(client_addr), None)\n        .expect(\"No error\");\n    assert_eq!(sent, MIN_CLIENT_INITIAL_LEN);\n    assert_eq!(si.from, client_addr);\n    assert_eq!(si.to, server_addr_2);\n\n    let ri = RecvInfo {\n        to: si.to,\n        from: si.from,\n    };\n    assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent));\n\n    let stats = pipe.server.stats();\n    assert_eq!(stats.path_challenge_rx_count, 2);\n\n    // STREAM frame on active path.\n    let (sent, si) = pipe\n        .client\n        .send_on_path(&mut buf, Some(client_addr), None)\n        .expect(\"No error\");\n    assert_eq!(si.from, client_addr);\n    assert_eq!(si.to, server_addr);\n\n    let ri = RecvInfo {\n        to: si.to,\n        from: si.from,\n    };\n    assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent));\n\n    let stats = pipe.server.stats();\n    assert_eq!(stats.path_challenge_rx_count, 2);\n\n    // PATH_CHALLENGE\n    let (sent, si) = pipe\n        .client\n        .send_on_path(&mut buf, None, Some(server_addr))\n        .expect(\"No error\");\n    assert_eq!(sent, MIN_CLIENT_INITIAL_LEN);\n    assert_eq!(si.from, client_addr_3);\n    assert_eq!(si.to, server_addr);\n\n    let ri = RecvInfo {\n        to: si.to,\n        from: si.from,\n    };\n    assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent));\n\n    let stats = pipe.server.stats();\n    assert_eq!(stats.path_challenge_rx_count, 3);\n\n    // STREAM frame on active path.\n    let (sent, si) = pipe\n        .client\n        .send_on_path(&mut buf, None, Some(server_addr))\n        .expect(\"No error\");\n    assert_eq!(si.from, client_addr);\n    assert_eq!(si.to, server_addr);\n\n    let ri = RecvInfo {\n        to: si.to,\n        from: si.from,\n    };\n    assert_eq!(pipe.server.recv(&mut buf[..sent], ri), Ok(sent));\n\n    // No more data to exchange leads to Error::Done.\n    assert_eq!(\n        pipe.client.send_on_path(&mut buf, Some(client_addr), None),\n        Err(Error::Done)\n    );\n    assert_eq!(\n        pipe.client.send_on_path(&mut buf, None, Some(server_addr)),\n        Err(Error::Done)\n    );\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let mut v1 = pipe.client.paths_iter(client_addr).collect::<Vec<_>>();\n    let mut v2 = vec![server_addr, server_addr_2];\n\n    v1.sort();\n    v2.sort();\n\n    assert_eq!(v1, v2);\n\n    let mut v1 = pipe.client.paths_iter(client_addr_2).collect::<Vec<_>>();\n    let mut v2 = vec![server_addr];\n\n    v1.sort();\n    v2.sort();\n\n    assert_eq!(v1, v2);\n\n    let mut v1 = pipe.client.paths_iter(client_addr_3).collect::<Vec<_>>();\n    let mut v2 = vec![server_addr];\n\n    v1.sort();\n    v2.sort();\n\n    assert_eq!(v1, v2);\n\n    let stats = pipe.server.stats();\n    assert_eq!(stats.path_challenge_rx_count, 3);\n}\n\n#[rstest]\nfn connection_migration(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(3);\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 2);\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n    let client_addr_3 = \"127.0.0.1:9012\".parse().unwrap();\n    let client_addr_4 = \"127.0.0.1:8908\".parse().unwrap();\n\n    // Case 1: the client first probes the new address, the server too, and\n    // then migrates.\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(\n        pipe.client.path_event_next(),\n        Some(PathEvent::Validated(client_addr_2, server_addr))\n    );\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::New(server_addr, client_addr_2))\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::Validated(server_addr, client_addr_2))\n    );\n    assert_eq!(\n        pipe.client.is_path_validated(client_addr_2, server_addr),\n        Ok(true)\n    );\n    assert_eq!(\n        pipe.server.is_path_validated(server_addr, client_addr_2),\n        Ok(true)\n    );\n    // The server can never initiates the connection migration.\n    assert_eq!(\n        pipe.server.migrate(server_addr, client_addr_2),\n        Err(Error::InvalidState)\n    );\n    assert_eq!(pipe.client.migrate(client_addr_2, server_addr), Ok(1));\n    assert_eq!(pipe.client.stream_send(0, b\"data\", true), Ok(4));\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        client_addr_2\n    );\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        server_addr\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::PeerMigrated(server_addr, client_addr_2))\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        server_addr\n    );\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        client_addr_2\n    );\n\n    // Case 2: the client migrates on a path that was not previously\n    // validated, and has spare SCIDs/DCIDs to do so.\n    assert_eq!(pipe.client.migrate(client_addr_3, server_addr), Ok(2));\n    assert_eq!(pipe.client.stream_send(4, b\"data\", true), Ok(4));\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        client_addr_3\n    );\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        server_addr\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::New(server_addr, client_addr_3))\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::Validated(server_addr, client_addr_3))\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::PeerMigrated(server_addr, client_addr_3))\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        server_addr\n    );\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        client_addr_3\n    );\n\n    // Case 3: the client tries to migrate on the current active path.\n    // This is not an error, but it triggers nothing.\n    assert_eq!(pipe.client.migrate(client_addr_3, server_addr), Ok(2));\n    assert_eq!(pipe.client.stream_send(8, b\"data\", true), Ok(4));\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        client_addr_3\n    );\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        server_addr\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        server_addr\n    );\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        client_addr_3\n    );\n\n    // Case 4: the client tries to migrate on a path that was not previously\n    // validated, and has no spare SCIDs/DCIDs. Prevent active migration.\n    assert_eq!(\n        pipe.client.migrate(client_addr_4, server_addr),\n        Err(Error::OutOfIdentifiers)\n    );\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        client_addr_3\n    );\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        server_addr\n    );\n}\n\n#[rstest]\nfn connection_migration_zero_length_cid(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 0, 16, 1);\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n\n    // The client migrates on a path that was not previously\n    // validated, and has spare SCIDs/DCIDs to do so.\n    assert_eq!(pipe.client.migrate(client_addr_2, server_addr), Ok(1));\n    assert_eq!(pipe.client.stream_send(4, b\"data\", true), Ok(4));\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        client_addr_2\n    );\n    assert_eq!(\n        pipe.client\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        server_addr\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::New(server_addr, client_addr_2))\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::Validated(server_addr, client_addr_2))\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::PeerMigrated(server_addr, client_addr_2))\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .local_addr(),\n        server_addr\n    );\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        client_addr_2\n    );\n}\n\n#[rstest]\nfn connection_migration_reordered_non_probing(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(2);\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);\n\n    let client_addr = test_utils::Pipe::client_addr();\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n\n    assert_eq!(pipe.client.probe_path(client_addr_2, server_addr), Ok(1));\n    assert_eq!(pipe.advance(), Ok(()));\n    assert_eq!(\n        pipe.client.path_event_next(),\n        Some(PathEvent::Validated(client_addr_2, server_addr))\n    );\n    assert_eq!(pipe.client.path_event_next(), None);\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::New(server_addr, client_addr_2))\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::Validated(server_addr, client_addr_2))\n    );\n    assert_eq!(pipe.server.path_event_next(), None);\n\n    // A first flight sent from secondary address.\n    assert_eq!(pipe.client.stream_send(0, b\"data\", true), Ok(4));\n    let mut first = test_utils::emit_flight(&mut pipe.client).unwrap();\n    first.iter_mut().for_each(|(_, si)| si.from = client_addr_2);\n    // A second one, but sent from the original one.\n    assert_eq!(pipe.client.stream_send(4, b\"data\", true), Ok(4));\n    let second = test_utils::emit_flight(&mut pipe.client).unwrap();\n    // Second flight is received before first one.\n    assert_eq!(test_utils::process_flight(&mut pipe.server, second), Ok(()));\n    assert_eq!(test_utils::process_flight(&mut pipe.server, first), Ok(()));\n\n    // Server does not perform connection migration because of packet\n    // reordering.\n    assert_eq!(pipe.server.path_event_next(), None);\n    assert_eq!(\n        pipe.server\n            .paths\n            .get_active()\n            .expect(\"no active\")\n            .peer_addr(),\n        client_addr\n    );\n}\n\n#[rstest]\nfn resilience_against_migration_attack(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(3);\n    config.set_initial_max_data(100000);\n    config.set_initial_max_stream_data_bidi_local(100000);\n    config.set_initial_max_stream_data_bidi_remote(100000);\n    config.set_initial_max_streams_bidi(2);\n\n    let mut pipe = pipe_with_exchanged_cids(&mut config, 16, 16, 1);\n\n    let client_addr = test_utils::Pipe::client_addr();\n    let server_addr = test_utils::Pipe::server_addr();\n    let spoofed_client_addr = \"127.0.0.1:6666\".parse().unwrap();\n\n    const DATA_BYTES: usize = 24000;\n    let buf = [42; DATA_BYTES];\n    let mut recv_buf = [0; DATA_BYTES];\n    let send1_bytes = pipe.server.stream_send(1, &buf, true).unwrap();\n    assert_eq!(send1_bytes, match cc_algorithm_name {\n        #[cfg(feature = \"openssl\")]\n        \"bbr2\" => 13966,\n        #[cfg(not(feature = \"openssl\"))]\n        \"bbr2\" => 13880,\n        #[cfg(feature = \"openssl\")]\n        \"bbr2_gcongestion\" => 13966,\n        #[cfg(not(feature = \"openssl\"))]\n        \"bbr2_gcongestion\" => 13880,\n        _ => 12000,\n    });\n    assert_eq!(\n        test_utils::process_flight(\n            &mut pipe.client,\n            test_utils::emit_flight(&mut pipe.server).unwrap()\n        ),\n        Ok(())\n    );\n    let (rcv_data_1, _) = pipe.client.stream_recv(1, &mut recv_buf).unwrap();\n\n    // Fake the source address of client.\n    let mut faked_addr_flight =\n        test_utils::emit_flight(&mut pipe.client).unwrap();\n    faked_addr_flight\n        .iter_mut()\n        .for_each(|(_, si)| si.from = spoofed_client_addr);\n    assert_eq!(\n        test_utils::process_flight(&mut pipe.server, faked_addr_flight),\n        Ok(())\n    );\n    assert_eq!(\n        pipe.server.stream_send(1, &buf[send1_bytes..], true),\n        Ok(24000 - send1_bytes)\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::ReusedSourceConnectionId(\n            0,\n            (server_addr, client_addr),\n            (server_addr, spoofed_client_addr)\n        ))\n    );\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::New(server_addr, spoofed_client_addr))\n    );\n\n    assert_eq!(\n        pipe.server.is_path_validated(server_addr, client_addr),\n        Ok(true)\n    );\n    assert_eq!(\n        pipe.server\n            .is_path_validated(server_addr, spoofed_client_addr),\n        Ok(false)\n    );\n\n    // The client creates the PATH CHALLENGE, but it is always lost.\n    test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    // Wait until probing timer expires. Since the RTT is very low,\n    // wait a bit more.\n    let probed_pid = pipe\n        .server\n        .paths\n        .path_id_from_addrs(&(server_addr, spoofed_client_addr))\n        .unwrap();\n    let probe_instant = pipe\n        .server\n        .paths\n        .get(probed_pid)\n        .unwrap()\n        .recovery\n        .loss_detection_timer()\n        .unwrap();\n    let timer = probe_instant.duration_since(Instant::now());\n    std::thread::sleep(timer + Duration::from_millis(1));\n\n    pipe.server.on_timeout();\n\n    // Because of the small ACK size, the server cannot send more to the\n    // client. Fallback on the previous active path.\n    assert_eq!(\n        pipe.server.path_event_next(),\n        Some(PathEvent::FailedValidation(\n            server_addr,\n            spoofed_client_addr\n        ))\n    );\n\n    assert_eq!(\n        pipe.server.is_path_validated(server_addr, client_addr),\n        Ok(true)\n    );\n    assert_eq!(\n        pipe.server\n            .is_path_validated(server_addr, spoofed_client_addr),\n        Ok(false)\n    );\n\n    let server_active_path = pipe.server.paths.get_active().unwrap();\n    assert_eq!(server_active_path.local_addr(), server_addr);\n    assert_eq!(server_active_path.peer_addr(), client_addr);\n    assert_eq!(pipe.advance(), Ok(()));\n    let (rcv_data_2, fin) = pipe.client.stream_recv(1, &mut recv_buf).unwrap();\n    assert!(fin);\n    assert_eq!(rcv_data_1 + rcv_data_2, DATA_BYTES);\n}\n\n#[rstest]\nfn consecutive_non_ack_eliciting(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends a bunch of PING frames, causing server to ACK (ACKs aren't\n    // ack-eliciting)\n    let frames = [frame::Frame::Ping { mtu_probe: None }];\n    let pkt_type = Type::Short;\n    for _ in 0..24 {\n        let len = pipe\n            .send_pkt_to_server(pkt_type, &frames, &mut buf)\n            .unwrap();\n        assert!(len > 0);\n\n        let frames =\n            test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n        assert!(\n            frames\n                .iter()\n                .all(|frame| matches!(frame, frame::Frame::ACK { .. })),\n            \"ACK only\"\n        );\n    }\n\n    // After 24 non-ack-eliciting, an ACK is explicitly elicited with a PING\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n    assert!(len > 0);\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    assert!(\n        frames\n            .iter()\n            .any(|frame| matches!(frame, frame::Frame::Ping { mtu_probe: None })),\n        \"found a PING\"\n    );\n}\n\n#[rstest]\nfn send_ack_eliciting_causes_ping(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    // First establish a connection\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Queue a PING frame\n    pipe.server.send_ack_eliciting().unwrap();\n\n    // Make sure ping is sent\n    let mut buf = [0; 1500];\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let mut iter = frames.iter();\n\n    assert_eq!(iter.next(), Some(&frame::Frame::Ping { mtu_probe: None }));\n}\n\n#[rstest]\nfn send_ack_eliciting_no_ping(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    // First establish a connection\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Queue a PING frame\n    pipe.server.send_ack_eliciting().unwrap();\n\n    // Send a stream frame, which is ACK-eliciting to make sure the ping is\n    // not sent\n    assert_eq!(pipe.server.stream_send(1, b\"a\", false), Ok(1));\n\n    // Make sure ping is not sent\n    let mut buf = [0; 1500];\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let mut iter = frames.iter();\n\n    assert!(matches!(\n        iter.next(),\n        Some(&frame::Frame::Stream {\n            stream_id: 1,\n            data: _\n        })\n    ));\n    assert!(iter.next().is_none());\n}\n\n/// Tests that streams do not keep being \"writable\" after being collected\n/// on reset.\n#[rstest]\nfn stop_sending_stream_send_after_reset_stream_ack(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n    #[values(true, false)] discard: bool,\n) {\n    let mut buf = [0; 65535];\n\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.set_initial_max_data(999999999);\n    config.set_initial_max_stream_data_bidi_local(30);\n    config.set_initial_max_stream_data_bidi_remote(30);\n    config.set_initial_max_stream_data_uni(30);\n    config.set_initial_max_streams_bidi(1000);\n    config.set_initial_max_streams_uni(0);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    assert_eq!(pipe.server.streams.len(), 0);\n    assert_eq!(pipe.server.readable().len(), 0);\n    assert_eq!(pipe.server.writable().len(), 0);\n\n    // Client opens a load of streams\n    assert_eq!(pipe.client.stream_send(0, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(4, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(8, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(12, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(16, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(20, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(24, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(28, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(32, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.client.stream_send(36, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server iterators are populated\n    let mut r = pipe.server.readable();\n    assert_eq!(r.len(), 10);\n    assert_eq!(r.next(), Some(0));\n    assert_eq!(r.next(), Some(4));\n    assert_eq!(r.next(), Some(8));\n    assert_eq!(r.next(), Some(12));\n    assert_eq!(r.next(), Some(16));\n    assert_eq!(r.next(), Some(20));\n    assert_eq!(r.next(), Some(24));\n    assert_eq!(r.next(), Some(28));\n    assert_eq!(r.next(), Some(32));\n    assert_eq!(r.next(), Some(36));\n\n    assert_eq!(r.next(), None);\n\n    let mut w = pipe.server.writable();\n    assert_eq!(w.len(), 10);\n    assert_eq!(w.next(), Some(0));\n    assert_eq!(w.next(), Some(4));\n    assert_eq!(w.next(), Some(8));\n    assert_eq!(w.next(), Some(12));\n    assert_eq!(w.next(), Some(16));\n    assert_eq!(w.next(), Some(20));\n    assert_eq!(w.next(), Some(24));\n    assert_eq!(w.next(), Some(28));\n    assert_eq!(w.next(), Some(32));\n    assert_eq!(w.next(), Some(36));\n    assert_eq!(w.next(), None);\n\n    // Read one stream\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, discard, 0),\n        Ok((5, true))\n    );\n    assert!(pipe.server.stream_finished(0));\n\n    assert_eq!(pipe.server.readable().len(), 9);\n    assert_eq!(pipe.server.writable().len(), 10);\n\n    assert_eq!(pipe.server.stream_writable(0, 0), Ok(true));\n\n    // Server sends data on stream 0, until blocked.\n    while pipe.server.stream_send(0, b\"world\", false) != Err(Error::Done) {\n        assert_eq!(pipe.advance(), Ok(()));\n    }\n\n    assert_eq!(pipe.server.writable().len(), 9);\n    assert_eq!(pipe.server.stream_writable(0, 0), Ok(true));\n\n    // Client sends STOP_SENDING.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    // Server sent a RESET_STREAM frame in response.\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    let mut iter = frames.iter();\n\n    // Skip ACK frame.\n    iter.next();\n\n    assert_eq!(\n        iter.next(),\n        Some(&frame::Frame::ResetStream {\n            stream_id: 0,\n            error_code: 42,\n            final_size: 30,\n        })\n    );\n\n    // Stream 0 is now writable in order to make apps aware of STOP_SENDING\n    // via returning an error.\n    let mut w = pipe.server.writable();\n    assert_eq!(w.len(), 10);\n\n    assert!(w.any(|s| s == 0));\n    assert_eq!(\n        pipe.server.stream_writable(0, 1),\n        Err(Error::StreamStopped(42))\n    );\n\n    // Returning `StreamStopped` causes the stream to be collected.\n    assert_eq!(pipe.server.streams.len(), 9);\n    assert_eq!(pipe.server.writable().len(), 9);\n\n    // Client acks RESET_STREAM frame.\n    let mut ranges = ranges::RangeSet::default();\n    ranges.insert(0..12);\n\n    let frames = [frame::Frame::ACK {\n        ack_delay: 15,\n        ranges,\n        ecn_counts: None,\n    }];\n\n    assert_eq!(pipe.send_pkt_to_server(pkt_type, &frames, &mut buf), Ok(0));\n\n    // Stream is collected on the server after RESET_STREAM is acked.\n    assert_eq!(pipe.server.streams.len(), 9);\n\n    // Sending STOP_SENDING again shouldn't trigger RESET_STREAM again.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    assert_eq!(frames.len(), 1);\n\n    match frames.first() {\n        Some(frame::Frame::ACK { .. }) => (),\n\n        f => panic!(\"expected ACK frame, got {f:?}\"),\n    };\n\n    assert_eq!(pipe.server.streams.len(), 9);\n\n    // Stream 0 has been collected and must not be writable anymore.\n    let mut w = pipe.server.writable();\n    assert_eq!(w.len(), 9);\n    assert!(!w.any(|s| s == 0));\n\n    // If we called send before the client ACK of reset stream, it would\n    // have failed with StreamStopped.\n    assert_eq!(pipe.server.stream_send(0, b\"world\", true), Err(Error::Done),);\n\n    // Stream 0 is still not writable.\n    let mut w = pipe.server.writable();\n    assert_eq!(w.len(), 9);\n    assert!(!w.any(|s| s == 0));\n}\n\n#[rstest]\nfn challenge_no_cids(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_active_connection_id_limit(4);\n    config.set_initial_max_data(30);\n    config.set_initial_max_stream_data_bidi_local(15);\n    config.set_initial_max_stream_data_bidi_remote(15);\n    config.set_initial_max_stream_data_uni(10);\n    config.set_initial_max_streams_bidi(3);\n\n    let mut pipe =\n        test_utils::Pipe::with_config_and_scid_lengths(&mut config, 16, 16)\n            .unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Server send CIDs to client\n    let mut server_cids = Vec::new();\n    for _ in 0..2 {\n        let (cid, reset_token) = test_utils::create_cid_and_reset_token(16);\n        pipe.server\n            .new_scid(&cid, reset_token, true)\n            .expect(\"server issue cid\");\n        server_cids.push(cid);\n    }\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let server_addr = test_utils::Pipe::server_addr();\n    let client_addr_2 = \"127.0.0.1:5678\".parse().unwrap();\n\n    // Client probes path before sending CIDs (simulating race condition)\n    let frames = [frame::Frame::PathChallenge {\n        data: [0, 1, 2, 3, 4, 5, 6, 7],\n    }];\n    let mut pkt_buf = [0u8; 1500];\n    let mut b = octets::OctetsMut::with_slice(&mut pkt_buf);\n    let epoch = Type::Short.to_epoch().unwrap();\n    let crypto_ctx = &mut pipe.client.crypto_ctx[epoch];\n    let pn = pipe.client.next_pkt_num;\n    let pn_len = 4;\n\n    let hdr = Header {\n        ty: Type::Short,\n        version: pipe.client.version,\n        dcid: server_cids[0].clone(),\n        scid: ConnectionId::from_ref(&[5, 4, 3, 2, 1]),\n        pkt_num: 0,\n        pkt_num_len: pn_len,\n        token: pipe.client.token.clone(),\n        versions: None,\n        key_phase: pipe.client.key_phase,\n    };\n    hdr.to_bytes(&mut b).expect(\"encode header\");\n    let payload_len = frames.iter().fold(0, |acc, x| acc + x.wire_len());\n    b.put_u32(pn as u32).expect(\"put pn\");\n\n    let payload_offset = b.off();\n\n    for frame in frames {\n        frame.to_bytes(&mut b).expect(\"encode frames\");\n    }\n\n    let aead = crypto_ctx.crypto_seal.as_mut().expect(\"crypto seal\");\n\n    let written = packet::encrypt_pkt(\n        &mut b,\n        pn,\n        pn_len,\n        payload_len,\n        payload_offset,\n        None,\n        aead,\n    )\n    .expect(\"packet encrypt\");\n    pipe.client.next_pkt_num += 1;\n\n    pipe.server\n        .recv(&mut pkt_buf[..written], RecvInfo {\n            to: server_addr,\n            from: client_addr_2,\n        })\n        .expect(\"server receive path challenge\");\n\n    // Show that the new path is not considered a destination path by quiche\n    assert!(!pipe\n        .server\n        .paths_iter(server_addr)\n        .any(|path| path == client_addr_2));\n}\n\n#[rstest]\nfn pmtud_probe_success(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config.set_cc_algorithm_name(cc_algorithm_name).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config.set_application_protos(&[b\"proto1\"]).unwrap();\n    config.verify_peer(false);\n    config.set_max_send_udp_payload_size(1400);\n    config.discover_pmtu(true);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Send probe and let it be acknowledged\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Verify probing is disabled after successful probe\n    let pmtud = pipe\n        .client\n        .paths\n        .get_active_mut()\n        .unwrap()\n        .pmtud\n        .as_mut()\n        .unwrap();\n    assert!(!pmtud.should_probe());\n\n    // Verify MTU was updated\n    let current_mtu = pmtud.get_current_mtu();\n    assert_eq!(current_mtu, 1400);\n\n    let path_stats = pipe.client.path_stats().next().unwrap();\n    assert_eq!(path_stats.pmtu, current_mtu);\n}\n\n#[rstest]\n/// This test verifies that multiple send() calls after handshake completion\n/// only generate one PMTUD probe packet, not multiple identical probes.\nfn pmtud_no_duplicate_probes(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config.set_cc_algorithm_name(cc_algorithm_name).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    config.verify_peer(false);\n    config.set_max_send_udp_payload_size(1400);\n    config.discover_pmtu(true);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Verify PMTUD is enabled and ready to probe\n    let pmtud = pipe\n        .client\n        .paths\n        .get_active_mut()\n        .unwrap()\n        .pmtud\n        .as_mut()\n        .unwrap();\n    assert!(pmtud.should_probe());\n    let initial_probe_size = pmtud.get_probe_size();\n    assert_eq!(initial_probe_size, 1400);\n\n    let mut frames: Vec<frame::Frame> = Vec::new();\n    for _ in 0..2 {\n        let mut buf = [0; 1400];\n        let (len, _) = pipe.client.send(&mut buf).unwrap();\n        frames.append(\n            test_utils::decode_pkt(&mut pipe.server, &mut buf[..len])\n                .unwrap()\n                .as_mut(),\n        );\n    }\n\n    assert_eq!(frames.len(), 3);\n    assert!(matches!(frames[0], frame::Frame::ACK { .. }));\n    assert!(matches!(frames[1], frame::Frame::Padding { .. }));\n    assert!(matches!(frames[2], frame::Frame::Ping { .. }));\n\n    let mut buf = [0; 1400];\n    assert_eq!(pipe.client.send(&mut buf).unwrap_err(), Error::Done);\n\n    // Verify probe flag was reset after sending\n    assert!(!pipe\n        .client\n        .paths\n        .get_active_mut()\n        .unwrap()\n        .pmtud\n        .as_mut()\n        .unwrap()\n        .should_probe());\n}\n\n#[rstest]\n/// Test that PMTUD retries with smaller probe size after loss.\nfn pmtud_probe_retry_after_loss(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut config = Config::new(PROTOCOL_VERSION).unwrap();\n    config.set_cc_algorithm_name(cc_algorithm_name).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config.set_application_protos(&[b\"proto1\"]).unwrap();\n    config.verify_peer(false);\n    config.set_max_send_udp_payload_size(1400);\n    config.discover_pmtu(true);\n    config.set_pmtud_max_probes(2);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Get initial probe size\n    let active_path = pipe.client.paths.get_active_mut().unwrap();\n    let initial_probe_size = active_path.pmtud.as_mut().unwrap().get_probe_size();\n    assert_eq!(initial_probe_size, 1400);\n\n    // Send first probe\n    let mut out = [0; 4096];\n    // ACK frame\n    let _ = pipe.client.send(&mut out).unwrap();\n    // PING + PADDING frames\n    let (len, _) = pipe.client.send(&mut out).unwrap();\n    assert_eq!(len, 1400);\n\n    // Verify probe flag was reset after sending\n    let pmtud = pipe\n        .client\n        .paths\n        .get_active_mut()\n        .unwrap()\n        .pmtud\n        .as_mut()\n        .unwrap();\n    assert!(!pmtud.should_probe());\n\n    // Simulate probe loss - need 2 failures (configured via set_pmtud_max_probes)\n    // before size changes. First failure - should retry at same size\n    pmtud.failed_probe(initial_probe_size);\n    assert_eq!(pmtud.get_current_mtu(), 1200);\n    assert!(pmtud.should_probe());\n    assert_eq!(pmtud.get_probe_size(), 1400); // Still trying 1400\n\n    // Second failure (max_probes reached) - now size should change\n    pmtud.failed_probe(initial_probe_size);\n    assert_eq!(pmtud.get_current_mtu(), 1200);\n    assert!(pmtud.should_probe());\n    assert_eq!(pmtud.get_probe_size(), 1300);\n\n    // Send second probe\n    let mut out = [0; 4096];\n    // PING + PADDING frames\n    let (len, _) = pipe.client.send(&mut out).unwrap();\n    assert_eq!(len, 1300);\n\n    // Verify should_probe flag gets reset\n    let pmtud = pipe\n        .client\n        .paths\n        .get_active_mut()\n        .unwrap()\n        .pmtud\n        .as_mut()\n        .unwrap();\n    assert!(!pmtud.should_probe());\n\n    // Simulate second probe loss (2 failures needed)\n    pmtud.failed_probe(1300);\n    pmtud.failed_probe(1300);\n\n    // Verify MTU is not updated\n    assert_eq!(pmtud.get_current_mtu(), 1200);\n\n    // Verify probe flag is re-enabled and probe size is reduced\n    assert!(pmtud.should_probe());\n    // Third probe should be 1250 bytes which is halfway between 1200 and the\n    // second probe size=1300.\n    assert_eq!(pmtud.get_probe_size(), 1250);\n\n    let path_stats = pipe.client.path_stats().next().unwrap();\n    assert_eq!(path_stats.pmtu, 1200);\n\n    // Make probes succeed til pmtu is found\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let pmtud = pipe\n        .client\n        .paths\n        .get_active_mut()\n        .unwrap()\n        .pmtud\n        .as_mut()\n        .unwrap();\n\n    // MTU should finally update\n    let current_mtu = pmtud.get_current_mtu();\n    assert_eq!(current_mtu, 1299);\n\n    // Verify should_probe gets reset\n    assert!(!pmtud.should_probe());\n\n    let path_stats = pipe.client.path_stats().next().unwrap();\n    assert_eq!(path_stats.pmtu, current_mtu);\n}\n\n#[cfg(feature = \"boringssl-boring-crate\")]\n#[rstest]\nfn enable_pmtud_mid_handshake(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    // Manually construct `SslContextBuilder` for the server so we can enable\n    // PMTUD during the handshake.\n    let mut server_tls_ctx_builder =\n        boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls())\n            .unwrap();\n    server_tls_ctx_builder\n        .set_certificate_chain_file(\"examples/cert.crt\")\n        .unwrap();\n    server_tls_ctx_builder\n        .set_private_key_file(\"examples/cert.key\", boring::ssl::SslFiletype::PEM)\n        .unwrap();\n    server_tls_ctx_builder.set_select_certificate_callback(|mut hello| {\n        <Connection>::set_discover_pmtu_in_handshake(hello.ssl_mut(), true, 1)\n            .unwrap();\n\n        Ok(())\n    });\n\n    let mut server_config = Config::with_boring_ssl_ctx_builder(\n        PROTOCOL_VERSION,\n        server_tls_ctx_builder,\n    )\n    .unwrap();\n    assert_eq!(\n        server_config.set_cc_algorithm_name(cc_algorithm_name),\n        Ok(())\n    );\n\n    let mut client_config = Config::new(PROTOCOL_VERSION).unwrap();\n    client_config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    client_config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n\n    for config in [&mut client_config, &mut server_config] {\n        config\n            .set_application_protos(&[b\"proto1\", b\"proto2\"])\n            .unwrap();\n        config.set_initial_max_data(1000000);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.set_max_idle_timeout(180_000);\n        config.verify_peer(false);\n        config.set_ack_delay_exponent(8);\n        config.set_max_send_udp_payload_size(1350);\n    }\n\n    let mut pipe = test_utils::Pipe::with_client_and_server_config(\n        &mut client_config,\n        &mut server_config,\n    )\n    .unwrap();\n\n    let active_path = pipe.server.paths.get_active_mut().unwrap();\n    assert!(active_path.pmtud.is_none());\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let active_path = pipe.server.paths.get_active_mut().unwrap();\n    assert!(active_path.pmtud.is_some());\n    assert_eq!(active_path.pmtud.as_mut().unwrap().get_current_mtu(), 1200);\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let current_mtu = pipe\n        .server\n        .paths\n        .get_active_mut()\n        .unwrap()\n        .pmtud\n        .as_mut()\n        .unwrap()\n        .get_current_mtu();\n    assert_eq!(current_mtu, 1350);\n\n    let path_stats = pipe.server.path_stats().next().unwrap();\n    assert_eq!(path_stats.pmtu, current_mtu);\n}\n\n#[cfg(feature = \"boringssl-boring-crate\")]\n#[rstest]\nfn disable_pmtud_mid_handshake(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    // Manually construct `SslContextBuilder` for the server so we can disable\n    // PMTUD during the handshake.\n    let mut server_tls_ctx_builder =\n        boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls())\n            .unwrap();\n    server_tls_ctx_builder\n        .set_certificate_chain_file(\"examples/cert.crt\")\n        .unwrap();\n    server_tls_ctx_builder\n        .set_private_key_file(\"examples/cert.key\", boring::ssl::SslFiletype::PEM)\n        .unwrap();\n    server_tls_ctx_builder.set_select_certificate_callback(|mut hello| {\n        <Connection>::set_discover_pmtu_in_handshake(hello.ssl_mut(), false, 0)\n            .unwrap();\n\n        Ok(())\n    });\n\n    let mut server_config = Config::with_boring_ssl_ctx_builder(\n        PROTOCOL_VERSION,\n        server_tls_ctx_builder,\n    )\n    .unwrap();\n    assert_eq!(\n        server_config.set_cc_algorithm_name(cc_algorithm_name),\n        Ok(())\n    );\n\n    let mut client_config = Config::new(PROTOCOL_VERSION).unwrap();\n    client_config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    client_config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n\n    for config in [&mut client_config, &mut server_config] {\n        config\n            .set_application_protos(&[b\"proto1\", b\"proto2\"])\n            .unwrap();\n        config.set_initial_max_data(1000000);\n        config.set_initial_max_stream_data_bidi_local(15);\n        config.set_initial_max_stream_data_bidi_remote(15);\n        config.set_initial_max_stream_data_uni(10);\n        config.set_initial_max_streams_bidi(3);\n        config.set_initial_max_streams_uni(3);\n        config.set_max_idle_timeout(180_000);\n        config.verify_peer(false);\n        config.set_ack_delay_exponent(8);\n        config.set_max_send_udp_payload_size(1350);\n        config.discover_pmtu(true);\n    }\n\n    let mut pipe = test_utils::Pipe::with_client_and_server_config(\n        &mut client_config,\n        &mut server_config,\n    )\n    .unwrap();\n\n    let active_path = pipe.server.paths.get_active_mut().unwrap();\n    assert!(active_path.pmtud.is_some());\n\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    let active_path = pipe.server.paths.get_active_mut().unwrap();\n    assert!(active_path.pmtud.is_none());\n\n    assert_eq!(pipe.advance(), Ok(()));\n\n    let active_path = pipe.server.paths.get_active_mut().unwrap();\n    assert!(active_path.pmtud.is_none());\n}\n\n#[rstest]\nfn configuration_values_are_limited_to_max_varint() {\n    let mut config = Config::new(0x1).unwrap();\n    config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    let v = octets::MAX_VAR_INT + 1;\n    let uv = v as usize;\n    config.set_max_idle_timeout(v);\n    config.set_max_recv_udp_payload_size(uv);\n    config.set_initial_max_data(v);\n    config.set_initial_max_stream_data_bidi_local(v);\n    config.set_initial_max_stream_data_bidi_remote(v);\n    config.set_initial_max_stream_data_uni(v);\n    config.set_initial_max_streams_bidi(v);\n    config.set_initial_max_streams_uni(v);\n    config.set_ack_delay_exponent(v);\n    config.set_max_ack_delay(v);\n    config.set_active_connection_id_limit(v);\n    config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe::with_client_config(&mut config).unwrap();\n    assert_eq!(\n        pipe.client.local_transport_params.max_idle_timeout,\n        octets::MAX_VAR_INT\n    );\n    assert_eq!(\n        pipe.client.local_transport_params.max_udp_payload_size,\n        cmp::min(octets::MAX_VAR_INT, uv as u64)\n    );\n    assert_eq!(\n        pipe.client.local_transport_params.initial_max_data,\n        octets::MAX_VAR_INT\n    );\n    assert_eq!(\n        pipe.client\n            .local_transport_params\n            .initial_max_stream_data_bidi_local,\n        octets::MAX_VAR_INT\n    );\n    assert_eq!(\n        pipe.client\n            .local_transport_params\n            .initial_max_stream_data_bidi_remote,\n        octets::MAX_VAR_INT\n    );\n    assert_eq!(\n        pipe.client\n            .local_transport_params\n            .initial_max_stream_data_uni,\n        octets::MAX_VAR_INT\n    );\n    assert_eq!(\n        pipe.client.local_transport_params.initial_max_streams_bidi,\n        octets::MAX_VAR_INT\n    );\n    assert_eq!(\n        pipe.client.local_transport_params.initial_max_streams_uni,\n        octets::MAX_VAR_INT\n    );\n    assert_eq!(\n        pipe.client.local_transport_params.ack_delay_exponent,\n        octets::MAX_VAR_INT\n    );\n    assert_eq!(\n        pipe.client.local_transport_params.active_conn_id_limit,\n        octets::MAX_VAR_INT\n    );\n\n    // It's fine that this will fail with an error. We just want to ensure we\n    // do not panic because of too large values that we try to encode via varint.\n    assert_eq!(pipe.handshake(), Err(Error::InvalidTransportParam));\n}\n\n#[cfg(feature = \"custom-client-dcid\")]\n#[rstest]\nfn connect_custom_client_dcid() {\n    let mut client_scid = [0; 16];\n    rand::rand_bytes(&mut client_scid[..]);\n    let client_scid = ConnectionId::from_ref(&client_scid);\n    let client_addr = \"127.0.0.1:1234\".parse().unwrap();\n\n    let mut server_scid = [0; 16];\n    rand::rand_bytes(&mut server_scid[..]);\n    let server_scid = ConnectionId::from_ref(&server_scid);\n    let server_addr = \"127.0.0.1:4321\".parse().unwrap();\n\n    // 8 is the minimum required.\n    let mut client_dcid = [0; 8];\n    rand::rand_bytes(&mut client_dcid[..]);\n    let client_dcid = ConnectionId::from_ref(&client_dcid);\n\n    let mut client_config = Config::new(PROTOCOL_VERSION).unwrap();\n    client_config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let client = connect_with_dcid(\n        Some(\"quic.tech\"),\n        &client_scid,\n        &client_dcid,\n        client_addr,\n        server_addr,\n        &mut client_config,\n    );\n\n    let mut server_config = Config::new(PROTOCOL_VERSION).unwrap();\n    server_config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    server_config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    server_config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n    server_config.verify_peer(false);\n\n    let mut pipe = test_utils::Pipe {\n        client: client.unwrap(),\n        server: accept(\n            &server_scid,\n            None,\n            server_addr,\n            client_addr,\n            &mut server_config,\n        )\n        .unwrap(),\n    };\n\n    // Client sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    let (pkt, _info) = flight.first().unwrap();\n    let header =\n        Header::from_slice(&mut pkt.to_vec()[..], client_dcid.len()).unwrap();\n\n    // Validate that the dcid is the same as the one we provided.\n    assert_eq!(client_dcid, header.dcid);\n\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    // Server sends initial flight.\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    assert!(!pipe.client.is_established());\n    assert!(!pipe.client.handshake_confirmed);\n\n    assert!(!pipe.server.is_established());\n    assert!(!pipe.server.handshake_confirmed);\n\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Client sends Handshake packet and completes handshake.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    assert!(pipe.client.is_established());\n    assert!(!pipe.client.handshake_confirmed);\n\n    assert!(!pipe.server.is_established());\n    assert!(!pipe.server.handshake_confirmed);\n\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    // Server completes and confirms handshake, and sends HANDSHAKE_DONE.\n    let flight = test_utils::emit_flight(&mut pipe.server).unwrap();\n\n    assert!(pipe.client.is_established());\n    assert!(!pipe.client.handshake_confirmed);\n\n    assert!(pipe.server.is_established());\n    assert!(pipe.server.handshake_confirmed);\n\n    test_utils::process_flight(&mut pipe.client, flight).unwrap();\n\n    // Client acks 1-RTT packet, and confirms handshake.\n    let flight = test_utils::emit_flight(&mut pipe.client).unwrap();\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.client.handshake_confirmed);\n\n    assert!(pipe.server.is_established());\n    assert!(pipe.server.handshake_confirmed);\n\n    test_utils::process_flight(&mut pipe.server, flight).unwrap();\n\n    assert!(pipe.client.is_established());\n    assert!(pipe.client.handshake_confirmed);\n\n    assert!(pipe.server.is_established());\n    assert!(pipe.server.handshake_confirmed);\n}\n\n#[rstest]\n/// Tests that RESET_STREAM is retransmitted when the packet containing it is\n/// lost, even if the stream has been collected.\nfn reset_stream_retransmit_after_stream_collected(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data on stream 0.\n    assert_eq!(pipe.client.stream_send(0, b\"hello\", true), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server receives and reads the data.\n    assert_eq!(\n        stream_recv_discard(&mut pipe.server, false, 0),\n        Ok((5, true))\n    );\n    assert!(pipe.server.stream_finished(0));\n\n    // Server sends data back on stream 0, until blocked by flow control.\n    while pipe.server.stream_send(0, b\"world\", false) != Err(Error::Done) {\n        assert_eq!(pipe.advance(), Ok(()));\n    }\n\n    // Client sends STOP_SENDING to trigger RESET_STREAM from server.\n    let frames = [frame::Frame::StopSending {\n        stream_id: 0,\n        error_code: 42,\n    }];\n\n    let pkt_type = Type::Short;\n\n    // send_pkt_to_server() causes the server to receive a STOP_SENDING and\n    // respond with a RESET_STREAM. However, we won't deliver the packet back to\n    // the client and instead just check its contents with decode_pkt()\n    let len = pipe\n        .send_pkt_to_server(pkt_type, &frames, &mut buf)\n        .unwrap();\n\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let has_reset_stream = frames.iter().any(|f| {\n        matches!(f, frame::Frame::ResetStream {\n            stream_id: 0,\n            error_code: 42,\n            final_size: 15,\n        })\n    });\n    assert!(has_reset_stream, \"Expected RESET_STREAM in response\");\n\n    // Now the application tries to write to the stream, which will return\n    // StreamStopped error and cause the stream to be collected.\n    assert_eq!(\n        pipe.server.stream_writable(0, 1),\n        Err(Error::StreamStopped(42))\n    );\n\n    // Stream 0 should now be collected.\n    assert_eq!(pipe.server.streams.len(), 0);\n\n    // Trigger loss detection for the first RESET_STREAM packet and subsequent\n    // retransmission. Confirm the new packet contains RESET_STREAM.\n    test_utils::trigger_ack_based_loss(&mut pipe.server, &mut pipe.client);\n\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let has_reset_stream = frames.iter().any(|f| {\n        matches!(f, frame::Frame::ResetStream {\n            stream_id: 0,\n            error_code: 42,\n            final_size: 15,\n        })\n    });\n\n    assert!(\n        has_reset_stream,\n        \"RESET_STREAM should be retransmitted even after stream is collected\"\n    );\n}\n\n#[rstest]\n/// Tests that STOP_SENDING is retransmitted when the packet containing it is\n/// lost.\nfn stop_sending_retransmit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data on stream 4 (no fin).\n    assert_eq!(pipe.client.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server receives the data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n\n    // Server shuts down the read side, triggering STOP_SENDING.\n    assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Read, 42), Ok(()));\n\n    // Server sends STOP_SENDING, but we don't deliver it (simulating loss).\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let has_stop_sending = frames.iter().any(|f| {\n        matches!(f, frame::Frame::StopSending {\n            stream_id: 4,\n            error_code: 42,\n        })\n    });\n    assert!(has_stop_sending, \"Expected STOP_SENDING in packet\");\n\n    // Trigger loss detection.\n    test_utils::trigger_ack_based_loss(&mut pipe.server, &mut pipe.client);\n\n    // Server should retransmit STOP_SENDING.\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n    let has_stop_sending = frames.iter().any(|f| {\n        matches!(f, frame::Frame::StopSending {\n            stream_id: 4,\n            error_code: 42,\n        })\n    });\n\n    assert!(has_stop_sending, \"STOP_SENDING should be retransmitted\");\n}\n\n#[rstest]\n/// Tests that STOP_SENDING is not retransmitted after the stream receives FIN,\n/// since there's no point asking the peer to stop sending when they already\n/// finished.\nfn stop_sending_no_retransmit_after_fin(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    let mut buf = [0; 65535];\n\n    let mut pipe = test_utils::Pipe::new(cc_algorithm_name).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client sends some data on stream 4 (no fin yet).\n    assert_eq!(pipe.client.stream_send(4, b\"hello\", false), Ok(5));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Server receives the data.\n    let mut r = pipe.server.readable();\n    assert_eq!(r.next(), Some(4));\n\n    // Server shuts down the read side, triggering STOP_SENDING.\n    assert_eq!(pipe.server.stream_shutdown(4, Shutdown::Read, 42), Ok(()));\n\n    // Server sends STOP_SENDING, but we don't deliver it (simulating loss).\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    // Verify STOP_SENDING is in the packet.\n    let has_stop_sending = frames.iter().any(|f| {\n        matches!(f, frame::Frame::StopSending {\n            stream_id: 4,\n            error_code: 42,\n        })\n    });\n    assert!(has_stop_sending, \"Expected STOP_SENDING in packet\");\n\n    // Now client sends FIN with **no data** on the stream. This forces the\n    // server into knowing the streams final size without needing to read any\n    // data.\n    assert_eq!(pipe.client.stream_send(4, b\"\", true), Ok(0));\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Trigger loss detection for the STOP_SENDING packet.\n    test_utils::trigger_ack_based_loss(&mut pipe.server, &mut pipe.client);\n\n    // Server should have nothing to send since STOP_SENDING should not be\n    // retransmitted after FIN.\n    assert_eq!(pipe.server.send(&mut buf), Err(Error::Done));\n}\n\n#[rstest]\n/// Tests that MAX_STREAMS_BIDI frames are retransmitted if lost\nfn max_streams_bidi_frame_retransmit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    const NUM_STREAMS: u64 = 4;\n    let mut buf = [0; 65535];\n\n    let mut config = test_utils::Pipe::default_config(cc_algorithm_name).unwrap();\n    config.set_initial_max_streams_bidi(NUM_STREAMS);\n    config.set_initial_max_streams_uni(0);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client opens all streams with FIN\n    for i in 0..NUM_STREAMS {\n        let stream_id = i * NUM_STREAMS;\n        pipe.client.stream_send(stream_id, b\"a\", true).unwrap();\n    }\n    pipe.advance().unwrap();\n\n    // Server reads all streams\n    for i in 0..NUM_STREAMS {\n        let stream_id = i * NUM_STREAMS;\n        pipe.server.stream_recv(stream_id, &mut buf).unwrap();\n    }\n\n    // Respond with stream FIN. However, stream is not collected until client\n    // ACKs the FIN.\n    pipe.server.stream_send(0, b\"a\", true).unwrap();\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    assert_eq!(pipe.server.streams.max_streams_bidi_next(), NUM_STREAMS);\n    assert_eq!(pipe.client.streams.peer_streams_left_bidi(), 0);\n\n    pipe.client_recv(&mut buf[..len]).unwrap();\n    let (len, _) = pipe.client.send(&mut buf).unwrap();\n    pipe.server_recv(&mut buf[..len]).unwrap();\n\n    // Stream 0 is now complete. Server should send MAX_STREAMS_BIDI.\n    assert_eq!(pipe.server.streams.max_streams_bidi_next(), NUM_STREAMS + 1);\n\n    // Capture MAX_STREAMS_BIDI packet (don't deliver to simulate loss)\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    // Verify MAX_STREAMS is in the packet.\n    let has_max_streams = frames\n        .iter()\n        .any(|f| matches!(f, frame::Frame::MaxStreamsBidi { max: 5 }));\n    assert!(has_max_streams, \"Expected MAX_STREAMS in packet\");\n    assert_eq!(pipe.client.streams.peer_streams_left_bidi(), 0);\n\n    // Trigger loss detection for retransmission\n    test_utils::trigger_ack_based_loss(&mut pipe.server, &mut pipe.client);\n\n    // Provide a buffer that is too small to generate the MAX_STREAMS\n    // frame. Make sure the frame is not lost.\n    assert_eq!(pipe.server.send(&mut buf[0..1]), Err(Error::Done));\n\n    // Server should retransmit MAX_STREAMS_BIDI\n    let (len, _) = pipe\n        .server\n        .send(&mut buf)\n        .expect(\"Expected a packet to carry MAX_STREAMS\");\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    // Verify MAX_STREAMS is in the packet.\n    let has_max_streams = frames\n        .iter()\n        .any(|f| matches!(f, frame::Frame::MaxStreamsBidi { max: 5 }));\n    assert!(has_max_streams, \"Expected MAX_STREAMS in packet\");\n}\n\n#[rstest]\n/// Tests that MAX_STREAMS_UNI frames are retransmitted if lost\nfn max_streams_uni_frame_retransmit(\n    #[values(\"cubic\", \"bbr2_gcongestion\")] cc_algorithm_name: &str,\n) {\n    const NUM_STREAMS: u64 = 4;\n    let mut buf = [0; 65535];\n\n    let mut config = test_utils::Pipe::default_config(cc_algorithm_name).unwrap();\n    config.set_initial_max_streams_bidi(0);\n    config.set_initial_max_streams_uni(4);\n\n    let mut pipe = test_utils::Pipe::with_config(&mut config).unwrap();\n    assert_eq!(pipe.handshake(), Ok(()));\n\n    // Client opens all unidirectional streams with FIN\n    for i in 0..NUM_STREAMS {\n        let stream_id = i * NUM_STREAMS + 2;\n        assert_eq!(pipe.client.stream_send(stream_id, b\"data\", true), Ok(4));\n    }\n    assert_eq!(pipe.advance(), Ok(()));\n\n    // Prior to reading, streams are not collected.\n    assert_eq!(pipe.server.streams.max_streams_uni_next(), NUM_STREAMS);\n\n    // Server receives all streams and triggers collection.\n    for i in 0..NUM_STREAMS {\n        let stream_id = i * NUM_STREAMS + 2;\n        pipe.server.stream_recv(stream_id, &mut buf).ok();\n    }\n\n    assert_eq!(pipe.server.streams.max_streams_uni_next(), NUM_STREAMS + 4);\n    assert_eq!(pipe.client.streams.peer_streams_left_uni(), 0);\n\n    // Server should want to send MAX_STREAMS_UNI.\n    // Capture the packet (don't deliver to client to simulate loss).\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    // Verify MAX_STREAMS is in the packet.\n    let has_max_streams = frames\n        .iter()\n        .any(|f| matches!(f, frame::Frame::MaxStreamsUni { max: 8 }));\n    assert!(has_max_streams, \"Expected MAX_STREAMS in packet\");\n\n    // Client's view is unchanged (packet was \"lost\")\n    assert_eq!(pipe.client.streams.peer_streams_left_uni(), 0);\n\n    // Trigger loss detection on server\n    test_utils::trigger_ack_based_loss(&mut pipe.server, &mut pipe.client);\n\n    // Provide a buffer that is too small to generate the MAX_STREAMS\n    // frame. Make sure the frame is not lost.\n    assert_eq!(pipe.server.send(&mut buf[0..1]), Err(Error::Done));\n\n    // Server should retransmit MAX_STREAMS_UNI\n    let (len, _) = pipe.server.send(&mut buf).unwrap();\n    let frames =\n        test_utils::decode_pkt(&mut pipe.client, &mut buf[..len]).unwrap();\n\n    // Verify MAX_STREAMS is in the packet.\n    let has_max_streams = frames\n        .iter()\n        .any(|f| matches!(f, frame::Frame::MaxStreamsUni { max: 8 }));\n    assert!(has_max_streams, \"Expected MAX_STREAMS in packet\");\n}\n\n#[cfg(feature = \"custom-client-dcid\")]\n#[rstest]\nfn connect_custom_client_dcid_too_short() {\n    let mut client_scid = [0; 16];\n    rand::rand_bytes(&mut client_scid[..]);\n    let client_scid = ConnectionId::from_ref(&client_scid);\n    let client_addr = \"127.0.0.1:1234\".parse().unwrap();\n\n    let server_addr = \"127.0.0.1:4321\".parse().unwrap();\n\n    // Just use something which is smaller than 8 (which is the minimum)\n    let mut client_dcid = [0; 6];\n    rand::rand_bytes(&mut client_dcid[..]);\n    let client_dcid = ConnectionId::from_ref(&client_dcid);\n\n    let mut client_config = Config::new(PROTOCOL_VERSION).unwrap();\n    client_config\n        .set_application_protos(&[b\"proto1\", b\"proto2\"])\n        .unwrap();\n\n    let client = connect_with_dcid(\n        Some(\"quic.tech\"),\n        &client_scid,\n        &client_dcid,\n        client_addr,\n        server_addr,\n        &mut client_config,\n    );\n    assert_eq!(client.err().unwrap(), Error::InvalidDcidInitialization);\n}\n"
  },
  {
    "path": "quiche/src/tls/boringssl.rs",
    "content": "use super::*;\n\nuse libc::c_long;\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct CRYPTO_BUFFER {\n    _unused: c_void,\n}\n\n#[repr(C)]\n#[allow(non_camel_case_types)]\npub(super) struct SSL_QUIC_METHOD {\n    set_read_secret: Option<\n        unsafe extern \"C\" fn(\n            ssl: *mut SSL,\n            level: crypto::Level,\n            cipher: *const SSL_CIPHER,\n            secret: *const u8,\n            secret_len: usize,\n        ) -> c_int,\n    >,\n\n    set_write_secret: Option<\n        unsafe extern \"C\" fn(\n            ssl: *mut SSL,\n            level: crypto::Level,\n            cipher: *const SSL_CIPHER,\n            secret: *const u8,\n            secret_len: usize,\n        ) -> c_int,\n    >,\n\n    add_handshake_data: Option<\n        unsafe extern \"C\" fn(\n            ssl: *mut SSL,\n            level: crypto::Level,\n            data: *const u8,\n            len: usize,\n        ) -> c_int,\n    >,\n\n    flush_flight: Option<extern \"C\" fn(ssl: *mut SSL) -> c_int>,\n\n    send_alert: Option<\n        extern \"C\" fn(ssl: *mut SSL, level: crypto::Level, alert: u8) -> c_int,\n    >,\n}\n\n#[cfg(test)]\n#[repr(C)]\n#[allow(non_camel_case_types)]\nstruct SSL_PRIVATE_KEY_METHOD {\n    sign: Option<\n        unsafe extern \"C\" fn(\n            ssl: *mut SSL,\n            out: *mut u8,\n            out_len: *mut usize,\n            max_out: usize,\n            signature_algorithm: u16,\n            r#in: *const u8,\n            in_len: usize,\n        ) -> ssl_private_key_result_t,\n    >,\n\n    decrypt: Option<\n        unsafe extern \"C\" fn(\n            ssl: *mut SSL,\n            out: *mut u8,\n            out_len: *mut usize,\n            max_out: usize,\n            r#in: *const u8,\n            in_len: usize,\n        ) -> ssl_private_key_result_t,\n    >,\n\n    complete: Option<\n        unsafe extern \"C\" fn(\n            ssl: *mut SSL,\n            out: *mut u8,\n            out_len: *mut usize,\n            max_out: usize,\n        ) -> ssl_private_key_result_t,\n    >,\n}\n\npub(super) static QUICHE_STREAM_METHOD: SSL_QUIC_METHOD = SSL_QUIC_METHOD {\n    set_read_secret: Some(set_read_secret),\n    set_write_secret: Some(set_write_secret),\n    add_handshake_data: Some(add_handshake_data),\n    flush_flight: Some(flush_flight),\n    send_alert: Some(send_alert),\n};\n\nimpl Context {\n    pub fn set_early_data_enabled(&mut self, _enabled: bool) {\n        unsafe {\n            SSL_CTX_set_early_data_enabled(\n                self.as_mut_ptr(),\n                i32::from(_enabled),\n            );\n        }\n    }\n}\n\nimpl Handshake {\n    pub fn set_quic_early_data_context(&mut self, context: &[u8]) -> Result<()> {\n        map_result(unsafe {\n            SSL_set_quic_early_data_context(\n                self.as_mut_ptr(),\n                context.as_ptr(),\n                context.len(),\n            )\n        })\n    }\n\n    pub fn set_session(&mut self, session: &[u8]) -> Result<()> {\n        unsafe {\n            let ctx = SSL_get_SSL_CTX(self.as_ptr());\n\n            if ctx.is_null() {\n                return Err(Error::TlsFail);\n            }\n\n            let session =\n                SSL_SESSION_from_bytes(session.as_ptr(), session.len(), ctx);\n\n            if session.is_null() {\n                return Err(Error::TlsFail);\n            }\n\n            let rc = SSL_set_session(self.as_mut_ptr(), session);\n            SSL_SESSION_free(session);\n\n            map_result(rc)\n        }\n    }\n\n    pub fn reset_early_data_reject(&mut self) {\n        unsafe { SSL_reset_early_data_reject(self.as_mut_ptr()) };\n    }\n\n    pub fn curve(&self) -> Option<String> {\n        let curve = unsafe {\n            let curve_id = SSL_get_curve_id(self.as_ptr());\n            if curve_id == 0 {\n                return None;\n            }\n\n            let curve_name = SSL_get_curve_name(curve_id);\n            match ffi::CStr::from_ptr(curve_name).to_str() {\n                Ok(v) => v,\n\n                Err(_) => return None,\n            }\n        };\n\n        Some(curve.to_string())\n    }\n\n    pub fn sigalg(&self) -> Option<String> {\n        let sigalg = unsafe {\n            let sigalg_id = SSL_get_peer_signature_algorithm(self.as_ptr());\n            if sigalg_id == 0 {\n                return None;\n            }\n\n            let sigalg_name = SSL_get_signature_algorithm_name(sigalg_id, 1);\n            match ffi::CStr::from_ptr(sigalg_name).to_str() {\n                Ok(v) => v,\n\n                Err(_) => return None,\n            }\n        };\n\n        Some(sigalg.to_string())\n    }\n\n    pub fn peer_cert_chain(&self) -> Option<Vec<&[u8]>> {\n        let cert_chain = unsafe {\n            let chain =\n                map_result_ptr(SSL_get0_peer_certificates(self.as_ptr())).ok()?;\n\n            let num = sk_num(chain);\n            if num == 0 {\n                return None;\n            }\n\n            let mut cert_chain = vec![];\n            for i in 0..num {\n                let buffer =\n                    map_result_ptr(sk_value(chain, i) as *const CRYPTO_BUFFER)\n                        .ok()?;\n\n                let out_len = CRYPTO_BUFFER_len(buffer);\n                if out_len == 0 {\n                    return None;\n                }\n\n                let out = CRYPTO_BUFFER_data(buffer);\n                let slice = slice::from_raw_parts(out, out_len);\n\n                cert_chain.push(slice);\n            }\n\n            cert_chain\n        };\n\n        Some(cert_chain)\n    }\n\n    pub fn peer_cert(&self) -> Option<&[u8]> {\n        let peer_cert = unsafe {\n            let chain =\n                map_result_ptr(SSL_get0_peer_certificates(self.as_ptr())).ok()?;\n            if sk_num(chain) == 0 {\n                return None;\n            }\n\n            let buffer =\n                map_result_ptr(sk_value(chain, 0) as *const CRYPTO_BUFFER)\n                    .ok()?;\n\n            let out_len = CRYPTO_BUFFER_len(buffer);\n            if out_len == 0 {\n                return None;\n            }\n\n            let out = CRYPTO_BUFFER_data(buffer);\n            slice::from_raw_parts(out, out_len)\n        };\n\n        Some(peer_cert)\n    }\n\n    // Only used for testing handling of failure during key signing.\n    #[cfg(test)]\n    pub fn set_failing_private_key_method(&mut self) {\n        extern \"C\" fn failing_sign(\n            _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize,\n            _signature_algorithm: u16, _in: *const u8, _in_len: usize,\n        ) -> ssl_private_key_result_t {\n            ssl_private_key_result_t::ssl_private_key_failure\n        }\n\n        extern \"C\" fn failing_decrypt(\n            _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize,\n            _in: *const u8, _in_len: usize,\n        ) -> ssl_private_key_result_t {\n            ssl_private_key_result_t::ssl_private_key_failure\n        }\n\n        extern \"C\" fn failing_complete(\n            _ssl: *mut SSL, _out: *mut u8, _out_len: *mut usize, _max_out: usize,\n        ) -> ssl_private_key_result_t {\n            ssl_private_key_result_t::ssl_private_key_failure\n        }\n\n        static QUICHE_PRIVATE_KEY_METHOD: SSL_PRIVATE_KEY_METHOD =\n            SSL_PRIVATE_KEY_METHOD {\n                decrypt: Some(failing_decrypt),\n                sign: Some(failing_sign),\n                complete: Some(failing_complete),\n            };\n\n        unsafe {\n            SSL_set_private_key_method(\n                self.as_mut_ptr(),\n                &QUICHE_PRIVATE_KEY_METHOD,\n            );\n        }\n    }\n\n    pub fn is_in_early_data(&self) -> bool {\n        unsafe { SSL_in_early_data(self.as_ptr()) == 1 }\n    }\n\n    pub fn early_data_reason(&self) -> u32 {\n        let reuse_reason_status =\n            unsafe { SSL_get_early_data_reason(self.as_ptr()) };\n        reuse_reason_status.0\n    }\n}\n\npub(super) fn get_session_bytes(session: *mut SSL_SESSION) -> Result<Vec<u8>> {\n    let session_bytes = unsafe {\n        let mut out: *mut u8 = ptr::null_mut();\n        let mut out_len: usize = 0;\n\n        if SSL_SESSION_to_bytes(session, &mut out, &mut out_len) == 0 {\n            return Err(Error::TlsFail);\n        }\n        let session_bytes = slice::from_raw_parts(out, out_len).to_vec();\n        OPENSSL_free(out as *mut c_void);\n        session_bytes\n    };\n\n    Ok(session_bytes)\n}\npub(super) const TLS_ERROR: c_int = 3;\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\n#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]\npub struct ssl_early_data_reason_t(pub ::std::os::raw::c_uint);\nextern \"C\" {\n    // SSL_METHOD specific for boringssl.\n    pub(super) fn SSL_CTX_set_tlsext_ticket_keys(\n        ctx: *mut SSL_CTX, key: *const u8, key_len: usize,\n    ) -> c_int;\n    fn SSL_CTX_set_early_data_enabled(ctx: *mut SSL_CTX, enabled: i32);\n\n    pub(super) fn SSL_CTX_set_session_cache_mode(\n        ctx: *mut SSL_CTX, mode: c_int,\n    ) -> c_int;\n    pub(super) fn SSL_get_ex_new_index(\n        argl: c_long, argp: *const c_void, unused: *const c_void,\n        dup_unused: *const c_void, free_func: *const c_void,\n    ) -> c_int;\n\n    fn SSL_get_curve_id(ssl: *const SSL) -> u16;\n    fn SSL_get_curve_name(curve: u16) -> *const c_char;\n\n    fn SSL_get_peer_signature_algorithm(ssl: *const SSL) -> u16;\n    fn SSL_get_signature_algorithm_name(\n        sigalg: u16, include_curve: i32,\n    ) -> *const c_char;\n\n    fn SSL_get0_peer_certificates(ssl: *const SSL) -> *const STACK_OF;\n\n    pub(super) fn SSL_set_min_proto_version(ssl: *mut SSL, version: u16)\n        -> c_int;\n\n    pub(super) fn SSL_set_max_proto_version(ssl: *mut SSL, version: u16)\n        -> c_int;\n\n    pub(super) fn SSL_set_tlsext_host_name(\n        ssl: *mut SSL, name: *const c_char,\n    ) -> c_int;\n\n    fn SSL_set_quic_early_data_context(\n        ssl: *mut SSL, context: *const u8, context_len: usize,\n    ) -> c_int;\n\n    #[cfg(test)]\n    fn SSL_set_private_key_method(\n        ssl: *mut SSL, key_method: *const SSL_PRIVATE_KEY_METHOD,\n    );\n\n    fn SSL_reset_early_data_reject(ssl: *mut SSL);\n\n    fn SSL_in_early_data(ssl: *const SSL) -> c_int;\n\n    fn SSL_get_early_data_reason(ssl: *const SSL) -> ssl_early_data_reason_t;\n\n    fn SSL_SESSION_to_bytes(\n        session: *const SSL_SESSION, out: *mut *mut u8, out_len: *mut usize,\n    ) -> c_int;\n\n    fn SSL_SESSION_from_bytes(\n        input: *const u8, input_len: usize, ctx: *const SSL_CTX,\n    ) -> *mut SSL_SESSION;\n\n    // STACK_OF\n\n    fn sk_num(stack: *const STACK_OF) -> usize;\n\n    fn sk_value(stack: *const STACK_OF, idx: usize) -> *mut c_void;\n\n    // CRYPTO_BUFFER\n\n    fn CRYPTO_BUFFER_len(buffer: *const CRYPTO_BUFFER) -> usize;\n\n    fn CRYPTO_BUFFER_data(buffer: *const CRYPTO_BUFFER) -> *const u8;\n}\n"
  },
  {
    "path": "quiche/src/tls/mod.rs",
    "content": "// Copyright (C) 2018-2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::ffi;\nuse std::ptr;\nuse std::slice;\n\nuse std::io::Write;\n\nuse std::sync::LazyLock;\n\nuse libc::c_char;\nuse libc::c_int;\nuse libc::c_uint;\nuse libc::c_void;\n\nuse crate::Error;\nuse crate::Result;\n\nuse crate::Connection;\nuse crate::ConnectionError;\n\nuse crate::crypto;\nuse crate::packet;\n\nconst TLS1_3_VERSION: u16 = 0x0304;\nconst TLS_ALERT_ERROR: u64 = 0x100;\nconst INTERNAL_ERROR: u64 = 0x01;\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct SSL_METHOD {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct SSL_CTX {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct SSL {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct SSL_CIPHER {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct SSL_SESSION {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct X509_VERIFY_PARAM {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\n#[cfg(windows)]\nstruct X509_STORE {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct X509_STORE_CTX {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\n#[cfg(windows)]\nstruct X509 {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct STACK_OF {\n    _unused: c_void,\n}\n\n#[cfg(test)]\n#[repr(C)]\n#[allow(non_camel_case_types)]\n#[allow(dead_code)]\nenum ssl_private_key_result_t {\n    ssl_private_key_success,\n    ssl_private_key_retry,\n    ssl_private_key_failure,\n}\n\n/// BoringSSL ex_data index for quiche connections.\npub static QUICHE_EX_DATA_INDEX: LazyLock<c_int> = LazyLock::new(|| unsafe {\n    SSL_get_ex_new_index(0, ptr::null(), ptr::null(), ptr::null(), ptr::null())\n});\n\npub struct Context(*mut SSL_CTX);\n\nimpl Context {\n    // Note: some vendor-specific methods are implemented by each vendor's\n    // submodule (openssl-quictls / boringssl).\n    pub fn new() -> Result<Context> {\n        unsafe {\n            let ctx_raw = SSL_CTX_new(TLS_method());\n\n            let mut ctx = Context(ctx_raw);\n\n            ctx.set_session_callback();\n\n            ctx.load_ca_certs()?;\n\n            Ok(ctx)\n        }\n    }\n\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    pub fn from_boring(\n        ssl_ctx_builder: boring::ssl::SslContextBuilder,\n    ) -> Context {\n        use foreign_types_shared::ForeignType;\n\n        let mut ctx = Context(ssl_ctx_builder.build().into_ptr() as _);\n        ctx.set_session_callback();\n\n        ctx\n    }\n\n    pub fn new_handshake(&mut self) -> Result<Handshake> {\n        unsafe {\n            let ssl = SSL_new(self.as_mut_ptr());\n            Ok(Handshake::new(ssl))\n        }\n    }\n\n    pub fn load_verify_locations_from_file(&mut self, file: &str) -> Result<()> {\n        let file = ffi::CString::new(file).map_err(|_| Error::TlsFail)?;\n        map_result(unsafe {\n            SSL_CTX_load_verify_locations(\n                self.as_mut_ptr(),\n                file.as_ptr(),\n                ptr::null(),\n            )\n        })\n    }\n\n    pub fn load_verify_locations_from_directory(\n        &mut self, path: &str,\n    ) -> Result<()> {\n        let path = ffi::CString::new(path).map_err(|_| Error::TlsFail)?;\n        map_result(unsafe {\n            SSL_CTX_load_verify_locations(\n                self.as_mut_ptr(),\n                ptr::null(),\n                path.as_ptr(),\n            )\n        })\n    }\n\n    pub fn use_certificate_chain_file(&mut self, file: &str) -> Result<()> {\n        let cstr = ffi::CString::new(file).map_err(|_| Error::TlsFail)?;\n        map_result(unsafe {\n            SSL_CTX_use_certificate_chain_file(self.as_mut_ptr(), cstr.as_ptr())\n        })\n    }\n\n    pub fn use_privkey_file(&mut self, file: &str) -> Result<()> {\n        let cstr = ffi::CString::new(file).map_err(|_| Error::TlsFail)?;\n        map_result(unsafe {\n            SSL_CTX_use_PrivateKey_file(self.as_mut_ptr(), cstr.as_ptr(), 1)\n        })\n    }\n\n    #[cfg(not(windows))]\n    fn load_ca_certs(&mut self) -> Result<()> {\n        unsafe { map_result(SSL_CTX_set_default_verify_paths(self.as_mut_ptr())) }\n    }\n\n    #[cfg(windows)]\n    fn load_ca_certs(&mut self) -> Result<()> {\n        unsafe {\n            let cstr = ffi::CString::new(\"Root\").map_err(|_| Error::TlsFail)?;\n            let sys_store =\n                windows_sys::Win32::Security::Cryptography::CertOpenSystemStoreA(\n                    0,\n                    cstr.as_ptr() as windows_sys::core::PCSTR,\n                );\n            if sys_store.is_null() {\n                return Err(Error::TlsFail);\n            }\n\n            let ctx_store = SSL_CTX_get_cert_store(self.as_mut_ptr());\n            if ctx_store.is_null() {\n                return Err(Error::TlsFail);\n            }\n\n            let mut ctx_p = windows_sys::Win32::Security::Cryptography::CertEnumCertificatesInStore(\n                sys_store,\n                ptr::null(),\n            );\n\n            while !ctx_p.is_null() {\n                let in_p = (*ctx_p).pbCertEncoded as *const u8;\n\n                let cert = d2i_X509(\n                    ptr::null_mut(),\n                    &in_p,\n                    (*ctx_p).cbCertEncoded as i32,\n                );\n                if !cert.is_null() {\n                    X509_STORE_add_cert(ctx_store, cert);\n                }\n\n                X509_free(cert);\n\n                ctx_p = windows_sys::Win32::Security::Cryptography::CertEnumCertificatesInStore(\n                    sys_store, ctx_p,\n                );\n            }\n\n            // tidy up\n            windows_sys::Win32::Security::Cryptography::CertFreeCertificateContext(ctx_p);\n            windows_sys::Win32::Security::Cryptography::CertCloseStore(\n                sys_store, 0,\n            );\n        }\n\n        Ok(())\n    }\n\n    fn set_session_callback(&mut self) {\n        unsafe {\n            // This is needed to enable the session callback on the client. On\n            // the server it doesn't do anything.\n            SSL_CTX_set_session_cache_mode(\n                self.as_mut_ptr(),\n                0x0001, // SSL_SESS_CACHE_CLIENT\n            );\n\n            SSL_CTX_sess_set_new_cb(self.as_mut_ptr(), Some(new_session));\n        };\n    }\n\n    pub fn set_verify(&mut self, verify: bool) {\n        // true  -> 0x01 SSL_VERIFY_PEER\n        // false -> 0x00 SSL_VERIFY_NONE\n        let mode = i32::from(verify);\n\n        // Note: Base on two used modes(see above), it seems ok for both, bssl and\n        // ossl. If mode needs to be ored then it may need to be adjusted.\n        unsafe {\n            SSL_CTX_set_verify(self.as_mut_ptr(), mode, None);\n        }\n    }\n\n    pub fn enable_keylog(&mut self) {\n        unsafe {\n            SSL_CTX_set_keylog_callback(self.as_mut_ptr(), Some(keylog));\n        }\n    }\n\n    pub fn set_alpn(&mut self, v: &[&[u8]]) -> Result<()> {\n        let mut protos: Vec<u8> = Vec::new();\n\n        for proto in v {\n            protos.push(proto.len() as u8);\n            protos.extend_from_slice(proto);\n        }\n\n        // Configure ALPN for servers.\n        unsafe {\n            SSL_CTX_set_alpn_select_cb(\n                self.as_mut_ptr(),\n                Some(select_alpn),\n                ptr::null_mut(),\n            );\n        }\n\n        // Configure ALPN for clients.\n        map_result_zero_is_success(unsafe {\n            SSL_CTX_set_alpn_protos(\n                self.as_mut_ptr(),\n                protos.as_ptr(),\n                protos.len(),\n            )\n        })\n    }\n\n    pub fn set_ticket_key(&mut self, key: &[u8]) -> Result<()> {\n        map_result(unsafe {\n            SSL_CTX_set_tlsext_ticket_keys(\n                self.as_mut_ptr(),\n                key.as_ptr(),\n                key.len(),\n            )\n        })\n    }\n\n    fn as_mut_ptr(&mut self) -> *mut SSL_CTX {\n        self.0\n    }\n}\n\n// NOTE: These traits are not automatically implemented for Context due to the\n// raw pointer it wraps. However, the underlying data is not aliased (as Context\n// should be its only owner), and there is no interior mutability, as the\n// pointer is not accessed directly outside of this module, and the Context\n// object API should preserve Rust's borrowing guarantees.\nunsafe impl Send for Context {}\nunsafe impl Sync for Context {}\n\nimpl Drop for Context {\n    fn drop(&mut self) {\n        unsafe { SSL_CTX_free(self.as_mut_ptr()) }\n    }\n}\n\npub struct Handshake {\n    /// Raw pointer\n    ptr: *mut SSL,\n    /// SSL_process_quic_post_handshake should be called when whenever\n    /// SSL_provide_quic_data is called to process the provided data.\n    provided_data_outstanding: bool,\n}\n\nimpl Handshake {\n    // Note: some vendor-specific methods are implemented by each vendor's\n    // submodule (openssl-quictls / boringssl).\n    #[cfg(any(feature = \"ffi\", feature = \"boringssl-boring-crate\"))]\n    pub unsafe fn from_ptr(ssl: *mut c_void) -> Handshake {\n        Handshake::new(ssl as *mut SSL)\n    }\n\n    fn new(ptr: *mut SSL) -> Handshake {\n        Handshake {\n            ptr,\n            provided_data_outstanding: false,\n        }\n    }\n\n    pub fn get_error(&self, ret_code: c_int) -> c_int {\n        unsafe { SSL_get_error(self.as_ptr(), ret_code) }\n    }\n\n    pub fn init(&mut self, is_server: bool) -> Result<()> {\n        self.set_state(is_server);\n\n        self.set_min_proto_version(TLS1_3_VERSION)?;\n        self.set_max_proto_version(TLS1_3_VERSION)?;\n\n        self.set_quic_method()?;\n\n        // TODO: the early data context should include transport parameters and\n        // HTTP/3 SETTINGS in wire format.\n        self.set_quic_early_data_context(b\"quiche\")?;\n\n        self.set_quiet_shutdown(true);\n\n        Ok(())\n    }\n\n    pub fn use_legacy_codepoint(&mut self, use_legacy: bool) {\n        unsafe {\n            SSL_set_quic_use_legacy_codepoint(\n                self.as_mut_ptr(),\n                use_legacy as c_int,\n            );\n        }\n    }\n\n    pub fn set_state(&mut self, is_server: bool) {\n        unsafe {\n            if is_server {\n                SSL_set_accept_state(self.as_mut_ptr());\n            } else {\n                SSL_set_connect_state(self.as_mut_ptr());\n            }\n        }\n    }\n\n    pub fn set_ex_data<T>(&mut self, idx: c_int, data: *const T) -> Result<()> {\n        map_result(unsafe {\n            let ptr = data as *mut c_void;\n            SSL_set_ex_data(self.as_mut_ptr(), idx, ptr)\n        })\n    }\n\n    pub fn set_quic_method(&mut self) -> Result<()> {\n        map_result(unsafe {\n            SSL_set_quic_method(self.as_mut_ptr(), &QUICHE_STREAM_METHOD)\n        })\n    }\n\n    pub fn set_min_proto_version(&mut self, version: u16) -> Result<()> {\n        map_result(unsafe {\n            SSL_set_min_proto_version(self.as_mut_ptr(), version)\n        })\n    }\n\n    pub fn set_max_proto_version(&mut self, version: u16) -> Result<()> {\n        map_result(unsafe {\n            SSL_set_max_proto_version(self.as_mut_ptr(), version)\n        })\n    }\n\n    pub fn set_quiet_shutdown(&mut self, mode: bool) {\n        unsafe { SSL_set_quiet_shutdown(self.as_mut_ptr(), i32::from(mode)) }\n    }\n\n    pub fn set_host_name(&mut self, name: &str) -> Result<()> {\n        let cstr = ffi::CString::new(name).map_err(|_| Error::TlsFail)?;\n        let rc =\n            unsafe { SSL_set_tlsext_host_name(self.as_mut_ptr(), cstr.as_ptr()) };\n        self.map_result_ssl(rc)?;\n\n        let param = unsafe { SSL_get0_param(self.as_mut_ptr()) };\n\n        map_result(unsafe {\n            X509_VERIFY_PARAM_set1_host(param, cstr.as_ptr(), name.len())\n        })\n    }\n\n    pub fn set_quic_transport_params(\n        &mut self, params: &crate::TransportParams, is_server: bool,\n    ) -> Result<()> {\n        let mut raw_params = [0; 128];\n\n        let raw_params =\n            crate::TransportParams::encode(params, is_server, &mut raw_params)?;\n\n        let rc = unsafe {\n            SSL_set_quic_transport_params(\n                self.as_mut_ptr(),\n                raw_params.as_ptr(),\n                raw_params.len(),\n            )\n        };\n        self.map_result_ssl(rc)\n    }\n\n    pub fn quic_transport_params(&self) -> &[u8] {\n        let mut ptr: *const u8 = ptr::null();\n        let mut len: usize = 0;\n\n        unsafe {\n            SSL_get_peer_quic_transport_params(self.as_ptr(), &mut ptr, &mut len);\n        }\n\n        if len == 0 {\n            return &mut [];\n        }\n\n        unsafe { slice::from_raw_parts(ptr, len) }\n    }\n\n    pub fn alpn_protocol(&self) -> &[u8] {\n        let mut ptr: *const u8 = ptr::null();\n        let mut len: u32 = 0;\n\n        unsafe {\n            SSL_get0_alpn_selected(self.as_ptr(), &mut ptr, &mut len);\n        }\n\n        if len == 0 {\n            return &mut [];\n        }\n\n        unsafe { slice::from_raw_parts(ptr, len as usize) }\n    }\n\n    pub fn server_name(&self) -> Option<&str> {\n        let s = unsafe {\n            let ptr = SSL_get_servername(\n                self.as_ptr(),\n                0, // TLSEXT_NAMETYPE_host_name\n            );\n\n            if ptr.is_null() {\n                return None;\n            }\n\n            ffi::CStr::from_ptr(ptr)\n        };\n\n        s.to_str().ok()\n    }\n\n    pub fn provide_data(\n        &mut self, level: crypto::Level, buf: &[u8],\n    ) -> Result<()> {\n        self.provided_data_outstanding = true;\n        let rc = unsafe {\n            SSL_provide_quic_data(\n                self.as_mut_ptr(),\n                level,\n                buf.as_ptr(),\n                buf.len(),\n            )\n        };\n        self.map_result_ssl(rc)\n    }\n\n    pub fn do_handshake(&mut self, ex_data: &mut ExData) -> Result<()> {\n        self.set_ex_data(*QUICHE_EX_DATA_INDEX, ex_data)?;\n        let rc = unsafe { SSL_do_handshake(self.as_mut_ptr()) };\n        self.set_ex_data::<Connection>(*QUICHE_EX_DATA_INDEX, ptr::null())?;\n\n        self.set_transport_error(ex_data, rc);\n        self.map_result_ssl(rc)\n    }\n\n    pub fn process_post_handshake(&mut self, ex_data: &mut ExData) -> Result<()> {\n        // If SSL_provide_quic_data hasn't been called since we last called\n        // SSL_process_quic_post_handshake, then there's nothing to do.\n        if !self.provided_data_outstanding {\n            return Ok(());\n        }\n        self.provided_data_outstanding = false;\n\n        self.set_ex_data(*QUICHE_EX_DATA_INDEX, ex_data)?;\n        let rc = unsafe { SSL_process_quic_post_handshake(self.as_mut_ptr()) };\n        self.set_ex_data::<Connection>(*QUICHE_EX_DATA_INDEX, ptr::null())?;\n\n        self.set_transport_error(ex_data, rc);\n        self.map_result_ssl(rc)\n    }\n\n    pub fn write_level(&self) -> crypto::Level {\n        unsafe { SSL_quic_write_level(self.as_ptr()) }\n    }\n\n    pub fn cipher(&self) -> Option<crypto::Algorithm> {\n        let cipher =\n            map_result_ptr(unsafe { SSL_get_current_cipher(self.as_ptr()) });\n\n        get_cipher_from_ptr(cipher.ok()?).ok()\n    }\n\n    #[cfg(test)]\n    pub fn set_options(&mut self, opts: u32) {\n        unsafe {\n            SSL_set_options(self.as_mut_ptr(), opts);\n        }\n    }\n\n    pub fn is_completed(&self) -> bool {\n        unsafe { SSL_in_init(self.as_ptr()) == 0 }\n    }\n\n    pub fn is_resumed(&self) -> bool {\n        unsafe { SSL_session_reused(self.as_ptr()) == 1 }\n    }\n\n    pub fn clear(&mut self) -> Result<()> {\n        let rc = unsafe { SSL_clear(self.as_mut_ptr()) };\n        self.map_result_ssl(rc)\n    }\n\n    fn as_ptr(&self) -> *const SSL {\n        self.ptr\n    }\n\n    fn as_mut_ptr(&mut self) -> *mut SSL {\n        self.ptr\n    }\n\n    fn map_result_ssl(&mut self, bssl_result: c_int) -> Result<()> {\n        match bssl_result {\n            1 => Ok(()),\n\n            _ => {\n                let ssl_err = self.get_error(bssl_result);\n                match ssl_err {\n                    // SSL_ERROR_SSL\n                    1 => {\n                        log_ssl_error();\n\n                        Err(Error::TlsFail)\n                    },\n\n                    // SSL_ERROR_WANT_READ\n                    2 => Err(Error::Done),\n\n                    // SSL_ERROR_WANT_WRITE\n                    3 => Err(Error::Done),\n\n                    // SSL_ERROR_WANT_X509_LOOKUP\n                    4 => Err(Error::Done),\n\n                    // SSL_ERROR_SYSCALL\n                    5 => Err(Error::TlsFail),\n\n                    // SSL_ERROR_PENDING_SESSION\n                    11 => Err(Error::Done),\n\n                    // SSL_ERROR_PENDING_CERTIFICATE\n                    12 => Err(Error::Done),\n\n                    // SSL_ERROR_WANT_PRIVATE_KEY_OPERATION\n                    13 => Err(Error::Done),\n\n                    // SSL_ERROR_PENDING_TICKET\n                    14 => Err(Error::Done),\n\n                    // SSL_ERROR_EARLY_DATA_REJECTED\n                    15 => {\n                        self.reset_early_data_reject();\n                        Err(Error::Done)\n                    },\n\n                    // SSL_ERROR_WANT_CERTIFICATE_VERIFY\n                    16 => Err(Error::Done),\n\n                    _ => Err(Error::TlsFail),\n                }\n            },\n        }\n    }\n\n    fn set_transport_error(&mut self, ex_data: &mut ExData, bssl_result: c_int) {\n        // SSL_ERROR_SSL\n        if self.get_error(bssl_result) == 1 {\n            // SSL_ERROR_SSL can't be recovered so ensure we set a\n            // local_error so the connection is closed.\n            // See https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html\n            if ex_data.local_error.is_none() {\n                *ex_data.local_error = Some(ConnectionError {\n                    is_app: false,\n                    error_code: INTERNAL_ERROR,\n                    reason: Vec::new(),\n                })\n            }\n        }\n    }\n\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    pub(crate) fn ssl_mut(&mut self) -> &mut boring::ssl::SslRef {\n        use foreign_types_shared::ForeignTypeRef;\n\n        unsafe { boring::ssl::SslRef::from_ptr_mut(self.as_mut_ptr() as _) }\n    }\n}\n\n// NOTE: These traits are not automatically implemented for Handshake due to the\n// raw pointer it wraps. However, the underlying data is not aliased (as\n// Handshake should be its only owner), and there is no interior mutability, as\n// the pointer is not accessed directly outside of this module, and the\n// Handshake object API should preserve Rust's borrowing guarantees.\nunsafe impl Send for Handshake {}\nunsafe impl Sync for Handshake {}\n\nimpl Drop for Handshake {\n    fn drop(&mut self) {\n        unsafe { SSL_free(self.as_mut_ptr()) }\n    }\n}\n\npub struct ExData<'a> {\n    pub application_protos: &'a Vec<Vec<u8>>,\n\n    pub crypto_ctx: &'a mut [packet::CryptoContext; packet::Epoch::count()],\n\n    pub session: &'a mut Option<Vec<u8>>,\n\n    pub local_error: &'a mut Option<ConnectionError>,\n\n    pub keylog: Option<&'a mut Box<dyn Write + Send + Sync>>,\n\n    pub trace_id: &'a str,\n\n    pub local_transport_params: crate::TransportParams,\n\n    pub recovery_config: crate::recovery::RecoveryConfig,\n\n    pub tx_cap_factor: f64,\n\n    /// PMTUD configuration: (enable, max_probes)\n    pub pmtud: Option<(bool, u8)>,\n\n    pub is_server: bool,\n}\n\nimpl<'a> ExData<'a> {\n    fn from_ssl_ptr(ptr: *const SSL) -> Option<&'a mut Self> {\n        get_ex_data_from_ptr::<ExData>(ptr, *QUICHE_EX_DATA_INDEX)\n    }\n\n    #[cfg(feature = \"boringssl-boring-crate\")]\n    pub fn from_ssl_ref(ssl: &mut boring::ssl::SslRef) -> Option<&mut Self> {\n        use boring::ex_data::Index;\n\n        // SAFETY: the QUICHE_EX_DATA_INDEX index is guaranteed to be created,\n        // and the associated data is always `ExData`.\n        let idx: Index<boring::ssl::Ssl, ExData> =\n            unsafe { Index::from_raw(*QUICHE_EX_DATA_INDEX) };\n\n        ssl.ex_data_mut(idx)\n    }\n}\n\nfn get_ex_data_from_ptr<'a, T>(ptr: *const SSL, idx: c_int) -> Option<&'a mut T> {\n    unsafe {\n        let data = SSL_get_ex_data(ptr, idx) as *mut T;\n        data.as_mut()\n    }\n}\n\nfn get_cipher_from_ptr(cipher: *const SSL_CIPHER) -> Result<crypto::Algorithm> {\n    let cipher_id = unsafe { SSL_CIPHER_get_id(cipher) };\n\n    let alg = match cipher_id {\n        0x0300_1301 => crypto::Algorithm::AES128_GCM,\n        0x0300_1302 => crypto::Algorithm::AES256_GCM,\n        0x0300_1303 => crypto::Algorithm::ChaCha20_Poly1305,\n        _ => return Err(Error::TlsFail),\n    };\n\n    Ok(alg)\n}\n\nextern \"C\" fn set_read_secret(\n    ssl: *mut SSL, level: crypto::Level, cipher: *const SSL_CIPHER,\n    secret: *const u8, secret_len: usize,\n) -> c_int {\n    let ex_data = match ExData::from_ssl_ptr(ssl) {\n        Some(v) => v,\n\n        None => return 0,\n    };\n\n    trace!(\"{} set read secret lvl={:?}\", ex_data.trace_id, level);\n\n    let space = match level {\n        crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial],\n        crypto::Level::ZeroRTT =>\n            &mut ex_data.crypto_ctx[packet::Epoch::Application],\n        crypto::Level::Handshake =>\n            &mut ex_data.crypto_ctx[packet::Epoch::Handshake],\n        crypto::Level::OneRTT =>\n            &mut ex_data.crypto_ctx[packet::Epoch::Application],\n    };\n\n    let aead = match get_cipher_from_ptr(cipher) {\n        Ok(v) => v,\n\n        Err(_) => return 0,\n    };\n\n    // 0-RTT read secrets are present only on the server.\n    if level != crypto::Level::ZeroRTT || ex_data.is_server {\n        let secret = unsafe { slice::from_raw_parts(secret, secret_len) };\n\n        let open = match crypto::Open::from_secret(aead, secret) {\n            Ok(v) => v,\n\n            Err(_) => return 0,\n        };\n\n        if level == crypto::Level::ZeroRTT {\n            space.crypto_0rtt_open = Some(open);\n            return 1;\n        }\n\n        space.crypto_open = Some(open);\n    }\n\n    1\n}\n\nextern \"C\" fn set_write_secret(\n    ssl: *mut SSL, level: crypto::Level, cipher: *const SSL_CIPHER,\n    secret: *const u8, secret_len: usize,\n) -> c_int {\n    let ex_data = match ExData::from_ssl_ptr(ssl) {\n        Some(v) => v,\n\n        None => return 0,\n    };\n\n    trace!(\"{} set write secret lvl={:?}\", ex_data.trace_id, level);\n\n    let space = match level {\n        crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial],\n        crypto::Level::ZeroRTT =>\n            &mut ex_data.crypto_ctx[packet::Epoch::Application],\n        crypto::Level::Handshake =>\n            &mut ex_data.crypto_ctx[packet::Epoch::Handshake],\n        crypto::Level::OneRTT =>\n            &mut ex_data.crypto_ctx[packet::Epoch::Application],\n    };\n\n    let aead = match get_cipher_from_ptr(cipher) {\n        Ok(v) => v,\n\n        Err(_) => return 0,\n    };\n\n    // 0-RTT write secrets are present only on the client.\n    if level != crypto::Level::ZeroRTT || !ex_data.is_server {\n        let secret = unsafe { slice::from_raw_parts(secret, secret_len) };\n\n        let seal = match crypto::Seal::from_secret(aead, secret) {\n            Ok(v) => v,\n\n            Err(_) => return 0,\n        };\n\n        space.crypto_seal = Some(seal);\n    }\n\n    1\n}\n\nextern \"C\" fn add_handshake_data(\n    ssl: *mut SSL, level: crypto::Level, data: *const u8, len: usize,\n) -> c_int {\n    let ex_data = match ExData::from_ssl_ptr(ssl) {\n        Some(v) => v,\n\n        None => return 0,\n    };\n\n    trace!(\n        \"{} write message lvl={:?} len={}\",\n        ex_data.trace_id,\n        level,\n        len\n    );\n\n    let buf = unsafe { slice::from_raw_parts(data, len) };\n\n    let space = match level {\n        crypto::Level::Initial => &mut ex_data.crypto_ctx[packet::Epoch::Initial],\n        crypto::Level::ZeroRTT => unreachable!(),\n        crypto::Level::Handshake =>\n            &mut ex_data.crypto_ctx[packet::Epoch::Handshake],\n        crypto::Level::OneRTT =>\n            &mut ex_data.crypto_ctx[packet::Epoch::Application],\n    };\n\n    if space.crypto_stream.send.write(buf, false).is_err() {\n        return 0;\n    }\n\n    1\n}\n\nextern \"C\" fn flush_flight(_ssl: *mut SSL) -> c_int {\n    // We don't really need to anything here since the output packets are\n    // generated separately, when conn.send() is called.\n\n    1\n}\n\nextern \"C\" fn send_alert(\n    ssl: *mut SSL, level: crypto::Level, alert: u8,\n) -> c_int {\n    let ex_data = match ExData::from_ssl_ptr(ssl) {\n        Some(v) => v,\n\n        None => return 0,\n    };\n\n    trace!(\n        \"{} send alert lvl={:?} alert={:x}\",\n        ex_data.trace_id,\n        level,\n        alert\n    );\n\n    let error: u64 = TLS_ALERT_ERROR + u64::from(alert);\n    *ex_data.local_error = Some(ConnectionError {\n        is_app: false,\n        error_code: error,\n        reason: Vec::new(),\n    });\n\n    1\n}\n\nextern \"C\" fn keylog(ssl: *const SSL, line: *const c_char) {\n    let ex_data = match ExData::from_ssl_ptr(ssl) {\n        Some(v) => v,\n\n        None => return,\n    };\n\n    if let Some(keylog) = &mut ex_data.keylog {\n        let data = unsafe { ffi::CStr::from_ptr(line).to_bytes() };\n\n        let mut full_line = Vec::with_capacity(data.len() + 1);\n        full_line.extend_from_slice(data);\n        full_line.push(b'\\n');\n\n        keylog.write_all(&full_line[..]).ok();\n        keylog.flush().ok();\n    }\n}\n\nextern \"C\" fn select_alpn(\n    ssl: *mut SSL, out: *mut *const u8, out_len: *mut u8, inp: *mut u8,\n    in_len: c_uint, _arg: *mut c_void,\n) -> c_int {\n    // SSL_TLSEXT_ERR_OK 0\n    // SSL_TLSEXT_ERR_ALERT_WARNING 1\n    // SSL_TLSEXT_ERR_ALERT_FATAL 2\n    // SSL_TLSEXT_ERR_NOACK 3\n\n    // Boringssl internally overwrite the return value from this callback, if the\n    // returned value is SSL_TLSEXT_ERR_NOACK and is quic, then the value gets\n    // overwritten to SSL_TLSEXT_ERR_ALERT_FATAL. In contrast openssl/quictls does\n    // not do that, so we need to explicitly respond with\n    // SSL_TLSEXT_ERR_ALERT_FATAL in case it is needed.\n    // TLS_ERROR is redefined for each vendor.\n    let ex_data = match ExData::from_ssl_ptr(ssl) {\n        Some(v) => v,\n\n        None => return TLS_ERROR,\n    };\n\n    if ex_data.application_protos.is_empty() {\n        return TLS_ERROR;\n    }\n\n    let mut protos = octets::Octets::with_slice(unsafe {\n        slice::from_raw_parts(inp, in_len as usize)\n    });\n\n    while let Ok(proto) = protos.get_bytes_with_u8_length() {\n        let found = ex_data.application_protos.iter().any(|expected| {\n            trace!(\n                \"checking peer ALPN {:?} against {:?}\",\n                std::str::from_utf8(proto.as_ref()),\n                std::str::from_utf8(expected.as_slice())\n            );\n\n            if expected.len() == proto.len() &&\n                expected.as_slice() == proto.as_ref()\n            {\n                unsafe {\n                    *out = expected.as_slice().as_ptr();\n                    *out_len = expected.len() as u8;\n                }\n\n                return true;\n            }\n\n            false\n        });\n\n        if found {\n            return 0; // SSL_TLSEXT_ERR_OK\n        }\n    }\n\n    TLS_ERROR\n}\n\nextern \"C\" fn new_session(ssl: *mut SSL, session: *mut SSL_SESSION) -> c_int {\n    let ex_data = match ExData::from_ssl_ptr(ssl) {\n        Some(v) => v,\n\n        None => return 0,\n    };\n\n    let handshake = Handshake::new(ssl);\n    let peer_params = handshake.quic_transport_params();\n\n    // Serialize session object into buffer.\n    let session_bytes = match get_session_bytes(session) {\n        Ok(v) => v,\n        Err(_) => return 0,\n    };\n\n    let mut buffer =\n        Vec::with_capacity(8 + peer_params.len() + 8 + session_bytes.len());\n\n    let session_bytes_len = session_bytes.len() as u64;\n\n    if buffer.write(&session_bytes_len.to_be_bytes()).is_err() {\n        std::mem::forget(handshake);\n        return 0;\n    }\n\n    if buffer.write(&session_bytes).is_err() {\n        std::mem::forget(handshake);\n        return 0;\n    }\n\n    let peer_params_len = peer_params.len() as u64;\n\n    if buffer.write(&peer_params_len.to_be_bytes()).is_err() {\n        std::mem::forget(handshake);\n        return 0;\n    }\n\n    if buffer.write(peer_params).is_err() {\n        std::mem::forget(handshake);\n        return 0;\n    }\n\n    *ex_data.session = Some(buffer);\n\n    // Prevent handshake from being freed, as we still need it.\n    std::mem::forget(handshake);\n\n    0\n}\n\npub fn map_result(bssl_result: c_int) -> Result<()> {\n    match bssl_result {\n        1 => Ok(()),\n        _ => Err(Error::TlsFail),\n    }\n}\n\npub fn map_result_zero_is_success(bssl_result: c_int) -> Result<()> {\n    match bssl_result {\n        0 => Ok(()),\n        _ => Err(Error::TlsFail),\n    }\n}\n\npub fn map_result_ptr<'a, T>(bssl_result: *const T) -> Result<&'a T> {\n    match unsafe { bssl_result.as_ref() } {\n        Some(v) => Ok(v),\n        None => Err(Error::TlsFail),\n    }\n}\n\nfn log_ssl_error() {\n    let mut err = [0u8; 1024];\n\n    unsafe {\n        let e = ERR_peek_error();\n        ERR_error_string_n(e, err.as_mut_ptr() as *mut c_char, err.len());\n    }\n\n    let cstr = ffi::CStr::from_bytes_until_nul(&err)\n        .expect(\"ERR_error_string_n should write a null terminated string\");\n\n    trace!(\n        \"{}\",\n        cstr.to_str()\n            .expect(\"ERR_error_string_n should create a valid UTF-8 message\")\n    );\n}\n\nextern \"C\" {\n    // Note: some vendor-specific methods are implemented by each vendor's\n    // submodule (openssl-quictls / boringssl).\n\n    // SSL_METHOD\n    fn TLS_method() -> *const SSL_METHOD;\n\n    // SSL_CTX\n    fn SSL_CTX_new(method: *const SSL_METHOD) -> *mut SSL_CTX;\n    fn SSL_CTX_free(ctx: *mut SSL_CTX);\n\n    fn SSL_CTX_use_certificate_chain_file(\n        ctx: *mut SSL_CTX, file: *const c_char,\n    ) -> c_int;\n\n    fn SSL_CTX_use_PrivateKey_file(\n        ctx: *mut SSL_CTX, file: *const c_char, ty: c_int,\n    ) -> c_int;\n\n    fn SSL_CTX_load_verify_locations(\n        ctx: *mut SSL_CTX, file: *const c_char, path: *const c_char,\n    ) -> c_int;\n\n    #[cfg(not(windows))]\n    fn SSL_CTX_set_default_verify_paths(ctx: *mut SSL_CTX) -> c_int;\n\n    #[cfg(windows)]\n    fn SSL_CTX_get_cert_store(ctx: *mut SSL_CTX) -> *mut X509_STORE;\n\n    fn SSL_CTX_set_verify(\n        ctx: *mut SSL_CTX, mode: c_int,\n        cb: Option<\n            unsafe extern \"C\" fn(\n                ok: c_int,\n                store_ctx: *mut X509_STORE_CTX,\n            ) -> c_int,\n        >,\n    );\n\n    fn SSL_CTX_set_keylog_callback(\n        ctx: *mut SSL_CTX,\n        cb: Option<unsafe extern \"C\" fn(ssl: *const SSL, line: *const c_char)>,\n    );\n\n    fn SSL_CTX_set_alpn_protos(\n        ctx: *mut SSL_CTX, protos: *const u8, protos_len: usize,\n    ) -> c_int;\n\n    fn SSL_CTX_set_alpn_select_cb(\n        ctx: *mut SSL_CTX,\n        cb: Option<\n            unsafe extern \"C\" fn(\n                ssl: *mut SSL,\n                out: *mut *const u8,\n                out_len: *mut u8,\n                inp: *mut u8,\n                in_len: c_uint,\n                arg: *mut c_void,\n            ) -> c_int,\n        >,\n        arg: *mut c_void,\n    );\n\n    fn SSL_CTX_sess_set_new_cb(\n        ctx: *mut SSL_CTX,\n        cb: Option<\n            unsafe extern \"C\" fn(\n                ssl: *mut SSL,\n                session: *mut SSL_SESSION,\n            ) -> c_int,\n        >,\n    );\n\n    fn SSL_new(ctx: *mut SSL_CTX) -> *mut SSL;\n\n    fn SSL_get_error(ssl: *const SSL, ret_code: c_int) -> c_int;\n\n    fn SSL_set_accept_state(ssl: *mut SSL);\n    fn SSL_set_connect_state(ssl: *mut SSL);\n\n    fn SSL_get0_param(ssl: *mut SSL) -> *mut X509_VERIFY_PARAM;\n\n    fn SSL_set_ex_data(ssl: *mut SSL, idx: c_int, ptr: *mut c_void) -> c_int;\n    fn SSL_get_ex_data(ssl: *const SSL, idx: c_int) -> *mut c_void;\n\n    fn SSL_get_current_cipher(ssl: *const SSL) -> *const SSL_CIPHER;\n\n    fn SSL_set_session(ssl: *mut SSL, session: *mut SSL_SESSION) -> c_int;\n\n    fn SSL_get_SSL_CTX(ssl: *const SSL) -> *mut SSL_CTX;\n\n    fn SSL_set_quiet_shutdown(ssl: *mut SSL, mode: c_int);\n\n    fn SSL_set_quic_transport_params(\n        ssl: *mut SSL, params: *const u8, params_len: usize,\n    ) -> c_int;\n\n    fn SSL_set_quic_method(\n        ssl: *mut SSL, quic_method: *const SSL_QUIC_METHOD,\n    ) -> c_int;\n\n    fn SSL_set_quic_use_legacy_codepoint(ssl: *mut SSL, use_legacy: c_int);\n\n    #[cfg(test)]\n    fn SSL_set_options(ssl: *mut SSL, opts: u32) -> u32;\n\n    fn SSL_get_peer_quic_transport_params(\n        ssl: *const SSL, out_params: *mut *const u8, out_params_len: *mut usize,\n    );\n\n    fn SSL_get0_alpn_selected(\n        ssl: *const SSL, out: *mut *const u8, out_len: *mut u32,\n    );\n\n    fn SSL_get_servername(ssl: *const SSL, ty: c_int) -> *const c_char;\n\n    fn SSL_provide_quic_data(\n        ssl: *mut SSL, level: crypto::Level, data: *const u8, len: usize,\n    ) -> c_int;\n\n    fn SSL_process_quic_post_handshake(ssl: *mut SSL) -> c_int;\n\n    fn SSL_do_handshake(ssl: *mut SSL) -> c_int;\n\n    fn SSL_quic_write_level(ssl: *const SSL) -> crypto::Level;\n\n    fn SSL_session_reused(ssl: *const SSL) -> c_int;\n\n    fn SSL_in_init(ssl: *const SSL) -> c_int;\n\n    fn SSL_clear(ssl: *mut SSL) -> c_int;\n\n    fn SSL_free(ssl: *mut SSL);\n\n    // SSL_CIPHER\n    fn SSL_CIPHER_get_id(cipher: *const SSL_CIPHER) -> c_uint;\n\n    // SSL_SESSION\n\n    fn SSL_SESSION_free(session: *mut SSL_SESSION);\n\n    // X509_VERIFY_PARAM\n    fn X509_VERIFY_PARAM_set1_host(\n        param: *mut X509_VERIFY_PARAM, name: *const c_char, namelen: usize,\n    ) -> c_int;\n\n    // X509_STORE\n    #[cfg(windows)]\n    fn X509_STORE_add_cert(ctx: *mut X509_STORE, x: *mut X509) -> c_int;\n\n    // X509\n    #[cfg(windows)]\n    fn X509_free(x: *mut X509);\n    #[cfg(windows)]\n    fn d2i_X509(px: *mut X509, input: *const *const u8, len: c_int) -> *mut X509;\n\n    // ERR\n    fn ERR_peek_error() -> c_uint;\n\n    fn ERR_error_string_n(err: c_uint, buf: *mut c_char, len: usize);\n\n    // OPENSSL\n    #[allow(dead_code)]\n    fn OPENSSL_free(ptr: *mut c_void);\n\n}\n\n#[cfg(not(feature = \"openssl\"))]\nmod boringssl;\n#[cfg(not(feature = \"openssl\"))]\nuse boringssl::*;\n\n#[cfg(feature = \"openssl\")]\nmod openssl_quictls;\n#[cfg(feature = \"openssl\")]\nuse openssl_quictls::*;\n"
  },
  {
    "path": "quiche/src/tls/openssl_quictls.rs",
    "content": "use super::*;\n\nuse libc::c_long;\nuse libc::c_uchar;\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct OPENSSL_STACK {\n    _unused: c_void,\n}\n\n#[allow(non_camel_case_types)]\n#[repr(transparent)]\nstruct X509 {\n    _unused: c_void,\n}\n\n#[repr(C)]\n#[allow(non_camel_case_types)]\npub(super) struct SSL_QUIC_METHOD {\n    set_encryption_secrets: Option<\n        extern \"C\" fn(\n            ssl: *mut SSL,\n            level: crypto::Level,\n            read_secret: *const u8,\n            write_secret: *const u8,\n            secret_len: usize,\n        ) -> c_int,\n    >,\n\n    add_handshake_data: Option<\n        unsafe extern \"C\" fn(\n            ssl: *mut SSL,\n            level: crypto::Level,\n            data: *const u8,\n            len: usize,\n        ) -> c_int,\n    >,\n\n    flush_flight: Option<extern \"C\" fn(ssl: *mut SSL) -> c_int>,\n\n    send_alert: Option<\n        extern \"C\" fn(ssl: *mut SSL, level: crypto::Level, alert: u8) -> c_int,\n    >,\n}\n\npub(super) static QUICHE_STREAM_METHOD: SSL_QUIC_METHOD = SSL_QUIC_METHOD {\n    set_encryption_secrets: Some(set_encryption_secrets),\n    add_handshake_data: Some(add_handshake_data),\n    flush_flight: Some(flush_flight),\n    send_alert: Some(send_alert),\n};\n\nimpl Context {\n    pub fn set_early_data_enabled(&mut self, _enabled: bool) {\n        // not yet supported\n    }\n}\n\nimpl Handshake {\n    pub fn set_quic_early_data_context(&mut self, _context: &[u8]) -> Result<()> {\n        // not supported for now.\n        map_result(1)\n    }\n\n    pub fn curve(&self) -> Option<String> {\n        let curve = unsafe {\n            let curve_id = SSL_get_negotiated_group(self.as_ptr());\n            if curve_id == 0 {\n                return None;\n            }\n\n            let curve_name = SSL_group_to_name(self.as_ptr(), curve_id);\n\n            match ffi::CStr::from_ptr(curve_name).to_str() {\n                Ok(v) => v,\n\n                Err(_) => return None,\n            }\n        };\n\n        Some(curve.to_string())\n    }\n\n    pub fn peer_cert_chain(&self) -> Option<Vec<&[u8]>> {\n        // If ssl is server then the leaf will not be included,\n        // SSL_get0_peer_certificate should be called.\n        let cert_chain = unsafe {\n            let chain =\n                map_result_ptr(SSL_get_peer_cert_chain(self.as_ptr())).ok()?;\n\n            let num = sk_X509_num(chain);\n            if num == 0 {\n                return None;\n            }\n\n            let mut cert_chain = vec![];\n            for i in 0..num {\n                let cert =\n                    map_result_ptr(sk_X509_value(chain, i) as *mut X509).ok()?;\n\n                let mut out: *mut u8 = ptr::null_mut();\n                let len = i2d_X509(cert, &mut out);\n                if len < 0 {\n                    return None;\n                }\n                cert_chain.push(slice::from_raw_parts(out, len as usize));\n            }\n\n            cert_chain\n        };\n\n        Some(cert_chain)\n    }\n\n    pub fn peer_cert(&self) -> Option<&[u8]> {\n        let peer_cert = unsafe {\n            // Important: Unit tests is disabled on this method.\n            // Although the client calls SSL_CTX_set_verify,  for some reason\n            // SSL_get0_peer_certificate seems not to return the peer's\n            // certificate as in bssl. SSL_peer_certificate does\n            // returns the object representing a certificate used as\n            // the local peer's identity.\n            let cert =\n                map_result_ptr(SSL_get0_peer_certificate(self.as_ptr())).ok()?;\n            let mut out: *mut u8 = ptr::null_mut();\n            let len = i2d_X509(cert, &mut out);\n            if len < 0 {\n                return None;\n            }\n            slice::from_raw_parts(out, len as usize)\n        };\n        Some(peer_cert)\n    }\n\n    #[cfg(test)]\n    #[allow(dead_code)] // for now, till we implement this using openssl\n    pub fn set_failing_private_key_method(&mut self) {}\n\n    pub fn is_in_early_data(&self) -> bool {\n        false\n    }\n\n    pub fn early_data_reason(&self) -> u32 {\n        0\n    }\n\n    pub fn set_session(&mut self, session: &[u8]) -> Result<()> {\n        unsafe {\n            let ctx = SSL_get_SSL_CTX(self.as_ptr());\n\n            if ctx.is_null() {\n                return Err(Error::TlsFail);\n            }\n\n            let session = d2i_SSL_SESSION(\n                ptr::null_mut(),\n                &mut session.as_ptr(),\n                session.len() as c_long,\n            );\n\n            if session.is_null() {\n                return Err(Error::TlsFail);\n            }\n\n            let rc = SSL_set_session(self.as_mut_ptr(), session);\n            SSL_SESSION_free(session);\n\n            map_result(rc)\n        }\n    }\n\n    pub fn reset_early_data_reject(&mut self) {\n        // not yet supported\n    }\n\n    pub fn sigalg(&self) -> Option<String> {\n        let sigalg = \"\";\n\n        Some(sigalg.to_string())\n    }\n}\n\nextern \"C\" fn set_encryption_secrets(\n    ssl: *mut SSL, level: crypto::Level, read_secret: *const u8,\n    write_secret: *const u8, secret_len: usize,\n) -> c_int {\n    let cipher = map_result_ptr(unsafe { SSL_get_current_cipher(ssl) });\n    let _write_ret =\n        set_write_secret(ssl, level, cipher.unwrap(), write_secret, secret_len);\n    let _read_ret =\n        set_read_secret(ssl, level, cipher.unwrap(), read_secret, secret_len);\n\n    1\n}\n\n// OpenSSL compatibility functions.\n//\n// These don't 100% follow the OpenSSL API (e.g. some arguments have slightly\n// different types) in order to make them compatible with the BoringSSL API.\n\n#[allow(non_snake_case)]\nunsafe fn sk_X509_num(stack: *const STACK_OF) -> usize {\n    OPENSSL_sk_num(stack as *const OPENSSL_STACK)\n}\n\n#[allow(non_snake_case)]\nunsafe fn sk_X509_value(stack: *const STACK_OF, idx: usize) -> *mut c_void {\n    OPENSSL_sk_value(stack as *const OPENSSL_STACK, idx)\n}\n\n#[allow(non_snake_case)]\npub(super) unsafe fn SSL_CTX_set_session_cache_mode(\n    ctx: *mut SSL_CTX, mode: c_int,\n) -> c_int {\n    const SSL_CTRL_SET_SESS_CACHE_MODE: c_int = 44;\n\n    SSL_CTX_ctrl(\n        ctx,\n        SSL_CTRL_SET_SESS_CACHE_MODE,\n        mode as c_long,\n        ptr::null_mut(),\n    ) as c_int\n}\n\n#[allow(non_snake_case)]\npub(super) unsafe fn SSL_CTX_set_tlsext_ticket_keys(\n    ctx: *mut SSL_CTX, key: *const u8, key_len: usize,\n) -> c_int {\n    const SSL_CTRL_SET_TLSEXT_TICKET_KEYS: c_int = 59;\n\n    SSL_CTX_ctrl(\n        ctx,\n        SSL_CTRL_SET_TLSEXT_TICKET_KEYS,\n        key_len as c_long,\n        key as *mut c_void,\n    ) as c_int\n}\n\n#[allow(non_snake_case)]\npub(super) unsafe fn SSL_set_min_proto_version(\n    s: *mut SSL, version: u16,\n) -> c_int {\n    const SSL_CTRL_SET_MIN_PROTO_VERSION: c_int = 123;\n\n    SSL_ctrl(\n        s,\n        SSL_CTRL_SET_MIN_PROTO_VERSION,\n        version as c_long,\n        ptr::null_mut(),\n    ) as c_int\n}\n\n#[allow(non_snake_case)]\npub(super) unsafe fn SSL_set_max_proto_version(\n    s: *mut SSL, version: u16,\n) -> c_int {\n    const SSL_CTRL_SET_MAX_PROTO_VERSION: c_int = 124;\n\n    SSL_ctrl(\n        s,\n        SSL_CTRL_SET_MAX_PROTO_VERSION,\n        version as c_long,\n        ptr::null_mut(),\n    ) as c_int\n}\n\n#[allow(non_snake_case)]\npub(super) unsafe fn SSL_set_tlsext_host_name(\n    s: *mut SSL, name: *const c_char,\n) -> c_int {\n    const SSL_CTRL_SET_TLSEXT_HOSTNAME: c_int = 55;\n\n    #[allow(non_upper_case_globals)]\n    const TLSEXT_NAMETYPE_host_name: c_long = 0;\n\n    SSL_ctrl(\n        s,\n        SSL_CTRL_SET_TLSEXT_HOSTNAME,\n        TLSEXT_NAMETYPE_host_name,\n        name as *mut c_void,\n    ) as c_int\n}\n\n#[allow(non_snake_case)]\npub(super) unsafe fn SSL_get_ex_new_index(\n    argl: c_long, argp: *const c_void, newf: *const c_void, dupf: *const c_void,\n    freef: *const c_void,\n) -> c_int {\n    const CRYPTO_EX_INDEX_SSL: c_int = 0;\n\n    CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, argl, argp, newf, dupf, freef)\n}\n\n#[allow(non_snake_case)]\nunsafe fn SSL_get_negotiated_group(ssl: *const SSL) -> c_int {\n    const SSL_CTRL_GET_NEGOTIATED_GROUP: c_int = 134;\n    SSL_ctrl(\n        ssl,\n        SSL_CTRL_GET_NEGOTIATED_GROUP,\n        0 as c_long,\n        ptr::null_mut(),\n    ) as c_int\n}\n\npub(super) fn get_session_bytes(session: *mut SSL_SESSION) -> Result<Vec<u8>> {\n    let session_bytes = unsafe {\n        // get session encoding length\n        let out_len = i2d_SSL_SESSION(session, ptr::null_mut());\n        if out_len == 0 {\n            return Err(Error::TlsFail);\n        }\n        let mut out: Vec<c_uchar> = Vec::with_capacity(out_len as usize);\n\n        let out_len = i2d_SSL_SESSION(session, &mut out.as_mut_ptr());\n        let session_bytes =\n            slice::from_raw_parts(out.as_mut_ptr(), out_len as usize).to_vec();\n        session_bytes\n    };\n\n    Ok(session_bytes)\n}\npub(super) const TLS_ERROR: c_int = 2;\n\nextern \"C\" {\n\n    fn SSL_CTX_ctrl(\n        ctx: *mut SSL_CTX, cmd: c_int, larg: c_long, parg: *mut c_void,\n    ) -> c_long;\n\n    fn SSL_get_peer_cert_chain(ssl: *const SSL) -> *mut STACK_OF;\n\n    fn SSL_get0_peer_certificate(ssl: *const SSL) -> *mut X509;\n\n    fn SSL_ctrl(\n        ssl: *const SSL, cmd: c_int, larg: c_long, parg: *mut c_void,\n    ) -> c_long;\n\n    fn i2d_X509(px: *const X509, out: *mut *mut c_uchar) -> c_int;\n\n    fn OPENSSL_sk_num(stack: *const OPENSSL_STACK) -> usize;\n\n    fn OPENSSL_sk_value(stack: *const OPENSSL_STACK, idx: usize) -> *mut c_void;\n\n    // CRYPTO\n\n    fn CRYPTO_get_ex_new_index(\n        class_index: c_int, argl: c_long, argp: *const c_void,\n        new_func: *const c_void, dup_func: *const c_void,\n        free_func: *const c_void,\n    ) -> c_int;\n\n    fn d2i_SSL_SESSION(\n        a: *mut *mut SSL_SESSION, pp: *mut *const c_uchar, len: c_long,\n    ) -> *mut SSL_SESSION;\n\n    pub(super) fn i2d_SSL_SESSION(\n        in_: *mut SSL_SESSION, pp: *mut *mut c_uchar,\n    ) -> c_int;\n\n    fn SSL_group_to_name(ssl: *const SSL, id: c_int) -> *const c_char;\n}\n"
  },
  {
    "path": "quiche/src/transport_params.rs",
    "content": "// Copyright (C) 2018-2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Transport parameters handling as per RFC 9000 Section 7.4\n//! Part of the Cryptographic and Transport Handshake\n\nuse std::collections::HashSet;\nuse std::mem::size_of;\n\nuse crate::ConnectionId;\nuse crate::Error;\nuse crate::Result;\nuse crate::MAX_STREAM_ID;\n\n#[cfg(feature = \"qlog\")]\nuse crate::crypto;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::quic::TransportInitiator;\n#[cfg(feature = \"qlog\")]\nuse qlog::events::EventData;\n\n/// QUIC Unknown Transport Parameter.\n///\n/// A QUIC transport parameter that is not specifically recognized\n/// by this implementation.\n#[derive(Clone, Debug, PartialEq)]\npub struct UnknownTransportParameter<T> {\n    /// The ID of the unknown transport parameter.\n    pub id: u64,\n\n    /// Original data representing the value of the unknown transport parameter.\n    pub value: T,\n}\n\nimpl<T> UnknownTransportParameter<T> {\n    /// Checks whether an unknown Transport Parameter's ID is in the reserved\n    /// space.\n    ///\n    /// See Section 18.1 in [RFC9000](https://datatracker.ietf.org/doc/html/rfc9000#name-reserved-transport-paramete).\n    pub fn is_reserved(&self) -> bool {\n        let n = (self.id - 27) / 31;\n        self.id == 31 * n + 27\n    }\n}\n\n#[cfg(feature = \"qlog\")]\nimpl From<UnknownTransportParameter<Vec<u8>>>\n    for qlog::events::quic::UnknownTransportParameter\n{\n    fn from(value: UnknownTransportParameter<Vec<u8>>) -> Self {\n        Self {\n            id: value.id,\n            value: qlog::HexSlice::maybe_string(Some(value.value.as_slice()))\n                .unwrap_or_default(),\n        }\n    }\n}\n\nimpl From<UnknownTransportParameter<&[u8]>>\n    for UnknownTransportParameter<Vec<u8>>\n{\n    // When an instance of an UnknownTransportParameter is actually\n    // stored in UnknownTransportParameters, then we make a copy\n    // of the bytes if the source is an instance of an UnknownTransportParameter\n    // whose value is not owned.\n    fn from(value: UnknownTransportParameter<&[u8]>) -> Self {\n        Self {\n            id: value.id,\n            value: value.value.to_vec(),\n        }\n    }\n}\n\n/// Track unknown transport parameters, up to a limit.\n#[derive(Clone, Debug, PartialEq, Default)]\npub struct UnknownTransportParameters {\n    /// The space remaining for storing unknown transport parameters.\n    pub capacity: usize,\n    /// The unknown transport parameters.\n    pub parameters: Vec<UnknownTransportParameter<Vec<u8>>>,\n}\n\nimpl UnknownTransportParameters {\n    /// Pushes an unknown transport parameter into storage if there is space\n    /// remaining.\n    pub fn push(&mut self, new: UnknownTransportParameter<&[u8]>) -> Result<()> {\n        let new_unknown_tp_size = new.value.len() + size_of::<u64>();\n        if new_unknown_tp_size < self.capacity {\n            self.capacity -= new_unknown_tp_size;\n            self.parameters.push(new.into());\n            Ok(())\n        } else {\n            Err(octets::BufferTooShortError.into())\n        }\n    }\n}\n\n/// An Iterator over unknown transport parameters.\npub struct UnknownTransportParameterIterator<'a> {\n    index: usize,\n    parameters: &'a Vec<UnknownTransportParameter<Vec<u8>>>,\n}\n\nimpl<'a> IntoIterator for &'a UnknownTransportParameters {\n    type IntoIter = UnknownTransportParameterIterator<'a>;\n    type Item = &'a UnknownTransportParameter<Vec<u8>>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        UnknownTransportParameterIterator {\n            index: 0,\n            parameters: &self.parameters,\n        }\n    }\n}\n\nimpl<'a> Iterator for UnknownTransportParameterIterator<'a> {\n    type Item = &'a UnknownTransportParameter<Vec<u8>>;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let result = self.parameters.get(self.index);\n        self.index += 1;\n        result\n    }\n}\n\n/// QUIC Transport Parameters\n#[derive(Clone, Debug, PartialEq)]\npub struct TransportParams {\n    /// Value of Destination CID field from first Initial packet sent by client\n    pub original_destination_connection_id: Option<ConnectionId<'static>>,\n    /// The maximum idle timeout.\n    pub max_idle_timeout: u64,\n    /// Token used for verifying stateless resets\n    pub stateless_reset_token: Option<u128>,\n    /// The maximum UDP payload size.\n    pub max_udp_payload_size: u64,\n    /// The initial flow control maximum data for the connection.\n    pub initial_max_data: u64,\n    /// The initial flow control maximum data for local bidirectional streams.\n    pub initial_max_stream_data_bidi_local: u64,\n    /// The initial flow control maximum data for remote bidirectional streams.\n    pub initial_max_stream_data_bidi_remote: u64,\n    /// The initial flow control maximum data for unidirectional streams.\n    pub initial_max_stream_data_uni: u64,\n    /// The initial maximum bidirectional streams.\n    pub initial_max_streams_bidi: u64,\n    /// The initial maximum unidirectional streams.\n    pub initial_max_streams_uni: u64,\n    /// The ACK delay exponent.\n    pub ack_delay_exponent: u64,\n    /// The max ACK delay.\n    pub max_ack_delay: u64,\n    /// Whether active migration is disabled.\n    pub disable_active_migration: bool,\n    /// The active connection ID limit.\n    pub active_conn_id_limit: u64,\n    /// The value that the endpoint included in the Source CID field of a Retry\n    /// Packet.\n    pub initial_source_connection_id: Option<ConnectionId<'static>>,\n    /// The value that the server included in the Source CID field of a Retry\n    /// Packet.\n    pub retry_source_connection_id: Option<ConnectionId<'static>>,\n    /// DATAGRAM frame extension parameter, if any.\n    pub max_datagram_frame_size: Option<u64>,\n    /// Unknown peer transport parameters and values, if any.\n    pub unknown_params: Option<UnknownTransportParameters>,\n    // pub preferred_address: ...,\n}\n\nimpl Default for TransportParams {\n    fn default() -> TransportParams {\n        TransportParams {\n            original_destination_connection_id: None,\n            max_idle_timeout: 0,\n            stateless_reset_token: None,\n            max_udp_payload_size: 65527,\n            initial_max_data: 0,\n            initial_max_stream_data_bidi_local: 0,\n            initial_max_stream_data_bidi_remote: 0,\n            initial_max_stream_data_uni: 0,\n            initial_max_streams_bidi: 0,\n            initial_max_streams_uni: 0,\n            ack_delay_exponent: 3,\n            max_ack_delay: 25,\n            disable_active_migration: false,\n            active_conn_id_limit: 2,\n            initial_source_connection_id: None,\n            retry_source_connection_id: None,\n            max_datagram_frame_size: None,\n            unknown_params: Default::default(),\n        }\n    }\n}\n\nimpl TransportParams {\n    pub(crate) fn decode(\n        buf: &[u8], is_server: bool, unknown_size: Option<usize>,\n    ) -> Result<TransportParams> {\n        let mut params = octets::Octets::with_slice(buf);\n        let mut seen_params = HashSet::new();\n\n        let mut tp = TransportParams::default();\n\n        if let Some(unknown_transport_param_tracking_size) = unknown_size {\n            tp.unknown_params = Some(UnknownTransportParameters {\n                capacity: unknown_transport_param_tracking_size,\n                parameters: vec![],\n            });\n        }\n\n        while params.cap() > 0 {\n            let id = params.get_varint()?;\n\n            if seen_params.contains(&id) {\n                return Err(Error::InvalidTransportParam);\n            }\n            seen_params.insert(id);\n\n            let mut val = params.get_bytes_with_varint_length()?;\n\n            match id {\n                0x0000 => {\n                    if is_server {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    tp.original_destination_connection_id =\n                        Some(val.to_vec().into());\n                },\n\n                0x0001 => {\n                    tp.max_idle_timeout = val.get_varint()?;\n                },\n\n                0x0002 => {\n                    if is_server {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    tp.stateless_reset_token = Some(u128::from_be_bytes(\n                        val.get_bytes(16)?\n                            .to_vec()\n                            .try_into()\n                            .map_err(|_| Error::BufferTooShort)?,\n                    ));\n                },\n\n                0x0003 => {\n                    tp.max_udp_payload_size = val.get_varint()?;\n\n                    if tp.max_udp_payload_size < 1200 {\n                        return Err(Error::InvalidTransportParam);\n                    }\n                },\n\n                0x0004 => {\n                    tp.initial_max_data = val.get_varint()?;\n                },\n\n                0x0005 => {\n                    tp.initial_max_stream_data_bidi_local = val.get_varint()?;\n                },\n\n                0x0006 => {\n                    tp.initial_max_stream_data_bidi_remote = val.get_varint()?;\n                },\n\n                0x0007 => {\n                    tp.initial_max_stream_data_uni = val.get_varint()?;\n                },\n\n                0x0008 => {\n                    let max = val.get_varint()?;\n\n                    if max > MAX_STREAM_ID {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    tp.initial_max_streams_bidi = max;\n                },\n\n                0x0009 => {\n                    let max = val.get_varint()?;\n\n                    if max > MAX_STREAM_ID {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    tp.initial_max_streams_uni = max;\n                },\n\n                0x000a => {\n                    let ack_delay_exponent = val.get_varint()?;\n\n                    if ack_delay_exponent > 20 {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    tp.ack_delay_exponent = ack_delay_exponent;\n                },\n\n                0x000b => {\n                    let max_ack_delay = val.get_varint()?;\n\n                    if max_ack_delay >= 2_u64.pow(14) {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    tp.max_ack_delay = max_ack_delay;\n                },\n\n                0x000c => {\n                    tp.disable_active_migration = true;\n                },\n\n                0x000d => {\n                    if is_server {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    // TODO: decode preferred_address\n                },\n\n                0x000e => {\n                    let limit = val.get_varint()?;\n\n                    if limit < 2 {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    tp.active_conn_id_limit = limit;\n                },\n\n                0x000f => {\n                    tp.initial_source_connection_id = Some(val.to_vec().into());\n                },\n\n                0x00010 => {\n                    if is_server {\n                        return Err(Error::InvalidTransportParam);\n                    }\n\n                    tp.retry_source_connection_id = Some(val.to_vec().into());\n                },\n\n                0x0020 => {\n                    tp.max_datagram_frame_size = Some(val.get_varint()?);\n                },\n\n                // Track unknown transport parameters specially.\n                unknown_tp_id => {\n                    if let Some(unknown_params) = &mut tp.unknown_params {\n                        // It is _not_ an error not to have space enough to track\n                        // an unknown parameter.\n                        let _ = unknown_params.push(UnknownTransportParameter {\n                            id: unknown_tp_id,\n                            value: val.buf(),\n                        });\n                    }\n                },\n            }\n        }\n\n        Ok(tp)\n    }\n\n    pub(crate) fn encode_param(\n        b: &mut octets::OctetsMut, ty: u64, len: usize,\n    ) -> Result<()> {\n        b.put_varint(ty)?;\n        b.put_varint(len as u64)?;\n\n        Ok(())\n    }\n\n    pub(crate) fn encode<'a>(\n        tp: &TransportParams, is_server: bool, out: &'a mut [u8],\n    ) -> Result<&'a mut [u8]> {\n        let mut b = octets::OctetsMut::with_slice(out);\n\n        if is_server {\n            if let Some(ref odcid) = tp.original_destination_connection_id {\n                TransportParams::encode_param(&mut b, 0x0000, odcid.len())?;\n                b.put_bytes(odcid)?;\n            }\n        };\n\n        if tp.max_idle_timeout != 0 {\n            assert!(tp.max_idle_timeout <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x0001,\n                octets::varint_len(tp.max_idle_timeout),\n            )?;\n            b.put_varint(tp.max_idle_timeout)?;\n        }\n\n        if is_server {\n            if let Some(ref token) = tp.stateless_reset_token {\n                TransportParams::encode_param(&mut b, 0x0002, 16)?;\n                b.put_bytes(&token.to_be_bytes())?;\n            }\n        }\n\n        if tp.max_udp_payload_size != 0 {\n            assert!(tp.max_udp_payload_size <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x0003,\n                octets::varint_len(tp.max_udp_payload_size),\n            )?;\n            b.put_varint(tp.max_udp_payload_size)?;\n        }\n\n        if tp.initial_max_data != 0 {\n            assert!(tp.initial_max_data <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x0004,\n                octets::varint_len(tp.initial_max_data),\n            )?;\n            b.put_varint(tp.initial_max_data)?;\n        }\n\n        if tp.initial_max_stream_data_bidi_local != 0 {\n            assert!(tp.initial_max_stream_data_bidi_local <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x0005,\n                octets::varint_len(tp.initial_max_stream_data_bidi_local),\n            )?;\n            b.put_varint(tp.initial_max_stream_data_bidi_local)?;\n        }\n\n        if tp.initial_max_stream_data_bidi_remote != 0 {\n            assert!(\n                tp.initial_max_stream_data_bidi_remote <= octets::MAX_VAR_INT\n            );\n            TransportParams::encode_param(\n                &mut b,\n                0x0006,\n                octets::varint_len(tp.initial_max_stream_data_bidi_remote),\n            )?;\n            b.put_varint(tp.initial_max_stream_data_bidi_remote)?;\n        }\n\n        if tp.initial_max_stream_data_uni != 0 {\n            assert!(tp.initial_max_stream_data_uni <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x0007,\n                octets::varint_len(tp.initial_max_stream_data_uni),\n            )?;\n            b.put_varint(tp.initial_max_stream_data_uni)?;\n        }\n\n        if tp.initial_max_streams_bidi != 0 {\n            assert!(tp.initial_max_streams_bidi <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x0008,\n                octets::varint_len(tp.initial_max_streams_bidi),\n            )?;\n            b.put_varint(tp.initial_max_streams_bidi)?;\n        }\n\n        if tp.initial_max_streams_uni != 0 {\n            assert!(tp.initial_max_streams_uni <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x0009,\n                octets::varint_len(tp.initial_max_streams_uni),\n            )?;\n            b.put_varint(tp.initial_max_streams_uni)?;\n        }\n\n        if tp.ack_delay_exponent != 0 {\n            assert!(tp.ack_delay_exponent <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x000a,\n                octets::varint_len(tp.ack_delay_exponent),\n            )?;\n            b.put_varint(tp.ack_delay_exponent)?;\n        }\n\n        if tp.max_ack_delay != 0 {\n            assert!(tp.max_ack_delay <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x000b,\n                octets::varint_len(tp.max_ack_delay),\n            )?;\n            b.put_varint(tp.max_ack_delay)?;\n        }\n\n        if tp.disable_active_migration {\n            TransportParams::encode_param(&mut b, 0x000c, 0)?;\n        }\n\n        // TODO: encode preferred_address\n\n        if tp.active_conn_id_limit != 2 {\n            assert!(tp.active_conn_id_limit <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x000e,\n                octets::varint_len(tp.active_conn_id_limit),\n            )?;\n            b.put_varint(tp.active_conn_id_limit)?;\n        }\n\n        if let Some(scid) = &tp.initial_source_connection_id {\n            TransportParams::encode_param(&mut b, 0x000f, scid.len())?;\n            b.put_bytes(scid)?;\n        }\n\n        if is_server {\n            if let Some(scid) = &tp.retry_source_connection_id {\n                TransportParams::encode_param(&mut b, 0x0010, scid.len())?;\n                b.put_bytes(scid)?;\n            }\n        }\n\n        if let Some(max_datagram_frame_size) = tp.max_datagram_frame_size {\n            assert!(max_datagram_frame_size <= octets::MAX_VAR_INT);\n            TransportParams::encode_param(\n                &mut b,\n                0x0020,\n                octets::varint_len(max_datagram_frame_size),\n            )?;\n            b.put_varint(max_datagram_frame_size)?;\n        }\n\n        let out_len = b.off();\n\n        Ok(&mut out[..out_len])\n    }\n\n    /// Creates a qlog event for connection transport parameters and TLS fields\n    #[cfg(feature = \"qlog\")]\n    pub fn to_qlog(\n        &self, initiator: TransportInitiator, cipher: Option<crypto::Algorithm>,\n    ) -> EventData {\n        let original_destination_connection_id = qlog::HexSlice::maybe_string(\n            self.original_destination_connection_id.as_ref(),\n        );\n\n        let stateless_reset_token = qlog::HexSlice::maybe_string(\n            self.stateless_reset_token.map(|s| s.to_be_bytes()).as_ref(),\n        );\n\n        let tls_cipher: Option<String> = cipher.map(|f| format!(\"{f:?}\"));\n\n        EventData::QuicParametersSet(qlog::events::quic::ParametersSet {\n            initiator: Some(initiator),\n            tls_cipher,\n            original_destination_connection_id,\n            stateless_reset_token,\n            disable_active_migration: Some(self.disable_active_migration),\n            max_idle_timeout: Some(self.max_idle_timeout),\n            max_udp_payload_size: Some(self.max_udp_payload_size),\n            ack_delay_exponent: Some(self.ack_delay_exponent),\n            max_ack_delay: Some(self.max_ack_delay),\n            active_connection_id_limit: Some(self.active_conn_id_limit),\n\n            initial_max_data: Some(self.initial_max_data),\n            initial_max_stream_data_bidi_local: Some(\n                self.initial_max_stream_data_bidi_local,\n            ),\n            initial_max_stream_data_bidi_remote: Some(\n                self.initial_max_stream_data_bidi_remote,\n            ),\n            initial_max_stream_data_uni: Some(self.initial_max_stream_data_uni),\n            initial_max_streams_bidi: Some(self.initial_max_streams_bidi),\n            initial_max_streams_uni: Some(self.initial_max_streams_uni),\n\n            unknown_parameters: self\n                .unknown_params\n                .as_ref()\n                .map(|unknown_params| {\n                    unknown_params\n                            .into_iter()\n                            .cloned()\n                            .map(\n                                Into::<\n                                    qlog::events::quic::UnknownTransportParameter,\n                                >::into,\n                            )\n                            .collect()\n                })\n                .unwrap_or_default(),\n\n            ..Default::default()\n        })\n    }\n}\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "max_width = 82\nhard_tabs = false\ntab_spaces = 4\nnewline_style = \"Auto\"\nuse_small_heuristics = \"Default\"\nindent_style = \"Block\"\nwrap_comments = true\nformat_code_in_doc_comments = true\ncomment_width = 80\nnormalize_comments = true\nnormalize_doc_attributes = true\nformat_strings = false\nformat_macro_matchers = false\nformat_macro_bodies = true\nempty_item_single_line = true\nstruct_lit_single_line = true\nfn_single_line = false\nwhere_single_line = false\nimports_indent = \"Block\"\nimports_layout = \"Vertical\"\nimports_granularity = \"Item\"\nreorder_imports = true\nreorder_modules = true\nreorder_impl_items = true\ntype_punctuation_density = \"Wide\"\nspace_before_colon = false\nspace_after_colon = true\nspaces_around_ranges = false\nbinop_separator = \"Back\"\nremove_nested_parens = true\ncombine_control_expr = true\noverflow_delimited_expr = true\nstruct_field_align_threshold = 0\nenum_discrim_align_threshold = 20\nmatch_arm_blocks = false\nforce_multiline_blocks = false\nfn_params_layout = \"Compressed\"\nbrace_style = \"SameLineWhere\"\ncontrol_brace_style = \"AlwaysSameLine\"\ntrailing_semicolon = true\ntrailing_comma = \"Vertical\"\nmatch_block_trailing_comma = true\nblank_lines_upper_bound = 1\nblank_lines_lower_bound = 0\nedition = \"2018\"\nmerge_derives = true\nuse_try_shorthand = true\nuse_field_init_shorthand = true\nforce_explicit_abi = true\ncondense_wildcard_suffixes = true\ncolor = \"Auto\"\nunstable_features = true\ndisable_all_formatting = false\nskip_children = false\nshow_parse_errors = true\nerror_on_line_overflow = false\nerror_on_unformatted = false\nignore = []\nemit_mode = \"Files\"\nmake_backup = false\n"
  },
  {
    "path": "task-killswitch/Cargo.toml",
    "content": "[package]\nname = \"task-killswitch\"\nversion = \"0.2.1\"\nrepository = { workspace = true }\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = { workspace = true }\ncategories = { workspace = true }\ndescription = \"Abort all tokio tasks at once\"\n\n[dependencies]\ndashmap = { workspace = true }\nparking_lot = { workspace = true }\ntokio = { workspace = true, features = [\"rt\", \"sync\"] }\n\n[dev-dependencies]\nfutures-util = { workspace = true, features = [\"alloc\"] }\ntokio = { workspace = true, features = [\"macros\", \"time\"] }\n"
  },
  {
    "path": "task-killswitch/src/lib.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse dashmap::DashMap;\nuse parking_lot::Mutex;\nuse tokio::sync::watch;\nuse tokio::task;\nuse tokio::task::AbortHandle;\nuse tokio::task::Id;\n\nuse std::future::Future;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::Ordering;\nuse std::sync::LazyLock;\n\n/// Drop guard for task removal. If a task panics, this makes sure\n/// it is removed from [`ActiveTasks`] properly.\nstruct RemoveOnDrop {\n    id: task::Id,\n    storage: &'static ActiveTasks,\n}\nimpl Drop for RemoveOnDrop {\n    fn drop(&mut self) {\n        self.storage.remove_task(self.id);\n    }\n}\n\n/// A task killswitch that allows aborting all the tasks spawned with it at\n/// once. The implementation strives to minimize in-band locking. Spawning a\n/// future requires a single sharded lock from an internal [`DashMap`].\n/// Conflicts are expected to be very rare (dashmap defaults to `4 * nproc`\n/// shards, while each thread can only spawn one task at a time.)\nstruct TaskKillswitch {\n    // Invariant: If `activated` is true, we don't add new tasks anymore.\n    activated: AtomicBool,\n    storage: &'static ActiveTasks,\n\n    /// Watcher that is triggered after all kill signals have been sent (by\n    /// dropping `signal_killed`.) Currently-running tasks are killed after\n    /// their next yield, which may be after this triggers.\n    all_killed: watch::Receiver<()>,\n    // NOTE: All we want here is to take ownership of `signal_killed` when\n    // activating the killswitch. That code path only runs once per instance, but\n    // requires interior mutability. Using `Mutex` is easier than bothering with\n    // an `UnsafeCell`. The mutex is guaranteed to be unlocked.\n    signal_killed: Mutex<Option<watch::Sender<()>>>,\n}\n\nimpl TaskKillswitch {\n    fn new(storage: &'static ActiveTasks) -> Self {\n        let (signal_killed, all_killed) = watch::channel(());\n        let signal_killed = Mutex::new(Some(signal_killed));\n\n        Self {\n            activated: AtomicBool::new(false),\n            storage,\n            signal_killed,\n            all_killed,\n        }\n    }\n\n    /// Creates a killswitch by allocating and leaking the task storage.\n    ///\n    /// **NOTE:** This is intended for use in `static`s and tests. It should not\n    /// be exposed publicly!\n    fn with_leaked_storage() -> Self {\n        let storage = Box::leak(Box::new(ActiveTasks::default()));\n        Self::new(storage)\n    }\n\n    fn was_activated(&self) -> bool {\n        // All synchronization is done using locks,\n        // so we can use relaxed for our atomics.\n        self.activated.load(Ordering::Relaxed)\n    }\n\n    #[track_caller]\n    fn spawn_task(\n        &self, fut: impl Future<Output = ()> + Send + 'static,\n    ) -> Option<Id> {\n        if self.was_activated() {\n            return None;\n        }\n\n        let storage = self.storage;\n        let handle = tokio::spawn(async move {\n            let id = task::id();\n            let _guard = RemoveOnDrop { id, storage };\n            fut.await;\n        })\n        .abort_handle();\n\n        let id = handle.id();\n\n        let res = self.storage.add_task_if(handle, || !self.was_activated());\n        if let Err(handle) = res {\n            // Killswitch was activated by the time we got a lock on the map shard\n            handle.abort();\n            return None;\n        }\n        Some(id)\n    }\n\n    fn activate(&self) {\n        // We check `activated` after locking the map shard and before inserting\n        // an element. This ensures in-progress spawns either complete before\n        // `tasks.kill_all()` obtains the lock for that shard, or they abort\n        // afterwards.\n        assert!(\n            !self.activated.swap(true, Ordering::Relaxed),\n            \"killswitch can't be used twice\"\n        );\n\n        let tasks = self.storage;\n        let signal_killed = self.signal_killed.lock().take();\n        std::thread::spawn(move || {\n            tasks.kill_all();\n            drop(signal_killed);\n        });\n    }\n\n    fn killed(&self) -> impl Future<Output = ()> + Send + 'static {\n        let mut signal = self.all_killed.clone();\n        async move {\n            let _ = signal.changed().await;\n        }\n    }\n}\n\nenum TaskEntry {\n    /// Task was added and not yet removed.\n    Handle(AbortHandle),\n    /// Task was removed before it was added. This can happen if a spawned\n    /// future completes before the spawning thread can add it to the map.\n    Tombstone,\n}\n\n#[derive(Default)]\nstruct ActiveTasks {\n    tasks: DashMap<task::Id, TaskEntry>,\n}\n\nimpl ActiveTasks {\n    fn kill_all(&self) {\n        self.tasks.retain(|_, entry| {\n            if let TaskEntry::Handle(task) = entry {\n                task.abort();\n            }\n            false // remove all elements\n        });\n    }\n\n    fn add_task_if(\n        &self, handle: AbortHandle, cond: impl FnOnce() -> bool,\n    ) -> Result<(), AbortHandle> {\n        use dashmap::Entry::*;\n        let id = handle.id();\n\n        match self.tasks.entry(id) {\n            Vacant(e) => {\n                if !cond() {\n                    return Err(handle);\n                }\n                e.insert(TaskEntry::Handle(handle));\n            },\n            Occupied(e) if matches!(e.get(), TaskEntry::Tombstone) => {\n                // Task was removed before it was added. Clear the map entry and\n                // drop the handle.\n                e.remove();\n            },\n            Occupied(_) => panic!(\"tokio task ID already in use: {id}\"),\n        }\n\n        Ok(())\n    }\n\n    fn remove_task(&self, id: task::Id) {\n        use dashmap::Entry::*;\n        match self.tasks.entry(id) {\n            Vacant(e) => {\n                // Task was not added yet, set a tombstone instead.\n                e.insert(TaskEntry::Tombstone);\n            },\n            Occupied(e) if matches!(e.get(), TaskEntry::Tombstone) => {},\n            Occupied(e) => {\n                e.remove();\n            },\n        }\n    }\n}\n\n/// The global [`TaskKillswitch`] exposed publicly from the crate.\nstatic TASK_KILLSWITCH: LazyLock<TaskKillswitch> =\n    LazyLock::new(TaskKillswitch::with_leaked_storage);\n\n/// Spawns a new asynchronous task and registers it in the crate's global\n/// killswitch.\n///\n/// Under the hood, [`tokio::spawn`] schedules the actual execution.\n#[inline]\n#[track_caller]\npub fn spawn_with_killswitch(\n    fut: impl Future<Output = ()> + Send + 'static,\n) -> Option<Id> {\n    TASK_KILLSWITCH.spawn_task(fut)\n}\n\n#[deprecated = \"activate() was unnecessarily declared async. Use activate_now() instead.\"]\npub async fn activate() {\n    TASK_KILLSWITCH.activate()\n}\n\n/// Triggers the killswitch, thereby scheduling all registered tasks to be\n/// killed.\n///\n/// Note: tasks are not killed synchronously in this function. This means\n/// `activate_now()` will return before all tasks have been stopped.\n#[inline]\npub fn activate_now() {\n    TASK_KILLSWITCH.activate();\n}\n\n/// Returns a future that resolves when all registered tasks have been killed,\n/// after [`activate_now`] has been called.\n///\n/// Note: tokio does not kill a task until the next time it yields to the\n/// runtime. This means some killed tasks may still be running by the time this\n/// Future resolves.\n#[inline]\npub fn killed_signal() -> impl Future<Output = ()> + Send + 'static {\n    TASK_KILLSWITCH.killed()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use futures_util::future;\n    use std::time::Duration;\n    use tokio::sync::oneshot;\n\n    struct TaskAbortSignal(Option<oneshot::Sender<()>>);\n\n    impl TaskAbortSignal {\n        fn new() -> (Self, oneshot::Receiver<()>) {\n            let (tx, rx) = oneshot::channel();\n\n            (Self(Some(tx)), rx)\n        }\n    }\n\n    impl Drop for TaskAbortSignal {\n        fn drop(&mut self) {\n            let _ = self.0.take().unwrap().send(());\n        }\n    }\n\n    fn start_test_tasks(\n        killswitch: &TaskKillswitch,\n    ) -> Vec<oneshot::Receiver<()>> {\n        (0..1000)\n            .map(|_| {\n                let (tx, rx) = TaskAbortSignal::new();\n\n                killswitch.spawn_task(async move {\n                    tokio::time::sleep(tokio::time::Duration::from_secs(3600))\n                        .await;\n                    drop(tx);\n                });\n\n                rx\n            })\n            .collect()\n    }\n\n    #[tokio::test]\n    async fn activate_killswitch_early() {\n        let killswitch = TaskKillswitch::with_leaked_storage();\n        let abort_signals = start_test_tasks(&killswitch);\n\n        killswitch.activate();\n\n        tokio::time::timeout(\n            Duration::from_secs(1),\n            future::join_all(abort_signals),\n        )\n        .await\n        .expect(\"tasks should be killed within given timeframe\");\n    }\n\n    #[tokio::test]\n    async fn activate_killswitch_with_delay() {\n        let killswitch = TaskKillswitch::with_leaked_storage();\n        let abort_signals = start_test_tasks(&killswitch);\n        let signal_handle = tokio::spawn(killswitch.killed());\n\n        // NOTE: give tasks time to start executing.\n        tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;\n\n        assert!(!signal_handle.is_finished());\n        killswitch.activate();\n\n        tokio::time::timeout(\n            Duration::from_secs(1),\n            future::join_all(abort_signals),\n        )\n        .await\n        .expect(\"tasks should be killed within given timeframe\");\n\n        tokio::time::timeout(Duration::from_secs(1), signal_handle)\n            .await\n            .expect(\"killed() signal should have resolved\")\n            .expect(\"signal task should join successfully\");\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/AGENTS.md",
    "content": "# tokio-quiche\n\n## OVERVIEW\n\nAsync tokio wrapper for `quiche`. Spawns per-connection IO worker tasks driven by an `ApplicationOverQuic` trait. Ships a ready-made `H3Driver` for HTTP/3. Uses `foundations` for structured logging (slog), telemetry, and settings.\n\n## STRUCTURE\n\n```\nsrc/\n  lib.rs              Re-exports, listen(), capture_quiche_logs()\n  buf_factory.rs      BufFactory: tiered static pools, QuicheBuf (zero-copy feature)\n  result.rs           BoxError = Box<dyn Error+Send+Sync>, QuicResult<T>\n  settings/           ConnectionParams → quiche::Config → h3::Config cascade\n  metrics/            Metrics trait (pluggable); DefaultMetrics (foundations Prometheus)\n  socket/             Socket<Tx,Rx>, QuicListener, SocketCapabilities (GSO/GRO)\n  quic/               connect(), start_listener(), ApplicationOverQuic, IoWorker, router\n  http3/              H3Driver<H>, DriverHooks (sealed), client/server controllers\n```\n\n## WHERE TO LOOK\n\n| Task | Location |\n|------|----------|\n| Server entrypoint | `lib.rs` — `listen()`, `listen_with_capabilities()` |\n| Client entrypoint | `quic/mod.rs` — `connect()`, `connect_with_config()` |\n| Custom app trait | `quic/connection/mod.rs:663` — `ApplicationOverQuic` |\n| H3 driver (main logic) | `http3/driver/mod.rs` — `H3Driver<H>` |\n| Per-connection IO loop | `quic/io/worker.rs` — `IoWorker` |\n| Packet routing/demux | `quic/router/mod.rs` — `InboundPacketRouter` |\n| Config cascade | `settings/config.rs` — `Config::new()` |\n| Buffer pools | `buf_factory.rs` — `BufFactory`, static pools |\n| Metrics interface | `metrics/mod.rs` — `Metrics` trait |\n\n## CODE MAP\n\n| Symbol | Type | Location | Role |\n|--------|------|----------|------|\n| `ApplicationOverQuic` | trait | `quic/connection/mod.rs` | Extension point: on_conn_established, process_reads/writes, wait_for_data |\n| `H3Driver<H>` | struct | `http3/driver/mod.rs` | Implements `ApplicationOverQuic` for HTTP/3 |\n| `DriverHooks` | sealed trait | `http3/driver/hooks.rs` | Client vs server H3 behavior |\n| `IoWorker<Tx,M,S>` | struct | `quic/io/worker.rs` | Per-connection state machine (recv -> app -> send) |\n| `InboundPacketRouter` | struct | `quic/router/mod.rs` | Sole owner of socket recv; routes by DCID |\n| `ConnectionAcceptor` | struct | `quic/router/acceptor.rs` | Server RETRY + yields InitialQuicConnection |\n| `InitialQuicConnection` | struct | `quic/connection/mod.rs` | Pre-handshake handle; `.start(app)` spawns worker |\n| `QuicConnection` | struct | `quic/connection/mod.rs` | Post-handshake metadata handle (not the qconn itself) |\n| `ConnectionParams` | struct | `settings/mod.rs` | QuicSettings + TLS + Hooks + session |\n| `Config` | struct(crate) | `settings/config.rs` | Builds quiche::Config from ConnectionParams |\n| `BufFactory` | struct | `buf_factory.rs` | Handle to static tiered buffer pools |\n| `QuicheBuf` | struct | `buf_factory.rs` | Zero-copy splittable buffer (zero-copy feature) |\n| `Metrics` | trait | `metrics/mod.rs` | Pluggable telemetry; `DefaultMetrics` uses foundations |\n| `QuicCommand` | enum | `quic/connection/mod.rs` | ConnectionClose / Custom / Stats commands |\n| `BoxError` | type alias | `result.rs` | `Box<dyn Error + Send + Sync>` — deliberate choice, see docstring |\n\n## CONVENTIONS (crate-specific)\n\n- Re-exports: `pub extern crate quiche`, `pub use buffer_pool`, `pub use datagram_socket`.\n- `foundations` for logging (slog), not `log` directly. `capture_quiche_logs()` bridges quiche's `log` into slog.\n- `DriverHooks` is **sealed** — prevents external `H3Driver` variants.\n- `QuicAuditStats` (from `datagram-socket`) threaded through all connections via `Arc`.\n- Task spawning via `metrics::tokio_task::spawn()` / `spawn_with_killswitch()` — wraps `tokio::spawn` with optional schedule/poll histograms.\n- `ConnectionStage` FSM: `Handshake` -> `RunningApplication` -> `Close`.\n\n## ANTI-PATTERNS\n\n- `connection_not_present()` returns `TlsFail` in driver — misleading sentinel. Don't propagate.\n- `Error::Done` used as success signal in H3 driver write path. Don't replicate.\n- Don't add new `ApplicationOverQuic` methods without considering the worker loop order (recv -> reads -> writes -> send).\n- Don't block in `wait_for_data` — it's polled concurrently with packet recv and timers.\n\n## NOTES\n\n- Hardcodes `quiche/boringssl-boring-crate` + `quiche/qlog` in Cargo.toml deps.\n- Features: `zero-copy` implies `gcongestion`. `rpk` enables raw public keys via `boring/rpk`.\n- `--cfg capture_keylogs` (build flag, not feature) enables SSLKEYLOGFILE support.\n- `perf-quic-listener-metrics` adds handshake timing instrumentation.\n- `tokio-task-metrics` adds schedule/poll duration histograms per spawned task.\n- Linux-only: `libc`/`nix` deps for signal handling and socket options.\n- One client connection per socket — no multiplexing on client side.\n"
  },
  {
    "path": "tokio-quiche/Cargo.toml",
    "content": "[package]\nname = \"tokio-quiche\"\nversion = \"0.16.1\"\ndescription = \"Asynchronous wrapper around quiche\"\nrepository = { workspace = true }\nedition = { workspace = true }\nlicense = { workspace = true }\nkeywords = [\"quic\", \"http3\", \"tokio\"]\ncategories = { workspace = true }\nreadme = \"README.md\"\nrust-version = \"1.85\"\n\n[features]\n# Forwarded quiche features for re-exports\nfuzzing = [\"quiche/fuzzing\"]\nquiche_internal = [\"quiche/internal\"]\n\n# Enable extra timing instrumentation for QUIC handshakes, including protocol\n# overhead and network delays.\nperf-quic-listener-metrics = []\n\n# Enable raw public key (RPK) support for QUIC handshakes.\nrpk = [\"boring/rpk\"]\n\n# Replaces quiche's original congestion control\n# implementation with one adapted from google/quiche.\ngcongestion = [\"quiche/gcongestion\"]\n# Use quiche with zero-copy send calls.\nzero-copy = [\"gcongestion\"]\n\n# Deprecated: use `--cfg capture_keylogs` instead.\ncapture_keylogs = []\n\n# Enable scheduling & poll duration histograms for tokio tasks.\ntokio-task-metrics = []\n\n[lints.rust]\n# capture_keylogs: enable SSLKEYLOGFILE capturing for QUIC connections.\nunexpected_cfgs = { level = \"warn\", check-cfg = ['cfg(capture_keylogs)'] }\n\n[dependencies]\nanyhow = { workspace = true }\nboring = { workspace = true }\nbuffer-pool = { workspace = true }\ncrossbeam = { workspace = true, default-features = false }\ndatagram-socket = { workspace = true }\nfoundations = { workspace = true, default-features = false, features = [\n  \"server-client-common-default\",\n] }\nfutures = { workspace = true }\nfutures-util = { workspace = true }\nipnetwork = { workspace = true }\nlog = { workspace = true }\noctets = { workspace = true }\npin-project = { workspace = true }\nquiche = { workspace = true, features = [\"boringssl-boring-crate\", \"qlog\"] }\nserde = { workspace = true, features = [\"derive\", \"rc\"] }\nserde_with = { workspace = true }\nslog-scope = { workspace = true }\nslog-stdlog = { workspace = true }\nsmallvec = { workspace = true }\ntask-killswitch = { workspace = true }\nthiserror = { workspace = true }\ntokio = { workspace = true, features = [\"macros\", \"rt\"] }\ntokio-stream = { workspace = true, features = [\"net\", \"io-util\"] }\ntokio-util = { workspace = true, features = [\n  \"compat\",\n  \"time\",\n  \"codec\",\n  \"io\",\n  \"rt\",\n] }\ntriomphe = { workspace = true }\nurl = { workspace = true }\n\n[dev-dependencies]\nassert_matches = { workspace = true }\nclap = { version = \"4.5.40\", features = [\"derive\"] }\nenv_logger = { workspace = true }\nh3i = { workspace = true }\nhttp = \"1\"\nhttp-body = \"1\"\nhttp-body-util = \"0.1\"\nregex = { workspace = true }\nserde_json = { workspace = true }\ntokio = { workspace = true, features = [\"time\", \"test-util\", \"rt-multi-thread\"] }\n\n[target.'cfg(target_os = \"linux\")'.dependencies]\nlibc = { workspace = true }\nnix = { workspace = true, features = [\"signal\"] }\n"
  },
  {
    "path": "tokio-quiche/README.md",
    "content": "# Tokio Quiche\n\nBridging the gap between [quiche][quiche] and [tokio][tokio].\n\ntokio-quiche connects [quiche::Connection][q-connection]s and\n[quiche::h3::Connection][q-h3-connection]s to tokio's event loop. Users have the\nchoice between implementing their own, custom <code>[ApplicationOverQuic]</code>\nor using the ready-made <code>[H3Driver]</code> for HTTP/3 clients and servers.\n\n# Starting an HTTP/3 Server\n\nA server listens on a UDP socket for QUIC connections and spawns a new tokio\ntask to handle each individual connection.\n\n```rust\nuse foundations::telemetry::log;\nuse futures::{SinkExt as _, StreamExt as _};\nuse tokio_quiche::buf_factory::BufFactory;\nuse tokio_quiche::http3::driver::{H3Event, IncomingH3Headers, OutboundFrame, ServerH3Event};\nuse tokio_quiche::http3::settings::Http3Settings;\nuse tokio_quiche::listen;\nuse tokio_quiche::metrics::DefaultMetrics;\nuse tokio_quiche::quic::SimpleConnectionIdGenerator;\nuse tokio_quiche::quiche::h3;\nuse tokio_quiche::{ConnectionParams, ServerH3Controller, ServerH3Driver};\n\nlet socket = tokio::net::UdpSocket::bind(\"0.0.0.0:4043\").await?;\nlet mut listeners = listen(\n    [socket],\n    ConnectionParams::new_server(\n        Default::default(),\n        tokio_quiche::settings::TlsCertificatePaths {\n            cert: \"/path/to/cert.pem\",\n            private_key: \"/path/to/key.pem\",\n            kind: tokio_quiche::settings::CertificateKind::X509,\n        },\n        Default::default(),\n    ),\n    SimpleConnectionIdGenerator,\n    DefaultMetrics,\n)?;\nlet accept_stream = &mut listeners[0];\n\nwhile let Some(conn) = accept_stream.next().await {\n    let (driver, controller) = ServerH3Driver::new(Http3Settings::default());\n    conn?.start(driver);\n    tokio::spawn(handle_connection(controller));\n}\n\nasync fn handle_connection(mut controller: ServerH3Controller) {\n    while let Some(ServerH3Event::Core(event)) = controller.event_receiver_mut().recv().await {\n        match event {\n            H3Event::IncomingHeaders(IncomingH3Headers {\n                mut send, headers, ..\n            }) => {\n                log::info!(\"incomming headers\"; \"headers\" => ?headers);\n                send.send(OutboundFrame::Headers(\n                    vec![h3::Header::new(b\":status\", b\"200\")],\n                    None,\n                ))\n                .await\n                .unwrap();\n\n                send.send(OutboundFrame::body(\n                    BufFactory::buf_from_slice(b\"hello from TQ!\"),\n                    true,\n                ))\n                .await\n                .unwrap();\n            }\n            event => {\n                log::info!(\"event: {event:?}\");\n            }\n        }\n    }\n}\n```\n\n# Sending an HTTP/3 request\n\n```rust\nuse foundations::telemetry::log;\nuse tokio_quiche::http3::driver::{ClientH3Event, H3Event, InboundFrame, IncomingH3Headers};\nuse tokio_quiche::quiche::h3;\n\nlet socket = tokio::net::UdpSocket::bind(\"0.0.0.0:0\").await?;\nsocket.connect(\"127.0.0.1:4043\").await?;\nlet (_, mut controller) = tokio_quiche::quic::connect(socket, None).await?;\n\ncontroller\n    .request_sender()\n    .send(tokio_quiche::http3::driver::NewClientRequest {\n        request_id: 0,\n        headers: vec![h3::Header::new(b\":method\", b\"GET\")],\n        body_writer: None,\n    })\n    .unwrap();\n\nwhile let Some(event) = controller.event_receiver_mut().recv().await {\n    match event {\n        ClientH3Event::Core(H3Event::IncomingHeaders(IncomingH3Headers {\n            stream_id,\n            headers,\n            mut recv,\n            ..\n        })) => {\n            log::info!(\"incomming headers\"; \"stream_id\" => stream_id, \"headers\" => ?headers);\n            'body: while let Some(frame) = recv.recv().await {\n                match frame {\n                    InboundFrame::Body(pooled, fin) => {\n                        log::info!(\"inbound body: {:?}\", std::str::from_utf8(&pooled);\n                            \"fin\" => fin,\n                            \"len\" => pooled.len()\n                        );\n                        if fin {\n                            log::info!(\"received full body, exiting\");\n                            break 'body;\n                        }\n                    }\n                    InboundFrame::Datagram(pooled) => {\n                        log::info!(\"inbound datagram\"; \"len\" => pooled.len());\n                    }\n                }\n            }\n        }\n        ClientH3Event::Core(H3Event::BodyBytesReceived { fin: true, .. }) => {\n            log::info!(\"fin received\");\n            break;\n        }\n        ClientH3Event::Core(event) => log::info!(\"received event: {event:?}\"),\n        ClientH3Event::NewOutboundRequest {\n            stream_id,\n            request_id,\n        } => log::info!(\n            \"sending outbound request\";\n            \"stream_id\" => stream_id,\n            \"request_id\" => request_id\n        ),\n    }\n}\n```\n\n**Note**: Omited in these two examples are is the use of `stream_id` to track\nmultiplexed requests within the same connection.\n\n# Feature Flags\n\ntokio-quiche supports a number of feature flags to enable experimental features,\nperformance enhancements, and additional telemetry. By default, no feature flags are\nenabled.\n\n- `rpk`: Support for raw public keys (RPK) in QUIC handshakes (via [boring]).\n- `gcongestion`: Replace quiche's original congestion control implementation with one\n   adapted from google/quiche.\n- `zero-copy`: Use zero-copy sends with quiche (implies `gcongestion`).\n- `perf-quic-listener-metrics`: Extra telemetry for QUIC handshake durations,\n  including protocol overhead and network delays.\n- `tokio-task-metrics`: Scheduling & poll duration histograms for tokio tasks.\n\nOther parts of the crate are enabled by separate build flags instead, to be\ncontrolled by the final binary:\n\n- `--cfg capture_keylogs`: Optional `SSLKEYLOGFILE` capturing for QUIC connections.\n\n\n# Server usage architecture\n\n![server-arch](https://github.com/cloudflare/quiche/blob/master/tokio-quiche/docs/arch-server.drawio.svg?raw=true)\n\n# Client usage architecture\n\n![client-arch](https://github.com/cloudflare/quiche/blob/master/tokio-quiche/docs/arch-client.drawio.svg?raw=true)\n\n[quiche]: https://docs.quic.tech/quiche/\n[tokio]: https://tokio.rs\n[q-connection]: https://docs.quic.tech/quiche/struct.Connection.html\n[q-h3-connection]: https://docs.quic.tech/quiche/h3/struct.Connection.html\n[connect]: https://docs.rs/tokio-quiche/latest/tokio_quiche/quic/fn.connect.html\n[ApplicationOverQuic]: https://docs.rs/tokio-quiche/latest/tokio_quiche/trait.ApplicationOverQuic.html\n[H3Driver]: https://docs.rs/tokio-quiche/latest/tokio-quiche/http3/driver/struct.H3Driver.html\n[boring]: https://docs.rs/boring/latest/boring/\n"
  },
  {
    "path": "tokio-quiche/docs/arch.drawio",
    "content": "<mxfile host=\"app.diagrams.net\" agent=\"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\" version=\"26.1.0\" pages=\"2\">\n  <diagram name=\"Page-1\" id=\"3qpx236MMJjKQpdtaQj8\">\n    <mxGraphModel dx=\"2067\" dy=\"1156\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"850\" pageHeight=\"1100\" math=\"0\" shadow=\"0\">\n      <root>\n        <mxCell id=\"0\" />\n        <mxCell id=\"1\" parent=\"0\" />\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-3\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;entryX=0.5;entryY=0;entryDx=0;entryDy=0;\" parent=\"1\" source=\"mWPUdoHqn-piwVSKCePV-1\" target=\"mWPUdoHqn-piwVSKCePV-4\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"240\" y=\"100\" as=\"targetPoint\" />\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-1\" value=\"fn: tokio_quiche::listen\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"1\" vertex=\"1\">\n          <mxGeometry x=\"30\" y=\"80\" width=\"140\" height=\"40\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-6\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontSize=12;startSize=8;endSize=8;\" parent=\"1\" source=\"mWPUdoHqn-piwVSKCePV-4\" target=\"mWPUdoHqn-piwVSKCePV-5\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-7\" value=\"yields N\" style=\"edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;\" parent=\"mWPUdoHqn-piwVSKCePV-6\" vertex=\"1\" connectable=\"0\">\n          <mxGeometry x=\"-0.5507\" y=\"2\" relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"25\" y=\"20\" as=\"offset\" />\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-4\" value=\"QuicConnectionStream\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"1\" vertex=\"1\">\n          <mxGeometry x=\"25\" y=\"160\" width=\"150\" height=\"60\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-5\" value=\"InitialQuicConnection\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"1\" vertex=\"1\">\n          <mxGeometry x=\"20\" y=\"310\" width=\"160\" height=\"60\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-29\" style=\"edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;exitX=0.5;exitY=1;exitDx=0;exitDy=0;\" parent=\"1\" source=\"mWPUdoHqn-piwVSKCePV-28\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"100\" y=\"420\" as=\"targetPoint\" />\n            <Array as=\"points\">\n              <mxPoint x=\"300\" y=\"420\" />\n              <mxPoint x=\"100\" y=\"420\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-28\" value=\"impl ApplicationOverQuic\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"1\" vertex=\"1\">\n          <mxGeometry x=\"220\" y=\"310\" width=\"160\" height=\"60\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"pvk3wVF_-k6SXGWQYtcm-1\" value=\"fn: IQC::start\" style=\"swimlane;whiteSpace=wrap;html=1;\" parent=\"1\" vertex=\"1\">\n          <mxGeometry x=\"-50\" y=\"450\" width=\"590\" height=\"290\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-14\" value=\"Tokio Task: quic_handshake_worker\" style=\"swimlane;whiteSpace=wrap;html=1;startSize=23;\" parent=\"pvk3wVF_-k6SXGWQYtcm-1\" vertex=\"1\">\n          <mxGeometry x=\"30\" y=\"70\" width=\"240\" height=\"100\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-17\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;\" parent=\"mWPUdoHqn-piwVSKCePV-14\" source=\"mWPUdoHqn-piwVSKCePV-11\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"300\" y=\"60\" as=\"targetPoint\" />\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-11\" value=\"handshake\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"mWPUdoHqn-piwVSKCePV-14\" vertex=\"1\">\n          <mxGeometry x=\"62.5\" y=\"40\" width=\"110\" height=\"40\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-30\" value=\"AoQ\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"mWPUdoHqn-piwVSKCePV-14\" vertex=\"1\">\n          <mxGeometry x=\"10\" y=\"45\" width=\"40\" height=\"30\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-18\" value=\"Tokio task: quic_io_worker\" style=\"swimlane;whiteSpace=wrap;html=1;\" parent=\"pvk3wVF_-k6SXGWQYtcm-1\" vertex=\"1\">\n          <mxGeometry x=\"330\" y=\"70\" width=\"240\" height=\"190\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-24\" style=\"edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;entryX=0.847;entryY=0.989;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.845;exitY=0.069;exitDx=0;exitDy=0;exitPerimeter=0;\" parent=\"mWPUdoHqn-piwVSKCePV-18\" source=\"mWPUdoHqn-piwVSKCePV-22\" target=\"mWPUdoHqn-piwVSKCePV-22\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"120\" y=\"130\" as=\"targetPoint\" />\n            <Array as=\"points\">\n              <mxPoint x=\"139\" y=\"40\" />\n              <mxPoint x=\"181\" y=\"40\" />\n              <mxPoint x=\"181\" y=\"100\" />\n              <mxPoint x=\"140\" y=\"100\" />\n              <mxPoint x=\"140\" y=\"82\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-25\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontSize=12;startSize=8;endSize=8;\" parent=\"mWPUdoHqn-piwVSKCePV-18\" source=\"mWPUdoHqn-piwVSKCePV-22\" target=\"mWPUdoHqn-piwVSKCePV-23\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-22\" value=\"Running\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"mWPUdoHqn-piwVSKCePV-18\" vertex=\"1\">\n          <mxGeometry x=\"40\" y=\"52\" width=\"117.5\" height=\"30\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-23\" value=\"Closing\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"mWPUdoHqn-piwVSKCePV-18\" vertex=\"1\">\n          <mxGeometry x=\"40\" y=\"140\" width=\"117.5\" height=\"30\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-31\" value=\"AoQ\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"mWPUdoHqn-piwVSKCePV-18\" vertex=\"1\">\n          <mxGeometry x=\"190\" y=\"52\" width=\"40\" height=\"30\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GngBAlXJXLjJrXuEWOCi-1\" value=\"\" style=\"endArrow=classic;html=1;rounded=0;fontSize=12;startSize=8;endSize=8;edgeStyle=orthogonalEdgeStyle;startArrow=oval;startFill=1;\" parent=\"pvk3wVF_-k6SXGWQYtcm-1\" edge=\"1\">\n          <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"13\" y=\"46\" as=\"sourcePoint\" />\n            <mxPoint x=\"30\" y=\"130\" as=\"targetPoint\" />\n            <Array as=\"points\">\n              <mxPoint x=\"13\" y=\"46\" />\n              <mxPoint x=\"13\" y=\"121\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"mWPUdoHqn-piwVSKCePV-20\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;\" parent=\"1\" source=\"mWPUdoHqn-piwVSKCePV-5\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"100\" y=\"450\" as=\"targetPoint\" />\n          </mxGeometry>\n        </mxCell>\n      </root>\n    </mxGraphModel>\n  </diagram>\n  <diagram id=\"dlILORrkHTa0uQWCBott\" name=\"Page-2\">\n    <mxGraphModel dx=\"2476\" dy=\"644\" grid=\"0\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"0\" pageScale=\"1\" pageWidth=\"850\" pageHeight=\"1100\" math=\"0\" shadow=\"0\">\n      <root>\n        <mxCell id=\"0\" />\n        <mxCell id=\"1\" parent=\"0\" />\n        <mxCell id=\"TgNAw08oQqk_PFP4HZ26-2\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;\" parent=\"1\" source=\"TgNAw08oQqk_PFP4HZ26-1\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"-155\" y=\"213\" as=\"targetPoint\" />\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"TgNAw08oQqk_PFP4HZ26-1\" value=\"Socket\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"1\" vertex=\"1\">\n          <mxGeometry x=\"-215\" y=\"48\" width=\"120\" height=\"39\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"TgNAw08oQqk_PFP4HZ26-5\" style=\"edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;\" parent=\"1\" source=\"TgNAw08oQqk_PFP4HZ26-4\" edge=\"1\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"-254\" y=\"751\" as=\"targetPoint\" />\n            <Array as=\"points\">\n              <mxPoint x=\"-431\" y=\"751\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"TgNAw08oQqk_PFP4HZ26-4\" value=\"impl ApplicationOverQuic\" style=\"rounded=0;whiteSpace=wrap;html=1;\" parent=\"1\" vertex=\"1\">\n          <mxGeometry x=\"-538\" y=\"49\" width=\"213\" height=\"60\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"TgNAw08oQqk_PFP4HZ26-6\" value=\"fn: tokio_quiche::quic::connect\" style=\"swimlane;whiteSpace=wrap;html=1;\" parent=\"1\" vertex=\"1\">\n          <mxGeometry x=\"-538\" y=\"212\" width=\"782\" height=\"906\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"CxVNtvkxROuJx1GsSL81-1\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontSize=12;startSize=8;endSize=8;\" edge=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\" source=\"CxVNtvkxROuJx1GsSL81-3\" target=\"CxVNtvkxROuJx1GsSL81-6\">\n          <mxGeometry relative=\"1\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"CxVNtvkxROuJx1GsSL81-2\" value=\"take 1\" style=\"edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=12;\" vertex=\"1\" connectable=\"0\" parent=\"CxVNtvkxROuJx1GsSL81-1\">\n          <mxGeometry x=\"-0.5507\" y=\"2\" relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"25\" y=\"20\" as=\"offset\" />\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"CxVNtvkxROuJx1GsSL81-3\" value=\"QuickConnectionStream\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\">\n          <mxGeometry x=\"194\" y=\"282\" width=\"179\" height=\"60\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"CxVNtvkxROuJx1GsSL81-4\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;entryX=0.265;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;\" edge=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\" source=\"CxVNtvkxROuJx1GsSL81-6\" target=\"GSPGxvrXwIzSSmzkrvUa-1\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"153\" y=\"582\" as=\"targetPoint\" />\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"CxVNtvkxROuJx1GsSL81-6\" value=\"InitialQuicConnection\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\">\n          <mxGeometry x=\"189\" y=\"432\" width=\"189\" height=\"60\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-1\" value=\"fn: IQC::start\" style=\"swimlane;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\">\n          <mxGeometry x=\"120.75\" y=\"581\" width=\"619\" height=\"290\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-2\" value=\"Tokio Task: quic_handshake_worker\" style=\"swimlane;whiteSpace=wrap;html=1;startSize=23;\" vertex=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-1\">\n          <mxGeometry x=\"30\" y=\"70\" width=\"240\" height=\"100\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-3\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;\" edge=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-2\" source=\"GSPGxvrXwIzSSmzkrvUa-4\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"300\" y=\"60\" as=\"targetPoint\" />\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-4\" value=\"handshake\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-2\">\n          <mxGeometry x=\"62.5\" y=\"40\" width=\"110\" height=\"40\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-5\" value=\"AoQ\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-2\">\n          <mxGeometry x=\"10\" y=\"45\" width=\"40\" height=\"30\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-6\" value=\"Tokio task: quic_io_worker\" style=\"swimlane;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-1\">\n          <mxGeometry x=\"330\" y=\"70\" width=\"240\" height=\"190\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-7\" style=\"edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=12;startSize=8;endSize=8;entryX=0.847;entryY=0.989;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.845;exitY=0.069;exitDx=0;exitDy=0;exitPerimeter=0;\" edge=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-6\" source=\"GSPGxvrXwIzSSmzkrvUa-9\" target=\"GSPGxvrXwIzSSmzkrvUa-9\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"120\" y=\"130\" as=\"targetPoint\" />\n            <Array as=\"points\">\n              <mxPoint x=\"139\" y=\"40\" />\n              <mxPoint x=\"181\" y=\"40\" />\n              <mxPoint x=\"181\" y=\"100\" />\n              <mxPoint x=\"140\" y=\"100\" />\n              <mxPoint x=\"140\" y=\"82\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-8\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontSize=12;startSize=8;endSize=8;\" edge=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-6\" source=\"GSPGxvrXwIzSSmzkrvUa-9\" target=\"GSPGxvrXwIzSSmzkrvUa-10\">\n          <mxGeometry relative=\"1\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-9\" value=\"Running\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-6\">\n          <mxGeometry x=\"40\" y=\"52\" width=\"117.5\" height=\"30\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-10\" value=\"Closing\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-6\">\n          <mxGeometry x=\"40\" y=\"140\" width=\"117.5\" height=\"30\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-11\" value=\"AoQ\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"GSPGxvrXwIzSSmzkrvUa-6\">\n          <mxGeometry x=\"190\" y=\"52\" width=\"40\" height=\"30\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-12\" value=\"\" style=\"endArrow=classic;html=1;rounded=0;fontSize=12;startSize=8;endSize=8;edgeStyle=orthogonalEdgeStyle;startArrow=oval;startFill=1;\" edge=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\">\n          <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"200\" y=\"626\" as=\"sourcePoint\" />\n            <mxPoint x=\"217\" y=\"710\" as=\"targetPoint\" />\n            <Array as=\"points\">\n              <mxPoint x=\"200\" y=\"626\" />\n              <mxPoint x=\"200\" y=\"701\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"g9Gv8jskTPhWmc6KxlJW-1\" style=\"edgeStyle=none;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontSize=12;startSize=8;endSize=8;\" edge=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\" source=\"qlOaM-RS4FKM01MQFeZX-2\" target=\"qlOaM-RS4FKM01MQFeZX-3\">\n          <mxGeometry relative=\"1\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"qlOaM-RS4FKM01MQFeZX-2\" value=\"fn: quiche::connect\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\">\n          <mxGeometry x=\"173\" y=\"59\" width=\"120\" height=\"60\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"qlOaM-RS4FKM01MQFeZX-4\" style=\"edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontSize=12;startSize=8;endSize=8;exitX=0.5;exitY=1;exitDx=0;exitDy=0;\" edge=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\" source=\"qlOaM-RS4FKM01MQFeZX-3\" target=\"CxVNtvkxROuJx1GsSL81-3\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <Array as=\"points\">\n              <mxPoint x=\"437\" y=\"199\" />\n              <mxPoint x=\"284\" y=\"199\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"qlOaM-RS4FKM01MQFeZX-3\" value=\"InboundPackerRouter::new\" style=\"rounded=0;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\">\n          <mxGeometry x=\"351\" y=\"59\" width=\"171\" height=\"60\" as=\"geometry\" />\n        </mxCell>\n        <mxCell id=\"GSPGxvrXwIzSSmzkrvUa-13\" value=\"\" style=\"endArrow=classic;html=1;rounded=0;fontSize=12;startSize=8;endSize=8;edgeStyle=orthogonalEdgeStyle;startArrow=oval;startFill=1;\" edge=\"1\" parent=\"TgNAw08oQqk_PFP4HZ26-6\">\n          <mxGeometry width=\"50\" height=\"50\" relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"156.5\" y=\"28\" as=\"sourcePoint\" />\n            <mxPoint x=\"173\" y=\"89\" as=\"targetPoint\" />\n            <Array as=\"points\">\n              <mxPoint x=\"157\" y=\"29\" />\n              <mxPoint x=\"156\" y=\"29\" />\n              <mxPoint x=\"156\" y=\"89\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"qlOaM-RS4FKM01MQFeZX-6\" value=\"tokio task: packet router driver\" style=\"swimlane;whiteSpace=wrap;html=1;\" vertex=\"1\" parent=\"1\">\n          <mxGeometry x=\"-52\" y=\"493\" width=\"200\" height=\"48\" as=\"geometry\">\n            <mxRectangle x=\"-52\" y=\"493\" width=\"196\" height=\"26\" as=\"alternateBounds\" />\n          </mxGeometry>\n        </mxCell>\n        <mxCell id=\"qlOaM-RS4FKM01MQFeZX-5\" style=\"edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fontSize=12;startSize=8;endSize=8;entryX=0.5;entryY=0;entryDx=0;entryDy=0;\" edge=\"1\" parent=\"1\" source=\"qlOaM-RS4FKM01MQFeZX-3\" target=\"qlOaM-RS4FKM01MQFeZX-6\">\n          <mxGeometry relative=\"1\" as=\"geometry\">\n            <mxPoint x=\"84\" y=\"488.8571428571429\" as=\"targetPoint\" />\n            <Array as=\"points\">\n              <mxPoint x=\"-101\" y=\"411\" />\n              <mxPoint x=\"48\" y=\"411\" />\n            </Array>\n          </mxGeometry>\n        </mxCell>\n      </root>\n    </mxGraphModel>\n  </diagram>\n</mxfile>\n"
  },
  {
    "path": "tokio-quiche/docs/worker.excalidraw",
    "content": "{\n  \"type\": \"excalidraw\",\n  \"version\": 2,\n  \"source\": \"https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor\",\n  \"elements\": [\n    {\n      \"id\": \"LmUL6PqKdGeVETZ3QvyHu\",\n      \"type\": \"rectangle\",\n      \"x\": 452.3697992791793,\n      \"y\": 243.0575497168851,\n      \"width\": 351.8581113783172,\n      \"height\": 429.1143283572601,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"seed\": 1913460770,\n      \"version\": 613,\n      \"versionNonce\": 2104386238,\n      \"isDeleted\": false,\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"nfJAz7W-gc24_C3U9dBTP\"\n        }\n      ],\n      \"updated\": 1738174044439,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"id\": \"nfJAz7W-gc24_C3U9dBTP\",\n      \"type\": \"text\",\n      \"x\": 481.8144799683379,\n      \"y\": 248.0575497168851,\n      \"width\": 292.96875,\n      \"height\": 24,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"seed\": 1902275198,\n      \"version\": 554,\n      \"versionNonce\": 556576802,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044439,\n      \"link\": null,\n      \"locked\": false,\n      \"text\": \"Task: InboundPacketRouter\",\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 19,\n      \"containerId\": \"LmUL6PqKdGeVETZ3QvyHu\",\n      \"originalText\": \"Task: InboundPacketRouter\",\n      \"lineHeight\": 1.2\n    },\n    {\n      \"id\": \"Tadf2Hzwo9-pbT_5DQ0-t\",\n      \"type\": \"rectangle\",\n      \"x\": 544.1134589567253,\n      \"y\": 298.460593598998,\n      \"width\": 173.27343750000003,\n      \"height\": 55,\n      \"angle\": 0,\n      \"strokeColor\": \"#2f9e44\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"seed\": 980659326,\n      \"version\": 726,\n      \"versionNonce\": 1498372862,\n      \"isDeleted\": false,\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"ZTpxJC0uMNkPp02z3QJYy\"\n        },\n        {\n          \"id\": \"8LrUovEh1sd9l3B9POYc7\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"_wDA5QE3xKvGoJr1DdJfp\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044439,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"id\": \"ZTpxJC0uMNkPp02z3QJYy\",\n      \"type\": \"text\",\n      \"x\": 560.4376777067253,\n      \"y\": 313.960593598998,\n      \"width\": 140.625,\n      \"height\": 24,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"seed\": 1299557090,\n      \"version\": 647,\n      \"versionNonce\": 707156962,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044439,\n      \"link\": null,\n      \"locked\": false,\n      \"text\": \"QuicListener\",\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"baseline\": 19,\n      \"containerId\": \"Tadf2Hzwo9-pbT_5DQ0-t\",\n      \"originalText\": \"QuicListener\",\n      \"lineHeight\": 1.2\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 653,\n      \"versionNonce\": 1782991714,\n      \"isDeleted\": false,\n      \"id\": \"YDVT5jLak02colj1TmLY0\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1148.985547625,\n      \"y\": 243.76148999999998,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 469.667464592764,\n      \"height\": 495.71606087696307,\n      \"seed\": 1153068414,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"R6XJ7qo-BotKI-GZ2sPhk\"\n        },\n        {\n          \"id\": \"06UDjCUF1Gslt7UNqxDb7\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"0aT5dlfXL525CHNhpCExI\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044439,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 640,\n      \"versionNonce\": 2090865598,\n      \"isDeleted\": false,\n      \"id\": \"R6XJ7qo-BotKI-GZ2sPhk\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1301.788029921382,\n      \"y\": 248.76148999999998,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 164.0625,\n      \"height\": 24,\n      \"seed\": 1481379262,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174044439,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"Task: IoWorker\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": \"YDVT5jLak02colj1TmLY0\",\n      \"originalText\": \"Task: IoWorker\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 19\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 951,\n      \"versionNonce\": 1413716734,\n      \"isDeleted\": false,\n      \"id\": \"PnQKeDi1dRXt4Ha2TD5bE\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 544.1134588122943,\n      \"y\": 565.3701504203678,\n      \"strokeColor\": \"#9c36b5\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 181.54687500000006,\n      \"height\": 58,\n      \"seed\": 1511412514,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"uqNvxXfkjG7qsvDcMzcmn\"\n        },\n        {\n          \"id\": \"agRbxvveMtgkNyHPODdHa\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"_wDA5QE3xKvGoJr1DdJfp\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"aiEGoN6Xj1yJIaNtwJ4zY\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174120487,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 926,\n      \"versionNonce\": 284971966,\n      \"isDeleted\": false,\n      \"id\": \"uqNvxXfkjG7qsvDcMzcmn\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 576.2931463122943,\n      \"y\": 570.3701504203678,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 117.1875,\n      \"height\": 48,\n      \"seed\": 2078514914,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174123448,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"Connection\\nAcceptor\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"PnQKeDi1dRXt4Ha2TD5bE\",\n      \"originalText\": \"Connection\\nAcceptor\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 43\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 828,\n      \"versionNonce\": 2144361186,\n      \"isDeleted\": false,\n      \"id\": \"KQO5CF1345KP6Te839nND\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 544.1134594557813,\n      \"y\": 431.915371610849,\n      \"strokeColor\": \"#e03131\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 173.27343750000003,\n      \"height\": 55,\n      \"seed\": 1890857954,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"52JTM2oXC7Kgn2NPxYlFQ\"\n        },\n        {\n          \"id\": \"8LrUovEh1sd9l3B9POYc7\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"06UDjCUF1Gslt7UNqxDb7\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"agRbxvveMtgkNyHPODdHa\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 763,\n      \"versionNonce\": 1746975806,\n      \"isDeleted\": false,\n      \"id\": \"52JTM2oXC7Kgn2NPxYlFQ\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 554.5783032057813,\n      \"y\": 447.415371610849,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 152.34375,\n      \"height\": 24,\n      \"seed\": 1837590434,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"ConnectionMap\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"KQO5CF1345KP6Te839nND\",\n      \"originalText\": \"ConnectionMap\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 19\n    },\n    {\n      \"id\": \"8LrUovEh1sd9l3B9POYc7\",\n      \"type\": \"arrow\",\n      \"x\": 628.3450835017004,\n      \"y\": 362.57989883873967,\n      \"width\": 2.441693515080374,\n      \"height\": 55.44022578920959,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 667683618,\n      \"version\": 352,\n      \"versionNonce\": 1357989054,\n      \"isDeleted\": false,\n      \"boundElements\": [],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          2.441693515080374,\n          55.44022578920959\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"Tadf2Hzwo9-pbT_5DQ0-t\",\n        \"focus\": 0.04593164502664871,\n        \"gap\": 9.119305239741664\n      },\n      \"endBinding\": {\n        \"elementId\": \"KQO5CF1345KP6Te839nND\",\n        \"focus\": 0.02116983126981002,\n        \"gap\": 13.895246982899721\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"id\": \"gfGwABmv7BRSmtDJVYNg4\",\n      \"type\": \"text\",\n      \"x\": 652.9337710237655,\n      \"y\": 372.97309265760475,\n      \"width\": 128.90625,\n      \"height\": 24,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"seed\": 670153250,\n      \"version\": 324,\n      \"versionNonce\": 1772967294,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"text\": \"Recv Packet\",\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 19,\n      \"containerId\": null,\n      \"originalText\": \"Recv Packet\",\n      \"lineHeight\": 1.2\n    },\n    {\n      \"id\": \"IawVXDCV_GGXaeFg-_VRP\",\n      \"type\": \"text\",\n      \"x\": 824.8985752864211,\n      \"y\": 430.638340149473,\n      \"width\": 117.1875,\n      \"height\": 24,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"seed\": 217711458,\n      \"version\": 445,\n      \"versionNonce\": 1645531326,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174204059,\n      \"link\": null,\n      \"locked\": false,\n      \"text\": \"Known DCID\",\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 19,\n      \"containerId\": null,\n      \"originalText\": \"Known DCID\",\n      \"lineHeight\": 1.2\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 363,\n      \"versionNonce\": 1687719422,\n      \"isDeleted\": false,\n      \"id\": \"agRbxvveMtgkNyHPODdHa\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 630.9517751493859,\n      \"y\": 499.03068112745865,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 2.441693515080374,\n      \"height\": 55.44022578920959,\n      \"seed\": 1755899362,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"KQO5CF1345KP6Te839nND\",\n        \"focus\": 0.017566014829574636,\n        \"gap\": 12.11530951660967\n      },\n      \"endBinding\": {\n        \"elementId\": \"PnQKeDi1dRXt4Ha2TD5bE\",\n        \"focus\": 0.002865936233535303,\n        \"gap\": 10.899243503699608\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          2.441693515080374,\n          55.44022578920959\n        ]\n      ]\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 611,\n      \"versionNonce\": 964548834,\n      \"isDeleted\": false,\n      \"id\": \"8JxY9x6Jg9dJJkS1oRzcq\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 651.4454188788704,\n      \"y\": 514.1427610437928,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 93.75,\n      \"height\": 24,\n      \"seed\": 1375124670,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"New DCID\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"New DCID\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 19\n    },\n    {\n      \"id\": \"06UDjCUF1Gslt7UNqxDb7\",\n      \"type\": \"arrow\",\n      \"x\": 720.9479757416424,\n      \"y\": 461.2622709745589,\n      \"width\": 462.6313848657859,\n      \"height\": 0,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 670523390,\n      \"version\": 1678,\n      \"versionNonce\": 143793726,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          462.6313848657859,\n          0\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"KQO5CF1345KP6Te839nND\",\n        \"focus\": 0.06715997686217799,\n        \"gap\": 3.5610787858611275\n      },\n      \"endBinding\": {\n        \"elementId\": \"-wGNwwD6aKg4h3sEl21Ox\",\n        \"focus\": -0.06715995517521896,\n        \"gap\": 1.6179145807734585\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"id\": \"_wDA5QE3xKvGoJr1DdJfp\",\n      \"type\": \"arrow\",\n      \"x\": 535.823807029869,\n      \"y\": 556.6559132329007,\n      \"width\": 60.84813651564639,\n      \"height\": 234.896430705848,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 596565502,\n      \"version\": 476,\n      \"versionNonce\": 399797410,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -60.84813651564639,\n          -200.52134328548004\n        ],\n        [\n          0,\n          -234.896430705848\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"PnQKeDi1dRXt4Ha2TD5bE\",\n        \"focus\": -0.8799403007544837,\n        \"gap\": 8.71423718746712\n      },\n      \"endBinding\": {\n        \"elementId\": \"Tadf2Hzwo9-pbT_5DQ0-t\",\n        \"focus\": 0.7564776680831878,\n        \"gap\": 8.289651926856209\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"id\": \"Rb_wV1Jk2lpAZeVHPUgcF\",\n      \"type\": \"text\",\n      \"x\": 484.2855900239055,\n      \"y\": 348.9730934015107,\n      \"width\": 117.1875,\n      \"height\": 48,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"seed\": 745460834,\n      \"version\": 305,\n      \"versionNonce\": 1332717182,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"text\": \"Opt:\\nSend RETRY\",\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 43,\n      \"containerId\": null,\n      \"originalText\": \"Opt:\\nSend RETRY\",\n      \"lineHeight\": 1.2\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1572,\n      \"versionNonce\": 1570932834,\n      \"isDeleted\": false,\n      \"id\": \"a_LqoiLsEAHk3Hk45okpT\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 877.9763839211818,\n      \"y\": 565.3701502246735,\n      \"strokeColor\": \"#f08c00\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 181.54687500000006,\n      \"height\": 58,\n      \"seed\": 409881186,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"0SgRkY2ZRxxjgsq1swJPn\"\n        },\n        {\n          \"id\": \"aiEGoN6Xj1yJIaNtwJ4zY\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"0aT5dlfXL525CHNhpCExI\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1569,\n      \"versionNonce\": 703735486,\n      \"isDeleted\": false,\n      \"id\": \"0SgRkY2ZRxxjgsq1swJPn\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 886.7185714211818,\n      \"y\": 570.3701502246735,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 164.0625,\n      \"height\": 48,\n      \"seed\": 1924836898,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"QuicConnection\\nStream\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"a_LqoiLsEAHk3Hk45okpT\",\n      \"originalText\": \"QuicConnectionStream\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 43\n    },\n    {\n      \"id\": \"aiEGoN6Xj1yJIaNtwJ4zY\",\n      \"type\": \"arrow\",\n      \"x\": 735.3944879661957,\n      \"y\": 594.1881163662968,\n      \"width\": 128.88067519200024,\n      \"height\": 0,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 2124866622,\n      \"version\": 1454,\n      \"versionNonce\": 755062754,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          128.88067519200024,\n          0\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"qNRQdIoTJl6vo9KQBZ3Cs\",\n        \"focus\": -1.157504455249428,\n        \"gap\": 6.244607376047043\n      },\n      \"endBinding\": {\n        \"elementId\": \"a_LqoiLsEAHk3Hk45okpT\",\n        \"focus\": 0.006277029599196982,\n        \"gap\": 13.701220762985884\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 468,\n      \"versionNonce\": 1821379490,\n      \"isDeleted\": false,\n      \"id\": \"qNRQdIoTJl6vo9KQBZ3Cs\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 741.6390953422427,\n      \"y\": 597.968223292283,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 46.875,\n      \"height\": 48,\n      \"seed\": 1807701886,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [\n        {\n          \"id\": \"aiEGoN6Xj1yJIaNtwJ4zY\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"Send\\nConn\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Send\\nConn\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 43\n    },\n    {\n      \"id\": \"0aT5dlfXL525CHNhpCExI\",\n      \"type\": \"arrow\",\n      \"x\": 967.0017358215034,\n      \"y\": 559.3414669738439,\n      \"width\": 168.09740167814698,\n      \"height\": 265.7584145888363,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 907044514,\n      \"version\": 1070,\n      \"versionNonce\": 2006075234,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          16.276008621740175,\n          -234.89643074207498\n        ],\n        [\n          168.09740167814698,\n          -265.7584145888363\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"a_LqoiLsEAHk3Hk45okpT\",\n        \"focus\": -0.044999999356309606,\n        \"gap\": 6.028683250829545\n      },\n      \"endBinding\": {\n        \"elementId\": \"YDVT5jLak02colj1TmLY0\",\n        \"focus\": 0.8410026602009159,\n        \"gap\": 13.886410125349698\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"id\": \"SFZvhlKlMttj2b0MY0itl\",\n      \"type\": \"text\",\n      \"x\": 981.271866218227,\n      \"y\": 239.22346533543993,\n      \"width\": 128.90625,\n      \"height\": 48,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"seed\": 5882750,\n      \"version\": 332,\n      \"versionNonce\": 924964798,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"text\": \"Recv Conn &\\nIQC::start\",\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"baseline\": 43,\n      \"containerId\": null,\n      \"originalText\": \"Recv Conn &\\nIQC::start\",\n      \"lineHeight\": 1.2\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1109,\n      \"versionNonce\": 587475618,\n      \"isDeleted\": false,\n      \"id\": \"dpkaHfA9ceoSe6Ns4Kdeo\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1250.1268316180303,\n      \"y\": 649.43196610596,\n      \"strokeColor\": \"#2f9e44\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 173.27343750000003,\n      \"height\": 62.29688592754598,\n      \"seed\": 1754743458,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"6ZecWVaBWTWkN5SftgPpK\"\n        },\n        {\n          \"id\": \"50mW9qefzW_jxavrb7uxo\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1032,\n      \"versionNonce\": 513984638,\n      \"isDeleted\": false,\n      \"id\": \"6ZecWVaBWTWkN5SftgPpK\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1266.4510503680303,\n      \"y\": 656.5804090697329,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 140.625,\n      \"height\": 48,\n      \"seed\": 2073730658,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"QuicListener\\n(Tx)\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"dpkaHfA9ceoSe6Ns4Kdeo\",\n      \"originalText\": \"QuicListener\\n(Tx)\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 43\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1372,\n      \"versionNonce\": 673875554,\n      \"isDeleted\": false,\n      \"id\": \"-wGNwwD6aKg4h3sEl21Ox\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1185.1972751882017,\n      \"y\": 431.91537220724035,\n      \"strokeColor\": \"#e03131\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 173.27343750000003,\n      \"height\": 55,\n      \"seed\": 1398538366,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"J-ZAsBNWiib4ZGCFBf-90\"\n        },\n        {\n          \"id\": \"06UDjCUF1Gslt7UNqxDb7\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"ReT43_2wsbd3SC9qurqzm\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"UUyMV9A1UcYgYtmIjopvd\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1320,\n      \"versionNonce\": 712050878,\n      \"isDeleted\": false,\n      \"id\": \"J-ZAsBNWiib4ZGCFBf-90\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1219.0996189382017,\n      \"y\": 447.41537220724035,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 105.46875,\n      \"height\": 24,\n      \"seed\": 1665351870,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174044440,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"Packet Rx\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"-wGNwwD6aKg4h3sEl21Ox\",\n      \"originalText\": \"Packet Rx\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 19\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1270,\n      \"versionNonce\": 29217278,\n      \"isDeleted\": false,\n      \"id\": \"MpNVl-BI7pCI6egkIZnGV\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1410.9178970086275,\n      \"y\": 295.4605935567818,\n      \"strokeColor\": \"#1971c2\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 181.54687500000006,\n      \"height\": 58,\n      \"seed\": 352743650,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"QOYJCBX57sgkNoPJSPtQB\"\n        },\n        {\n          \"id\": \"lwMZ2MLWl6kQDyaZ9SOVq\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"AyRdgdKZMK26TeNM3ijrs\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1265,\n      \"versionNonce\": 1698804734,\n      \"isDeleted\": false,\n      \"id\": \"QOYJCBX57sgkNoPJSPtQB\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1425.5194595086275,\n      \"y\": 300.4605935567818,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 152.34375,\n      \"height\": 48,\n      \"seed\": 646579362,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174185972,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"AppOverQuic\\nprocess_reads\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"MpNVl-BI7pCI6egkIZnGV\",\n      \"originalText\": \"AppOverQuic\\nprocess_reads\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 43\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1197,\n      \"versionNonce\": 1483661886,\n      \"isDeleted\": false,\n      \"id\": \"2WsfrpGjotqYrSLy6OYHx\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1410.9178973142873,\n      \"y\": 435.621905652404,\n      \"strokeColor\": \"#1971c2\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 181.54687500000006,\n      \"height\": 58,\n      \"seed\": 481747554,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"tJ6nBBgnzVUUXnzaAUK_x\"\n        },\n        {\n          \"id\": \"AyRdgdKZMK26TeNM3ijrs\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"myqQrUWuJum_Oa3rZSVld\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"heLRHkSwKWLan7kz7RuqR\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1195,\n      \"versionNonce\": 343635262,\n      \"isDeleted\": false,\n      \"id\": \"tJ6nBBgnzVUUXnzaAUK_x\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1419.6600848142873,\n      \"y\": 440.621905652404,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 164.0625,\n      \"height\": 48,\n      \"seed\": 305153570,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174191707,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"AppOverQuic\\nprocess_writes\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"2WsfrpGjotqYrSLy6OYHx\",\n      \"originalText\": \"AppOverQuic\\nprocess_writes\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 43\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1720,\n      \"versionNonce\": 1687291746,\n      \"isDeleted\": false,\n      \"id\": \"AVkoUhZO6zyvxZFb295Fl\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1176.9238383535146,\n      \"y\": 295.46059395328274,\n      \"strokeColor\": \"#3bc9db\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 181.54687500000006,\n      \"height\": 58,\n      \"seed\": 1474509502,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"9GH0Jf9s8Fo3qpu5gkl2f\"\n        },\n        {\n          \"id\": \"ReT43_2wsbd3SC9qurqzm\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"lwMZ2MLWl6kQDyaZ9SOVq\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174141612,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1753,\n      \"versionNonce\": 1848461502,\n      \"isDeleted\": false,\n      \"id\": \"9GH0Jf9s8Fo3qpu5gkl2f\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1203.2441508535146,\n      \"y\": 312.46059395328274,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 128.90625,\n      \"height\": 24,\n      \"seed\": 1063858942,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174145213,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"quiche recv\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"AVkoUhZO6zyvxZFb295Fl\",\n      \"originalText\": \"quiche recv\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 19\n    },\n    {\n      \"id\": \"ReT43_2wsbd3SC9qurqzm\",\n      \"type\": \"arrow\",\n      \"x\": 1268.788724441463,\n      \"y\": 422.65492140908924,\n      \"width\": 0,\n      \"height\": 58.72410767646198,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 1019235838,\n      \"version\": 91,\n      \"versionNonce\": 161796926,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          0,\n          -58.72410767646198\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"-wGNwwD6aKg4h3sEl21Ox\",\n        \"focus\": -0.03514987110172248,\n        \"gap\": 9.260450798151112\n      },\n      \"endBinding\": {\n        \"elementId\": \"AVkoUhZO6zyvxZFb295Fl\",\n        \"focus\": -0.012023876345416957,\n        \"gap\": 10.470219779344518\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"id\": \"lwMZ2MLWl6kQDyaZ9SOVq\",\n      \"type\": \"arrow\",\n      \"x\": 1366.90095312043,\n      \"y\": 323.1103974209402,\n      \"width\": 35.103442137253296,\n      \"height\": 0,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 1763622818,\n      \"version\": 69,\n      \"versionNonce\": 1848870818,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          35.103442137253296,\n          0\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"AVkoUhZO6zyvxZFb295Fl\",\n        \"focus\": -0.04655850111525938,\n        \"gap\": 8.430239766915292\n      },\n      \"endBinding\": {\n        \"elementId\": \"MpNVl-BI7pCI6egkIZnGV\",\n        \"focus\": 0.04655848744281378,\n        \"gap\": 8.913501750944306\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 439,\n      \"versionNonce\": 1868972898,\n      \"isDeleted\": false,\n      \"id\": \"AyRdgdKZMK26TeNM3ijrs\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1503.0686444079186,\n      \"y\": 364.71564758859233,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 0,\n      \"height\": 62.236159748042894,\n      \"seed\": 135215458,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"MpNVl-BI7pCI6egkIZnGV\",\n        \"focus\": -0.015173049927640436,\n        \"gap\": 11.255054031810516\n      },\n      \"endBinding\": {\n        \"elementId\": \"2WsfrpGjotqYrSLy6OYHx\",\n        \"focus\": 0.015173046560358065,\n        \"gap\": 8.670098315768769\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          0,\n          62.236159748042894\n        ]\n      ]\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1827,\n      \"versionNonce\": 1566493474,\n      \"isDeleted\": false,\n      \"id\": \"CXarQzXY0PVjbQ8dudupX\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1410.9178971251156,\n      \"y\": 554.9004749675241,\n      \"strokeColor\": \"#3bc9db\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 181.54687500000006,\n      \"height\": 58,\n      \"seed\": 488356002,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"6PAjumcw88xP9i3VuYnDz\"\n        },\n        {\n          \"id\": \"myqQrUWuJum_Oa3rZSVld\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"50mW9qefzW_jxavrb7uxo\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"dWhDdYJVezZn9p1OF-HCL\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174141612,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1893,\n      \"versionNonce\": 1387780478,\n      \"isDeleted\": false,\n      \"id\": \"6PAjumcw88xP9i3VuYnDz\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1437.2382096251156,\n      \"y\": 571.9004749675241,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 128.90625,\n      \"height\": 24,\n      \"seed\": 1411404898,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174147785,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"quiche send\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"CXarQzXY0PVjbQ8dudupX\",\n      \"originalText\": \"quiche send\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 19\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 561,\n      \"versionNonce\": 1198355454,\n      \"isDeleted\": false,\n      \"id\": \"myqQrUWuJum_Oa3rZSVld\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1504.6446432932414,\n      \"y\": 503.57960637787215,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 0,\n      \"height\": 45.406256676310875,\n      \"seed\": 1504548962,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"2WsfrpGjotqYrSLy6OYHx\",\n        \"focus\": -0.032534941501517034,\n        \"gap\": 9.957700725468158\n      },\n      \"endBinding\": {\n        \"elementId\": \"CXarQzXY0PVjbQ8dudupX\",\n        \"focus\": 0.03253494358551561,\n        \"gap\": 5.914611913341105\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          0,\n          45.406256676310875\n        ]\n      ]\n    },\n    {\n      \"id\": \"50mW9qefzW_jxavrb7uxo\",\n      \"type\": \"arrow\",\n      \"x\": 1503.7217763341932,\n      \"y\": 624.6085600037512,\n      \"width\": 67.34229365614215,\n      \"height\": 52.994926439733945,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 1111565026,\n      \"version\": 514,\n      \"versionNonce\": 16655074,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174082900,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          0,\n          49.4141881667789\n        ],\n        [\n          -67.34229365614215,\n          52.994926439733945\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"CXarQzXY0PVjbQ8dudupX\",\n        \"focus\": -0.02236823640261116,\n        \"gap\": 11.70808503622709\n      },\n      \"endBinding\": {\n        \"elementId\": \"dpkaHfA9ceoSe6Ns4Kdeo\",\n        \"focus\": 0.06488237057858036,\n        \"gap\": 12.979213560020753\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1309,\n      \"versionNonce\": 688498338,\n      \"isDeleted\": false,\n      \"id\": \"Dxr98E7lfYID4iw9bUWFq\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1176.9238381758385,\n      \"y\": 554.9004753355927,\n      \"strokeColor\": \"#1971c2\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 181.54687500000006,\n      \"height\": 58,\n      \"seed\": 959104418,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"78ie6nmwmREziaq4g1C6Q\"\n        },\n        {\n          \"id\": \"dWhDdYJVezZn9p1OF-HCL\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"UUyMV9A1UcYgYtmIjopvd\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"heLRHkSwKWLan7kz7RuqR\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1337,\n      \"versionNonce\": 307728610,\n      \"isDeleted\": false,\n      \"id\": \"78ie6nmwmREziaq4g1C6Q\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1191.5254006758385,\n      \"y\": 559.9004753355927,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 152.34375,\n      \"height\": 48,\n      \"seed\": 348504418,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174194290,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"AppOverQuic\\nwait_for_data\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"Dxr98E7lfYID4iw9bUWFq\",\n      \"originalText\": \"AppOverQuic\\nwait_for_data\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 43\n    },\n    {\n      \"id\": \"dWhDdYJVezZn9p1OF-HCL\",\n      \"type\": \"arrow\",\n      \"x\": 1404.140631159162,\n      \"y\": 586.6527343104282,\n      \"width\": 38.65976628561998,\n      \"height\": 0,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 521227042,\n      \"version\": 53,\n      \"versionNonce\": 1210090082,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -38.65976628561998,\n          0\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"CXarQzXY0PVjbQ8dudupX\",\n        \"focus\": -0.09490549458289795,\n        \"gap\": 6.777265965953575\n      },\n      \"endBinding\": {\n        \"elementId\": \"Dxr98E7lfYID4iw9bUWFq\",\n        \"focus\": 0.09490548189087974,\n        \"gap\": 7.010151697703577\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"id\": \"UUyMV9A1UcYgYtmIjopvd\",\n      \"type\": \"arrow\",\n      \"x\": 1268.8009315037573,\n      \"y\": 546.5484656533323,\n      \"width\": 0,\n      \"height\": 48.69804051218807,\n      \"angle\": 0,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"seed\": 1589268670,\n      \"version\": 103,\n      \"versionNonce\": 703122622,\n      \"isDeleted\": false,\n      \"boundElements\": null,\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          0,\n          -48.69804051218807\n        ]\n      ],\n      \"lastCommittedPoint\": null,\n      \"startBinding\": {\n        \"elementId\": \"Dxr98E7lfYID4iw9bUWFq\",\n        \"focus\": 0.012158356654928135,\n        \"gap\": 8.35200968226036\n      },\n      \"endBinding\": {\n        \"elementId\": \"-wGNwwD6aKg4h3sEl21Ox\",\n        \"focus\": 0.035008971694746295,\n        \"gap\": 10.935052933903876\n      },\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\"\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 452,\n      \"versionNonce\": 2009649634,\n      \"isDeleted\": false,\n      \"id\": \"6wtCy9PjJ92gDqtZeu679\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1471.3976392924349,\n      \"y\": 682.8561344859046,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 128.90625,\n      \"height\": 24,\n      \"seed\": 1837979682,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 3,\n      \"text\": \"Send Packet\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Send Packet\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 19\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 583,\n      \"versionNonce\": 609764770,\n      \"isDeleted\": false,\n      \"id\": \"3SunbRQ6zZQKUtjwv2Lc7\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1163.2588348215984,\n      \"y\": 518.3286266339662,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 93.75,\n      \"height\": 19.2,\n      \"seed\": 749205922,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 16,\n      \"fontFamily\": 3,\n      \"text\": \"New Packet\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"New Packet\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 15\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 218,\n      \"versionNonce\": 544333182,\n      \"isDeleted\": false,\n      \"id\": \"heLRHkSwKWLan7kz7RuqR\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1346.37635949533,\n      \"y\": 547.1117787608915,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 58.724107676462154,\n      \"height\": 55.14336940350711,\n      \"seed\": 851111678,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1738174044441,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"Dxr98E7lfYID4iw9bUWFq\",\n        \"focus\": 0.3246969392165988,\n        \"gap\": 7.7886965747011345\n      },\n      \"endBinding\": {\n        \"elementId\": \"2WsfrpGjotqYrSLy6OYHx\",\n        \"focus\": 0.5545826715691351,\n        \"gap\": 5.817430142495141\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          58.724107676462154,\n          -55.14336940350711\n        ]\n      ]\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1076,\n      \"versionNonce\": 1120277346,\n      \"isDeleted\": false,\n      \"id\": \"g5dShF_hVfWW3fTpKPhyz\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 5.4772162538742535,\n      \"x\": 1312.432181265338,\n      \"y\": 497.250425141144,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 103.125,\n      \"height\": 19.2,\n      \"seed\": 845916734,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1738174067506,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 16,\n      \"fontFamily\": 3,\n      \"text\": \"Timeout/AOQ\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Timeout/AOQ\",\n      \"lineHeight\": 1.2,\n      \"baseline\": 15\n    }\n  ],\n  \"appState\": {\n    \"gridSize\": null,\n    \"viewBackgroundColor\": \"#ffffff\"\n  },\n  \"files\": {}\n}"
  },
  {
    "path": "tokio-quiche/examples/README.md",
    "content": "# Running the example server\n\n⚠️ This example demonstrate simples usage of the tokio-quiche API. It is not\nintended to be used in production environments; no performance, security or\nreliability guarantees are provided.\n\nFirst, start the server. In this example, we'll be listening on\n`127.0.0.1:5757`. We can pass that to the `address` argument to specify it as\nthe listening address:\n\n```shell\nRUST_LOG=info cargo run --example async_http3_server -- --address <listening_address>\n```\n\nVerbosities can be specified with typical [`env_logger`](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) syntax.\n\nThe default TLS certificate covers `test.com`. Certificates can be passed via\nthe `--tls-cert-path` CLI argument, while private keys can be passed via the\n`--tls-private-key-path` argument.\n\nOnce the server is up and running, you can hit it with your favorite client:\n\n```shell\n❮ RUST_LOG=debug cargo run --bin quiche-client -- https://test.com --no-verify --connect-to 127.0.0.1:5757\n\n    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s\n     Running `target/debug/quiche-client 'https://test.com' --no-verify --connect-to '127.0.0.1:5757'`\n[2025-07-08T00:06:11.363952000Z INFO  quiche_apps::client] connecting to 127.0.0.1:5757 from 0.0.0.0:55110 with scid 531c918fbe27cc86abb7bd3e92695f2caf0d7809\n[2025-07-08T00:06:11.369728000Z DEBUG quiche_apps::common] Sent HTTP request [\":method: GET\", \":scheme: https\", \":authority: test.com\", \":path: /\", \"user-agent: quiche\"]\n[2025-07-08T00:06:11.371084000Z DEBUG quiche_apps::common] got response headers [(\":status\", \"200\")] on stream id 0\n[2025-07-08T00:06:11.371113000Z DEBUG quiche_apps::common] 1/1 responses received\n[2025-07-08T00:06:11.371121000Z INFO  quiche_apps::common] 1/1 response(s) received in 6.605ms, closing...\n[2025-07-08T00:06:11.402352000Z INFO  quiche_apps::client] connection closed, recv=9 sent=13 lost=0 retrans=0 sent_bytes=2793 recv_bytes=2436 lost_bytes=0 [local_addr=0.0.0.0:55110 peer_addr=127.0.0.1:5757 validation_state=Validated active=true recv=9 sent=13 lost=0 retrans=0 rtt=2.396186ms min_rtt=Some(199.677µs) rttvar=1.915735ms cwnd=13500 sent_bytes=2793 recv_bytes=2436 lost_bytes=0 stream_retrans_bytes=0 pmtu=1350 delivery_rate=1137163]\n```\n\nThe server should print the events something like this:\n\n```shell\n[2025-07-08T00:06:11.365942000Z INFO  async_http3_server] received new connection!\n[2025-07-08T00:06:11.370735000Z INFO  async_http3_server::server] received unhandled event: IncomingSettings { settings: [(2777032016412723649, 2920255815440916575)] }\n[2025-07-08T00:06:11.370759000Z INFO  async_http3_server::server] received headers: IncomingH3Headers { stream_id: 0, headers: [\":method: GET\", \":scheme: https\", \":authority: test.com\", \":path: /\", \"user-agent: quiche\"], read_fin: true, h3_audit_stats: H3AuditStats { stream_id: 0, downstream_bytes_sent: 0, downstream_bytes_recvd: 0, recvd_stop_sending_error_code: -1, recvd_reset_stream_error_code: -1, sent_stop_sending_error_code: -1, sent_reset_stream_error_code: -1, recvd_stream_fin: AtomicCell { value: Explicit }, sent_stream_fin: AtomicCell { value: None } } }\n[2025-07-08T00:06:11.370838000Z INFO  async_http3_server::server] received unhandled event: BodyBytesReceived { stream_id: 0, num_bytes: 0, fin: true }\n[2025-07-08T00:06:11.370983000Z INFO  async_http3_server::server] received unhandled event: StreamClosed { stream_id: 0 }\n```\n\nLogging can be suppressed entirely by omitting the `RUST_LOG` environment variable.\n\nThe server also exposes a `/stream-bytes/<n>` endpoint. When a request is made to said\nendpoint, `n` bytes will come back in the response body:\n\n```shell\n❯ RUST_LOG=debug cargo run --bin quiche-client -- https://test.com/stream-bytes/3 --no-verify --connect-to 127.0.0.1:5757\n\n   Compiling quiche v0.24.4 (/Users/erittenhouse/Documents/projects/quiche/quiche)\n   Compiling quiche_apps v0.1.0 (/Users/erittenhouse/Documents/projects/quiche/apps)\n    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.66s\n     Running `target/debug/quiche-client 'https://test.com/stream-bytes/3' --no-verify --connect-to '127.0.0.1:5757'`\n[2025-07-08T00:05:42.201487000Z INFO  quiche_apps::client] connecting to 127.0.0.1:5757 from 0.0.0.0:61497 with scid d6d1a81656e0a650c9466cb97de14faba3c8d7f8\n[2025-07-08T00:05:42.210185000Z DEBUG quiche_apps::common] Sent HTTP request [\":method: GET\", \":scheme: https\", \":authority: test.com\", \":path: /stream-bytes/3\", \"user-agent: quiche\"]\n[2025-07-08T00:05:42.211799000Z DEBUG quiche_apps::common] got response headers [(\":status\", \"200\")] on stream id 0\n[2025-07-08T00:05:42.211834000Z DEBUG quiche_apps::common] got 3 bytes of response data on stream 0\n[2025-07-08T00:05:42.211845000Z DEBUG quiche_apps::common] 1/1 responses received\n[2025-07-08T00:05:42.211850000Z INFO  quiche_apps::common] 1/1 response(s) received in 9.849541ms, closing...\n[2025-07-08T00:05:42.270737000Z INFO  quiche_apps::client] connection closed, recv=9 sent=13 lost=0 retrans=0 sent_bytes=2805 recv_bytes=2436 lost_bytes=0 [local_addr=0.0.0.0:61497 peer_addr=127.0.0.1:5757 validation_state=Validated active=true recv=9 sent=13 lost=0 retrans=0 rtt=5.045481ms min_rtt=Some(130.486µs) rttvar=3.559643ms cwnd=13500 sent_bytes=2805 recv_bytes=2436 lost_bytes=0 stream_retrans_bytes=0 pmtu=1350 delivery_rate=1104879]\n```\n"
  },
  {
    "path": "tokio-quiche/examples/async_http3_server/args.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse clap::Parser;\n\n/// Args for setting up an example tokio-quiche server.\n#[derive(Parser, Debug)]\n#[command(version, about, long_about = None)]\npub struct Args {\n    /// The address for the server to listen on.\n    #[arg(short, long)]\n    pub address: String,\n\n    /// Path for the TLS certificate.\n    #[arg(long, default_value_t = default_cert_path())]\n    pub tls_cert_path: String,\n\n    /// Path for the TLS private key.\n    #[arg(long, default_value_t = default_private_key_path())]\n    pub tls_private_key_path: String,\n\n    /// Congestion control algorithm to use (e.g. \"cubic\", \"reno\", \"bbr2\").\n    #[arg(long, default_value = \"cubic\")]\n    pub cc_algorithm: String,\n\n    /// Initial congestion window size in packets.\n    #[arg(long, default_value_t = 10)]\n    pub initial_cwnd_packets: usize,\n\n    /// Disable HyStart++ slow-start algorithm (only affects cubic/reno).\n    #[arg(long, default_value_t = false)]\n    pub disable_hystart: bool,\n\n    /// Enable pacing of outgoing packets.\n    #[arg(long, default_value_t = false)]\n    pub enable_pacing: bool,\n\n    /// Maximum pacing rate in bytes per second (0 = no limit). Only\n    /// meaningful when --enable-pacing is set.\n    #[arg(long, default_value_t = 0)]\n    pub max_pacing_rate: u64,\n}\n\nfn default_cert_path() -> String {\n    path_relative_to_manifest_dir(\"examples/cert.crt\")\n}\n\nfn default_private_key_path() -> String {\n    path_relative_to_manifest_dir(\"examples/cert.key\")\n}\n\nfn path_relative_to_manifest_dir(path: &str) -> String {\n    std::fs::canonicalize({\n        std::path::Path::new(env!(\"CARGO_MANIFEST_DIR\")).join(path)\n    })\n    .unwrap()\n    .to_string_lossy()\n    .into_owned()\n}\n"
  },
  {
    "path": "tokio-quiche/examples/async_http3_server/body.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse futures_util::SinkExt;\nuse futures_util::StreamExt;\nuse http::Request;\nuse http_body::Body;\nuse http_body::Frame;\nuse http_body::SizeHint;\nuse http_body_util::BodyDataStream;\nuse std::pin::Pin;\nuse std::task::Context;\nuse std::task::Poll;\nuse tokio_quiche::buf_factory::BufFactory as BufFactoryImpl;\nuse tokio_quiche::http3::driver::OutboundFrame;\nuse tokio_quiche::http3::driver::OutboundFrameSender;\nuse tokio_util::bytes::Bytes;\n\nconst STREAM_BYTES: &str = \"/stream-bytes/\";\n\n/// An extremely simply response body, for example purposes only.\npub struct ExampleBody {\n    remaining: usize,\n    chunk: Bytes,\n}\n\nimpl ExampleBody {\n    /// Create a new body.\n    ///\n    /// This takes the request's path and sees if the client has requested a\n    /// body back. If so, the body with the requested size is created.\n    ///\n    /// We statically allocate memory at the beginning to avoid allocation costs\n    /// when sending the body back.\n    pub fn new(req: &Request<()>) -> Self {\n        const DUMMY_CONTENT: u8 = 0x57;\n        const CHUNK_SIZE: usize = 1024 * 1024; // 1MB\n        static CHUNK_DATA: [u8; CHUNK_SIZE] = [DUMMY_CONTENT; CHUNK_SIZE];\n\n        let req_path = req.uri().path();\n        let size = if req_path.starts_with(STREAM_BYTES) {\n            req_path\n                .split(\"/\")\n                .last()\n                .and_then(|last| last.parse::<usize>().ok())\n                .unwrap_or(0)\n        } else {\n            0\n        };\n\n        Self {\n            remaining: size,\n            chunk: Bytes::from_static(&CHUNK_DATA),\n        }\n    }\n\n    /// Use the `frame_sender` to send DATA frames to tokio-quiche.\n    ///\n    /// The sender is paired with the underlying tokio-quiche `H3Driver`.\n    pub(crate) async fn send(\n        self, mut frame_sender: OutboundFrameSender,\n    ) -> Option<()> {\n        let mut body_stream = BodyDataStream::new(self);\n\n        while let Some(chunk) = body_stream.next().await {\n            match chunk {\n                Ok(chunk) => {\n                    for chunk in chunk.chunks(BufFactoryImpl::MAX_BUF_SIZE) {\n                        let chunk = OutboundFrame::body(\n                            BufFactoryImpl::buf_from_slice(chunk),\n                            false,\n                        );\n                        frame_sender.send(chunk).await.ok()?;\n                    }\n                },\n                Err(error) => {\n                    log::error!(\"Received error when sending or receiving HTTP body: {error:?}\");\n\n                    let fin_chunk = OutboundFrame::PeerStreamError;\n                    frame_sender.send(fin_chunk).await.ok()?;\n\n                    return None;\n                },\n            }\n        }\n\n        frame_sender\n            .send(OutboundFrame::Body(BufFactoryImpl::get_empty_buf(), true))\n            .await\n            .ok()?;\n\n        Some(())\n    }\n}\n\nimpl Body for ExampleBody {\n    type Data = Bytes;\n    type Error = Box<dyn std::error::Error + Send + Sync>;\n\n    fn poll_frame(\n        mut self: Pin<&mut Self>, _cx: &mut Context<'_>,\n    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {\n        Poll::Ready(match self.remaining {\n            0 => None,\n\n            _ => {\n                let chunk_len = std::cmp::min(self.remaining, self.chunk.len());\n\n                self.remaining -= chunk_len;\n\n                // Borrowing the slice of data and avoid copy.\n                Some(Ok(Frame::data(self.chunk.slice(..chunk_len))))\n            },\n        })\n    }\n\n    fn size_hint(&self) -> SizeHint {\n        SizeHint::with_exact(self.remaining as u64)\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/examples/async_http3_server/main.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod args;\nmod body;\nmod server;\n\nuse crate::args::Args;\nuse crate::server::service_fn;\nuse crate::server::Server;\nuse clap::Parser;\nuse futures::stream::StreamExt;\nuse tokio::net::UdpSocket;\nuse tokio_quiche::http3::settings::Http3Settings;\nuse tokio_quiche::listen;\nuse tokio_quiche::metrics::DefaultMetrics;\nuse tokio_quiche::settings::CertificateKind::{\n    self,\n};\nuse tokio_quiche::settings::Hooks;\nuse tokio_quiche::settings::QuicSettings;\nuse tokio_quiche::settings::TlsCertificatePaths;\nuse tokio_quiche::ConnectionParams;\nuse tokio_quiche::ServerH3Driver;\n\n#[tokio::main]\nasync fn main() {\n    env_logger::builder().format_timestamp_nanos().init();\n\n    // Create listening socket. Note that we use `ConnectionParams::new_server()`\n    // to denote that we're creating a server.\n    let args = Args::parse();\n    let socket = UdpSocket::bind(&args.address)\n        .await\n        .expect(\"UDP socket should be bindable\");\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.qlog_dir = std::env::var(\"QLOGDIR\").ok();\n    quic_settings.cc_algorithm = args.cc_algorithm.clone();\n    quic_settings.initial_congestion_window_packets = args.initial_cwnd_packets;\n    quic_settings.enable_hystart = !args.disable_hystart;\n    quic_settings.enable_pacing = args.enable_pacing;\n    quic_settings.max_pacing_rate =\n        (args.max_pacing_rate > 0).then_some(args.max_pacing_rate);\n\n    let mut listeners = listen(\n        [socket],\n        ConnectionParams::new_server(\n            quic_settings,\n            TlsCertificatePaths {\n                cert: &args.tls_cert_path,\n                private_key: &args.tls_private_key_path,\n                kind: CertificateKind::X509,\n            },\n            Hooks::default(),\n        ),\n        DefaultMetrics,\n    )\n    .expect(\"should be able to create a listener from a UDP socket\");\n\n    // Pull connections off the socket and serve them.\n    let accepted_connection_stream = &mut listeners[0];\n    while let Some(conn_res) = accepted_connection_stream.next().await {\n        match conn_res {\n            Ok(conn) => {\n                log::info!(\"received new connection!\");\n\n                // Create an `H3Driver` to serve the connection.\n                let (driver, mut controller) =\n                    ServerH3Driver::new(Http3Settings::default());\n\n                // Start the driver. This will execute the handshake under the\n                // hood, which lets us start receiving\n                // ServerH3Events without needing to do\n                // anything extra after this future resolves.\n                conn.start(driver);\n\n                // Spawn a task to process the new connection.\n                tokio::spawn(async move {\n                    let mut server = Server::new(service_fn);\n\n                    // tokio-quiche will send events to the `H3Controller`'s\n                    // receiver for processing.\n                    let _ = server\n                        .serve_connection(controller.event_receiver_mut())\n                        .await;\n                });\n            },\n            Err(e) => {\n                log::error!(\"could not create connection: {e:?}\");\n            },\n        }\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/examples/async_http3_server/server.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::body::ExampleBody;\nuse futures_util::Future;\nuse futures_util::SinkExt;\nuse http::request;\nuse http::uri::PathAndQuery;\nuse http::uri::{\n    self,\n};\nuse http::HeaderName;\nuse http::HeaderValue;\nuse http::Request;\nuse http::Response;\nuse http::Uri;\nuse quiche::h3::Header;\nuse quiche::h3::NameValue;\nuse std::sync::Arc;\nuse tokio_quiche::http3::driver::H3Event;\nuse tokio_quiche::http3::driver::IncomingH3Headers;\nuse tokio_quiche::http3::driver::OutboundFrame;\nuse tokio_quiche::http3::driver::OutboundFrameSender;\nuse tokio_quiche::http3::driver::RawPriorityValue;\nuse tokio_quiche::http3::driver::ServerEventStream;\nuse tokio_quiche::http3::driver::ServerH3Event;\nuse tokio_quiche::BoxError;\nuse tokio_quiche::QuicResult;\n\n/// A simple [service function].\n///\n/// If the request's path follows the form `/stream-bytes/<num_bytes>`, the\n/// response will come with a body that is `num_bytes` long.\n///\n/// For example, `https://test.com/stream-bytes/57` will return a 200 response with a body that is\n/// 57 bytes long.\n///\n/// [service function]: https://docs.rs/hyper/latest/hyper/service/index.html\npub async fn service_fn(req: Request<()>) -> Response<ExampleBody> {\n    let body = ExampleBody::new(&req);\n    Response::builder().status(200).body(body).unwrap()\n}\n\n/// A basic asynchronous HTTP/3 server served by tokio-quiche.\n///\n/// Note that this is simply an example, and **should not be run in\n/// production**. This merely shows how one could use tokio-quiche to write an\n/// HTTP/3 server.\npub struct Server<S, R>\nwhere\n    S: Fn(Request<()>) -> R + Send + Sync + 'static,\n{\n    service_fn: Arc<S>,\n}\n\nimpl<S, R> Server<S, R>\nwhere\n    S: Fn(Request<()>) -> R + Send + Sync + 'static,\n    R: Future<Output = Response<ExampleBody>> + Send + 'static,\n{\n    /// Create the server by registering a [service function].\n    ///\n    /// [service function]: https://docs.rs/hyper/latest/hyper/service/index.html\n    pub fn new(service_fn: S) -> Self {\n        Server {\n            service_fn: Arc::new(service_fn),\n        }\n    }\n\n    /// Serve the connection.\n    ///\n    /// The [`Server`] will listen for [`ServerH3Event`]s, process them, and\n    /// send response data back to tokio-quiche for quiche-side processing\n    /// and flushing.\n    ///\n    /// tokio-quiche's `H3Driver` emits these events in response to data that\n    /// comes off the underlying socket.\n    pub async fn serve_connection(\n        &mut self, h3_event_receiver: &mut ServerEventStream,\n    ) -> QuicResult<()> {\n        loop {\n            match h3_event_receiver.recv().await {\n                Some(event) => self.handle_server_h3_event(event).await?,\n                None => return Ok(()), /* The sender was dropped, implying\n                                        * connection was terminated */\n            }\n        }\n    }\n\n    /// Handle a [`H3Event`].\n    ///\n    /// For simplicity's sake, we only handle a couple of events here.\n    fn handle_h3_event(event: H3Event) -> QuicResult<()> {\n        match event {\n            // Received an explicit connection level error. Not much to do here.\n            H3Event::ConnectionError(err) => QuicResult::Err(Box::new(err)),\n\n            // The connection has shutdown.\n            H3Event::ConnectionShutdown(err) => {\n                let err = match err {\n                    Some(err) => Box::new(err),\n                    None => Box::new(quiche::h3::Error::Done) as BoxError,\n                };\n\n                QuicResult::Err(err)\n            },\n\n            _ => {\n                log::info!(\"received unhandled event: {event:?}\");\n                Ok(())\n            },\n        }\n    }\n\n    /// Handle a [`ServerH3Event`].\n    ///\n    /// TODO(evanrittenhouse): support POST requests\n    async fn handle_server_h3_event(\n        &mut self, event: ServerH3Event,\n    ) -> QuicResult<()> {\n        match event {\n            ServerH3Event::Core(event) => Self::handle_h3_event(event),\n\n            ServerH3Event::Headers {\n                incoming_headers,\n                priority,\n                is_in_early_data: _,\n            } => {\n                // Received headers for a new stream from the H3Driver.\n                self.handle_incoming_headers(incoming_headers, priority)\n                    .await;\n                Ok(())\n            },\n        }\n    }\n\n    /// Respond to the request corresponding to the [`IncomingH3Headers`].\n    ///\n    /// This function transforms the incoming headers into a [`Request`],\n    /// creating the proper response body if requested. It then spawns a\n    /// Tokio task which calls the `service_fn` on the [`Request`].\n    async fn handle_incoming_headers(\n        &mut self, headers: IncomingH3Headers,\n        _priority: Option<RawPriorityValue>,\n    ) {\n        log::info!(\"received headers: {:?}\", &headers);\n\n        let IncomingH3Headers {\n            headers: list,\n            send: mut frame_sender,\n            ..\n        } = headers;\n\n        let Ok((uri_builder, req_builder)) = convert_headers(list) else {\n            Self::end_stream(&mut frame_sender).await;\n            return;\n        };\n\n        let uri = uri_builder.build().expect(\"can't build uri\");\n        let req = req_builder.uri(uri).body(()).expect(\"can't build request\");\n\n        let service_fn = Arc::clone(&self.service_fn);\n\n        // TODO: use the _priority input parameter in request handling\n        tokio::spawn(async move {\n            Self::handle_request(service_fn, req, frame_sender).await;\n        });\n    }\n\n    /// Get a [`Response`] for a [`Request`] by calling the `service_fn`.\n    ///\n    /// The `frame_sender` parameter connects back to tokio-quiche `H3Driver`,\n    /// which communicates the data back to quiche.\n    async fn handle_request(\n        service_fn: Arc<S>, req: Request<()>,\n        mut frame_sender: OutboundFrameSender,\n    ) {\n        let res = service_fn(req).await;\n\n        // Convert the result of the `service_fn` into headers and a body which\n        // can be transmitted to tokio-quiche.\n        let (h3_headers, body) = convert_response(res);\n        let _ = frame_sender\n            .send(OutboundFrame::Headers(h3_headers, None))\n            .await;\n\n        body.send(frame_sender).await;\n    }\n\n    /// End the stream.\n    ///\n    /// This will send  STOP_SENDING and RESET_STREAM frames to the client.\n    async fn end_stream(frame_sender: &mut OutboundFrameSender) {\n        let _ = frame_sender.send(OutboundFrame::PeerStreamError).await;\n    }\n}\n\n/// Convert a list of [Header]s into a request object which can be processed by\n/// the [`Server`].\n///\n/// This serves as an example, and does not ensure HTTP semantics by any means.\n/// For example, this will not ensure that required pseudo-headers are present,\n/// nor will it detect duplicate pseudo-headers.\nfn convert_headers(\n    headers: Vec<Header>,\n) -> QuicResult<(uri::Builder, request::Builder)> {\n    let mut req_builder = Request::builder();\n    let mut uri_builder = Uri::builder();\n\n    for header in headers {\n        let name = header.name();\n        let value = header.value();\n\n        let Some(first) = name\n            .iter()\n            .next()\n            .and_then(|f| std::char::from_u32(*f as u32))\n        else {\n            log::warn!(\"received header with no or invalid first character\");\n            continue;\n        };\n\n        if first == ':' {\n            match name {\n                b\":method\" => {\n                    req_builder = req_builder.method(value);\n                },\n                b\":scheme\" => {\n                    uri_builder = uri_builder.scheme(value);\n                },\n                b\":authority\" => {\n                    let host = HeaderValue::from_bytes(value)?;\n                    uri_builder = uri_builder.authority(host.as_bytes());\n                    req_builder.headers_mut().map(|h| h.insert(\"host\", host));\n                },\n                b\":path\" => {\n                    let path = PathAndQuery::try_from(value)?;\n                    uri_builder = uri_builder.path_and_query(path);\n                },\n                _ => {\n                    log::warn!(\"received unknown pseudo-header: {name:?}\");\n                },\n            }\n        } else {\n            req_builder.headers_mut().map(|h| {\n                h.insert(\n                    HeaderName::from_bytes(name).expect(\"invalid header name\"),\n                    HeaderValue::from_bytes(value).expect(\"invalid header value\"),\n                )\n            });\n        }\n    }\n\n    Ok((uri_builder, req_builder))\n}\n\n/// Convert a [`Response`] into headers and a body, readable by tokio-quiche.\nfn convert_response<B>(res: Response<B>) -> (Vec<Header>, B) {\n    let mut h3_headers =\n        vec![Header::new(b\":status\", res.status().as_str().as_bytes())];\n\n    for (name, value) in res.headers().iter() {\n        h3_headers.push(Header::new(name.as_ref(), value.as_bytes()));\n    }\n\n    (h3_headers, res.into_body())\n}\n"
  },
  {
    "path": "tokio-quiche/examples/cert.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDkzCCAnugAwIBAgIUaj26Dyzr2W9R8juKm2pNyrtati0wDQYJKoZIhvcNAQEL\nBQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJcXVpYy50ZWNoMB4X\nDTE4MDkzMDIyMTE0OFoXDTE5MDkzMDIyMTE0OFowWTELMAkGA1UEBhMCQVUxEzAR\nBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5\nIEx0ZDESMBAGA1UEAwwJcXVpYy50ZWNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAqrS30fnkI6Q+5SKsBXkIwnhO61x/Wgt0zo5P+0yTAZDYVYtEhRlf\nmJ3esEleO1nq5MtM3d+6aVBJlwtTi8pBOzVfJklnxd07N3rKh3HZbGHybjhJFGT9\nU4sUrcKcCpSKJaEu7IQsQQs1Hh0B67MeqJG3F7OcYCF3OXC11WK3CtDDKcLcsa2x\n+WImzsPfayzEjQ4ELTVDP73oQGR6D3HaWauKES4JjI9CMn8EJRCcxjwet+c4U3kQ\ng2z5KDbooBfCfrzmX3/EpMf/RaASaUtZF3kgfDT648dICWUoiparo1V73pg2vDe5\nRsAp4n1A7VCY48VvGEz9Qgcp8QFztpFJnwIDAQABo1MwUTAdBgNVHQ4EFgQUFOlS\nIeYH/41CN5BP/8w8F3e/fkYwHwYDVR0jBBgwFoAUFOlSIeYH/41CN5BP/8w8F3e/\nfkYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAZa7XK3My4Jpe\nSLz0BAj44QtghGdg98QFR3iZEnn0XC09HrkhbjaR8Ma3dn0QyMDRuPLLNl5j3VWu\nrDqngENbuJJBPGkCTzozFfMU6MZzGLK1ljIiGzkMXVEaamSj7GDJ2eR2i2cBugiM\nYv7N/e8FbSMRBXoYVPjukoA8QwDJhS/oN47vt0+VsTi5wah9d3t0RCruAe/4TETo\njPxjbEGTQ71dmU66xPZMrnqlGCNa4kN2alCDNfSg1yRp4j10zSmK0jHEHOuiHliW\n/Zc+aLEFcVB1QHmIyvcBIhKiuDbfbkWrqSiel6nLScIvhJaJOrGzQYBfjeZ4TO0m\nIHJUojcgZA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tokio-quiche/examples/cert.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCqtLfR+eQjpD7l\nIqwFeQjCeE7rXH9aC3TOjk/7TJMBkNhVi0SFGV+Ynd6wSV47Werky0zd37ppUEmX\nC1OLykE7NV8mSWfF3Ts3esqHcdlsYfJuOEkUZP1TixStwpwKlIoloS7shCxBCzUe\nHQHrsx6okbcXs5xgIXc5cLXVYrcK0MMpwtyxrbH5YibOw99rLMSNDgQtNUM/vehA\nZHoPcdpZq4oRLgmMj0IyfwQlEJzGPB635zhTeRCDbPkoNuigF8J+vOZff8Skx/9F\noBJpS1kXeSB8NPrjx0gJZSiKlqujVXvemDa8N7lGwCnifUDtUJjjxW8YTP1CBynx\nAXO2kUmfAgMBAAECggEAdWR0KT1NS8luy0qtu9HBWWM8+pSQq87HFClADZRaYDBI\n5YMxqsqJOD4Q33CFEhHC/HZmtQpfen8RLINIgBCmDV6lwYGnkKWUTJHv53c+y08M\nVgn1D8Zng+VYYio7/vapjjkrONGoUU6wx7WxFXMHuWsD25PUDTPWdrTxBv6s3A0X\nLe7UtuCdo/xNY4YS6S64SfiEPsBddj1NhoiwOHkXekpNRoAwnizjngubEkiznScu\ngwKCW4nPV8y4CoIYyncGayrKieg03llgRngFiGJKpKeyL2UkX07Fqb2tXuJ36+RA\n9DrluEkYWZCjOS+aaQu+NwxCkUV5pq+HcXQmF5VX+QKBgQDTrgF4sKwcIjm+k3Fp\nbqhMS5stuSQJVn85fCIeQLq3u5DRq9n+UOvq6GvdEXz0SiupLfkXx/pDwiOux2sn\nCcwMaPqWbFE4mSsCFCBkL/PvXSzH2zYesHOplztvcV+gexAjmoCikMBCcM00QpN1\nGScUmQGTk/7BKJYGnVchJOXbfQKBgQDOcoZryCDxUPsg2ZMwkrnpcM+fSTT1gcgf\nI3gbGohagiXVTDU4+S7I7WmsJv+lBUJCWRG0p8JJZb0NsgnrGyOfCKL59xAV5PyT\nxSXMIi2+OH+fQXblII76GqWCs7A7NxtEU2geSy4ePPzSS4G81FN2oeV1OxZ9a6fk\n6cFIzmqsSwKBgQDIBQlg6NiI8RJNcXdeH/EpvtuQNfzGUhR/1jtLCPEmgjcS2Odx\nNzflzd92knrXP2rIPye7//wMoNsk4UzwI4LLSztWfl21NI5+NVRyNxmyWgHhi9M0\n5pk0bDH+WUv6Ea8rZWgdtNfnMD3HHw3FPZI/FWF2+QZlsRsqfuyA5iPI5QKBgQCu\nD7F2Po5H6FdUIx4O3icRw6PKURbtyDbKykUB1SUR6pmrdU2Kc84WatWl6Fuy7vQm\nrKJZBviwma8EVRA3wfIOrGF9D+noC+FJVffAXTDkKQ6xX6i3FvR1uvHBeW8k/hln\nSkuG/ywrIpCnXjJM21hjtayZYvBbXuF4B/6HPEKEcQKBgQC+DVoOVjsoyd9udTcp\n1v2xvwRVvU/OrPOLXwac1IbTgmb5FJYd8EZI0hdxJhialoTK3OONk04uxdn5tlAB\nQwKBmkXZEr9EIreMp18gbzmDGalx8UcS0j+nIZvmpZXWsIimAKDGEwFc8w+NAN5a\nX5UkSGjM6dnJocH0sLI7hXuVJw==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "tokio-quiche/src/buf_factory.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Pooled buffers for zero-copy packet handling.\n//!\n//! tokio-quiche maintains multiple [`buffer_pool::Pool`] instances for the\n//! lifetime of the program. Buffers from those pools are used for received\n//! network packets and HTTP/3 data, which is passed directly to users of the\n//! crate. Outbound HTTP/3 data (like a message body or a datagram) is provided\n//! by users in the same format.\n//!\n//! [`BufFactory`] provides access to the crate's pools to create outbound\n//! buffers, but users can also use their own custom [`buffer_pool::Pool`]s.\n//! There are two types of built-in pools:\n//! - The generic buffer pool with very large buffers, which is used for stream\n//!   data such as HTTP bodies.\n//! - The datagram pool, which retains buffers the size of a single UDP packet.\n\nuse buffer_pool::ConsumeBuffer;\nuse buffer_pool::Pool;\nuse buffer_pool::Pooled;\nuse datagram_socket::MAX_DATAGRAM_SIZE;\n\nconst POOL_SHARDS: usize = 8;\nconst POOL_SIZE: usize = 16 * 1024;\nconst DATAGRAM_POOL_SIZE: usize = 64 * 1024;\n\nconst TINY_BUF_SIZE: usize = 64;\nconst SMALL_BUF_SIZE: usize = 1024;\nconst MEDIUM_BUF_SIZE: usize = 4096;\nconst MAX_POOL_BUF_SIZE: usize = 64 * 1024;\n\ntype BufPool = Pool<POOL_SHARDS, ConsumeBuffer>;\n\nstatic TINY_POOL: BufPool =\n    BufPool::new(TINY_BUF_SIZE, TINY_BUF_SIZE, \"tiny_pool\");\nstatic SMALL_POOL: BufPool =\n    BufPool::new(SMALL_BUF_SIZE, SMALL_BUF_SIZE, \"small_pool\");\nstatic MEDIUM_POOL: BufPool =\n    BufPool::new(MEDIUM_BUF_SIZE, MEDIUM_BUF_SIZE, \"medium_pool\");\n\n/// A generic buffer pool used to pass data around without copying.\nstatic BUF_POOL: BufPool =\n    BufPool::new(POOL_SIZE, MAX_POOL_BUF_SIZE, \"generic_pool\");\n\n/// A datagram pool shared for both UDP streams, and incoming QUIC packets.\nstatic DATAGRAM_POOL: BufPool =\n    BufPool::new(DATAGRAM_POOL_SIZE, MAX_DATAGRAM_SIZE, \"datagram_pool\");\n\n/// A pooled byte buffer to pass stream data around without copying.\npub type PooledBuf = Pooled<ConsumeBuffer>;\n/// A pooled byte buffer to pass datagrams around without copying.\n///\n/// The buffer type records a head offset, which allows cheaply inserting\n/// data at the front given sufficient capacity.\npub type PooledDgram = Pooled<ConsumeBuffer>;\n\n#[cfg(feature = \"zero-copy\")]\npub use self::zero_copy::QuicheBuf;\n\n/// Prefix size to reserve in a [`PooledDgram`]. Up to 8 bytes for the flow ID\n/// plus 1 byte for the flow context.\nconst DGRAM_PREFIX: usize = 8 + 1;\n\n/// Handle to the crate's static buffer pools.\n#[derive(Default, Clone, Debug)]\npub struct BufFactory;\n\nimpl BufFactory {\n    /// The maximum size of the buffers in the generic pool. Larger buffers\n    /// will shrink to this size before returning to the pool.\n    pub const MAX_BUF_SIZE: usize = MAX_POOL_BUF_SIZE;\n    /// The maximum size of the buffers in the datagram pool.\n    pub const MAX_DGRAM_SIZE: usize = MAX_DATAGRAM_SIZE;\n\n    /// Creates an empty [`PooledBuf`] which is not taken from the pool. When\n    /// dropped, it may be assigned to the generic pool if no longer empty.\n    pub fn get_empty_buf() -> PooledBuf {\n        BUF_POOL.get_empty()\n    }\n\n    /// Creates an empty [`PooledDgram`] which is not taken from the pool. When\n    /// dropped, it may be assigned to the datagram pool if no longer empty.\n    pub fn get_empty_datagram() -> PooledDgram {\n        DATAGRAM_POOL.get_empty()\n    }\n\n    /// Fetches a `MAX_BUF_SIZE` sized [`PooledBuf`] from the generic pool.\n    pub fn get_max_buf() -> PooledBuf {\n        BUF_POOL.get_with(|d| d.expand(MAX_POOL_BUF_SIZE))\n    }\n\n    /// Fetches a `MAX_DATAGRAM_SIZE` sized [`PooledDgram`] from the datagram\n    /// pool.\n    pub fn get_max_datagram() -> PooledDgram {\n        DATAGRAM_POOL.get_with(|d| {\n            d.expand(MAX_DATAGRAM_SIZE);\n            // Make room to inject a prefix\n            d.pop_front(DGRAM_PREFIX);\n        })\n    }\n\n    /// Adds `dgram` to the datagram pool without copying it.\n    pub fn dgram_from_vec(dgram: Vec<u8>) -> PooledDgram {\n        DATAGRAM_POOL.from_owned(ConsumeBuffer::from_vec(dgram))\n    }\n\n    /// Fetches a [`PooledBuf`] from the generic pool and initializes it\n    /// with the contents of `slice`.\n    pub fn buf_from_slice(slice: &[u8]) -> PooledBuf {\n        #[allow(clippy::match_overlapping_arm)]\n        match slice.len() {\n            0 => TINY_POOL.get_empty(),\n            ..=TINY_BUF_SIZE => TINY_POOL.with_slice(slice),\n            ..=SMALL_BUF_SIZE => SMALL_POOL.with_slice(slice),\n            ..=MEDIUM_BUF_SIZE => MEDIUM_POOL.with_slice(slice),\n            _ => BUF_POOL.with_slice(slice),\n        }\n    }\n\n    /// Fetches a [`PooledDgram`] from the datagram pool and initializes it\n    /// with the contents of `slice`.\n    pub fn dgram_from_slice(slice: &[u8]) -> PooledDgram {\n        let mut dgram = Self::get_max_datagram();\n        dgram.truncate(0);\n        dgram.extend(slice);\n        dgram\n    }\n}\n\n#[cfg(feature = \"zero-copy\")]\nmod zero_copy {\n    use super::PooledBuf;\n    use quiche::BufSplit;\n\n    /// A pooled, splittable byte buffer for zero-copy [`quiche`] calls.\n    #[derive(Clone, Debug)]\n    pub struct QuicheBuf {\n        inner: triomphe::Arc<PooledBuf>,\n        start: usize,\n        end: usize,\n    }\n\n    impl QuicheBuf {\n        pub(crate) fn new(inner: PooledBuf) -> Self {\n            QuicheBuf {\n                start: 0,\n                end: inner.len(),\n                inner: triomphe::Arc::new(inner),\n            }\n        }\n    }\n\n    impl AsRef<[u8]> for QuicheBuf {\n        fn as_ref(&self) -> &[u8] {\n            &self.inner[self.start..self.end]\n        }\n    }\n\n    impl BufSplit for QuicheBuf {\n        fn split_at(&mut self, at: usize) -> Self {\n            assert!(self.start + at <= self.end);\n\n            let split = QuicheBuf {\n                inner: self.inner.clone(),\n                start: self.start + at,\n                end: self.end,\n            };\n\n            self.end = self.start + at;\n\n            split\n        }\n\n        fn try_add_prefix(&mut self, prefix: &[u8]) -> bool {\n            if self.start != 0 {\n                return false;\n            }\n\n            if let Some(unique) = triomphe::Arc::get_mut(&mut self.inner) {\n                if unique.add_prefix(prefix) {\n                    self.end += prefix.len();\n                    return true;\n                }\n            }\n\n            false\n        }\n    }\n\n    impl quiche::BufFactory for super::BufFactory {\n        type Buf = QuicheBuf;\n\n        fn buf_from_slice(buf: &[u8]) -> Self::Buf {\n            QuicheBuf::new(Self::buf_from_slice(buf))\n        }\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/AGENTS.md",
    "content": "# HTTP/3 Driver (`tokio-quiche/src/http3/driver/`)\n\n## OVERVIEW\n\nAsync HTTP/3 driver bridging `quiche::h3::Connection` to Tokio tasks via channels. `H3Driver<H: DriverHooks>` is generic over sealed client/server hooks; users interact through `H3Controller` + typed event/command channels.\n\n## STRUCTURE\n\n| File | Role |\n|------|------|\n| `mod.rs` | `H3Driver`, `H3Controller`, `H3Event`, `H3Command`, `OutboundFrame`/`InboundFrame`, channel types, `ApplicationOverQuic` impl |\n| `hooks.rs` | `DriverHooks` trait (sealed). Defines `headers_received`, `conn_established`, `conn_command`, `wait_for_action` |\n| `client.rs` | `ClientHooks` impl, `ClientH3Driver`/`ClientH3Controller` aliases, `ClientH3Event`/`ClientH3Command` |\n| `server.rs` | `ServerHooks` impl, `ServerH3Driver`/`ServerH3Controller` aliases, `ServerH3Event`/`ServerH3Command` |\n| `streams.rs` | `StreamCtx`, `FlowCtx`, `WaitForStream` future, capacity/readiness signals |\n| `datagram.rs` | DATAGRAM/CONNECT-UDP flow handling |\n| `connection.rs` | `H3Conn` wrapper exposing `h3::Connection` operations |\n| `test_utils.rs` | `DriverTestHelper<H>` -- wraps `Pipe` + `H3Driver` for unit tests |\n| `tests.rs` | ~1500 lines of driver tests |\n\n## WHERE TO LOOK\n\n| Task | Start at |\n|------|----------|\n| Channel architecture | `mod.rs:332` (`H3Driver` struct fields: `h3_event_sender`, `cmd_recv`, `stream_map`, `waiting_streams`) |\n| `select!` loop / priority ordering | `mod.rs` `wait_for_data` impl -- uses `biased` select! |\n| Stream lifecycle | `cleanup_stream`, `shutdown_stream`, `process_h3_fin`, `process_h3_data` in `mod.rs` |\n| Per-stream backpressure | `streams.rs` -- `FuturesUnordered<WaitForStream>`, `WaitForDownstreamData`, `WaitForUpstreamCapacity` |\n| Adding endpoint-specific behavior | `hooks.rs` -- add method to `DriverHooks`, impl in `client.rs`/`server.rs` |\n| Writing tests | `test_utils.rs` for `DriverTestHelper`, `tests.rs` for examples |\n\n## ANTI-PATTERNS\n\n- **`connection_not_present()` returns `TlsFail`** -- misleading sentinel error. Do not propagate this pattern.\n- **`process_write_frame` uses `Error::Done` as success** -- non-obvious control flow, don't replicate elsewhere.\n- **`DriverHooks` is sealed** -- `mod hooks` is `pub(crate)`, trait has `#[allow(private_interfaces)]`. Do not expose.\n- **Stream cleanup is distributed** across 4+ functions (`cleanup_stream`, `shutdown_stream`, `process_h3_fin`, `process_h3_data`). Understand all paths before modifying.\n- **`STREAM_CAPACITY`** is 1 in test/debug, 16 in release. Tests exercise backpressure differently from prod.\n\n## NOTES\n\n- `H3Driver::new()` returns `(H3Driver<H>, H3Controller<H>)` -- paired at construction, connected by unbounded mpsc channels.\n- Per-stream channels are bounded (`STREAM_CAPACITY`); per-connection event/cmd channels are unbounded.\n- Datagram flows use a shared `FLOW_CAPACITY=2048` bounded channel, separate from stream channels.\n- `H3Event` variants: `IncomingSettings`, `IncomingHeaders`, `NewFlow`, `ResetStream`, `ConnectionError`, `ConnectionShutdown`, `BodyBytesReceived`, `StreamClosed`.\n- `H3Command` variants: `QuicCmd`, `GoAway`, `ShutdownStream`.\n- Type aliases (`ClientH3Driver`, `ServerH3Driver`, etc.) are the public API; `H3Driver<H>` and `DriverHooks` are internal.\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/client.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::collections::BTreeMap;\nuse std::sync::Arc;\n\nuse foundations::telemetry::log;\nuse quiche::h3;\nuse tokio::sync::mpsc;\nuse tokio::sync::oneshot;\n\nuse super::datagram;\nuse super::DriverHooks;\nuse super::H3Command;\nuse super::H3ConnectionError;\nuse super::H3ConnectionResult;\nuse super::H3Controller;\nuse super::H3Driver;\nuse super::H3Event;\nuse super::InboundFrameStream;\nuse super::InboundHeaders;\nuse super::IncomingH3Headers;\nuse super::OutboundFrameSender;\nuse super::RequestSender;\nuse super::StreamCtx;\nuse super::STREAM_CAPACITY;\nuse crate::http3::settings::Http3Settings;\nuse crate::quic::HandshakeInfo;\nuse crate::quic::QuicCommand;\nuse crate::quic::QuicheConnection;\n\n/// An [H3Driver] for a client-side HTTP/3 connection. See [H3Driver] for\n/// details. Emits [`ClientH3Event`]s and expects [`ClientH3Command`]s for\n/// control.\npub type ClientH3Driver = H3Driver<ClientHooks>;\n/// The [H3Controller] type paired with [ClientH3Driver]. See [H3Controller] for\n/// details.\npub type ClientH3Controller = H3Controller<ClientHooks>;\n/// Receives [`ClientH3Event`]s from a [ClientH3Driver]. This is the control\n/// stream which describes what is happening on the connection, but does not\n/// transfer data.\npub type ClientEventStream = mpsc::UnboundedReceiver<ClientH3Event>;\n/// A [RequestSender] to send HTTP requests over a [ClientH3Driver]'s\n/// connection.\npub type ClientRequestSender = RequestSender<ClientH3Command, NewClientRequest>;\n\n/// An HTTP request sent using a [ClientRequestSender] to the [ClientH3Driver].\n#[derive(Debug)]\npub struct NewClientRequest {\n    /// A user-defined identifier to match [`ClientH3Event::NewOutboundRequest`]\n    /// to its original [`NewClientRequest`]. This ID is not used anywhere else.\n    pub request_id: u64,\n    /// The [`h3::Header`]s that make up this request.\n    pub headers: Vec<h3::Header>,\n    /// A sender to pass the request's [`OutboundFrameSender`] to the request\n    /// body.\n    pub body_writer: Option<oneshot::Sender<OutboundFrameSender>>,\n}\n\n/// Events produced by [ClientH3Driver].\n#[derive(Debug)]\npub enum ClientH3Event {\n    Core(H3Event),\n    /// Headers for the request with the given `request_id` were sent on\n    /// `stream_id`. The body, if there is one, could still be sending.\n    NewOutboundRequest {\n        stream_id: u64,\n        request_id: u64,\n    },\n}\n\nimpl From<H3Event> for ClientH3Event {\n    fn from(ev: H3Event) -> Self {\n        Self::Core(ev)\n    }\n}\n\n/// Commands accepted by [ClientH3Driver].\n#[derive(Debug)]\npub enum ClientH3Command {\n    Core(H3Command),\n    /// Send a new HTTP request over the [`quiche::h3::Connection`]. The driver\n    /// will allocate a stream ID and report it back to the controller via\n    /// [`ClientH3Event::NewOutboundRequest`].\n    ClientRequest(NewClientRequest),\n}\n\nimpl From<H3Command> for ClientH3Command {\n    fn from(cmd: H3Command) -> Self {\n        Self::Core(cmd)\n    }\n}\n\nimpl From<QuicCommand> for ClientH3Command {\n    fn from(cmd: QuicCommand) -> Self {\n        Self::Core(H3Command::QuicCmd(cmd))\n    }\n}\n\nimpl From<NewClientRequest> for ClientH3Command {\n    fn from(req: NewClientRequest) -> Self {\n        Self::ClientRequest(req)\n    }\n}\n\n/// A [`PendingClientRequest`] is a request which has not yet received a\n/// response.\n///\n/// The `send` and `recv` halves are passed to the [ClientH3Controller] in an\n/// [`H3Event::IncomingHeaders`] once the server's response has been received.\nstruct PendingClientRequest {\n    send: OutboundFrameSender,\n    recv: InboundFrameStream,\n}\n\npub struct ClientHooks {\n    /// Mapping from stream IDs to the associated [`PendingClientRequest`].\n    pending_requests: BTreeMap<u64, PendingClientRequest>,\n}\n\nimpl ClientHooks {\n    /// Initiates a client-side request. This sends the request, stores the\n    /// [`PendingClientRequest`] and allocates a new stream plus potential\n    /// DATAGRAM flow (CONNECT-{UDP,IP}).\n    fn initiate_request(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        request: NewClientRequest,\n    ) -> H3ConnectionResult<()> {\n        let body_finished = request.body_writer.is_none();\n\n        // TODO: retry the request if the error is not fatal\n        let stream_id = driver.conn_mut()?.send_request(\n            qconn,\n            &request.headers,\n            body_finished,\n        )?;\n\n        // log::info!(\"sent h3 request\"; \"stream_id\" => stream_id);\n        let (mut stream_ctx, send, recv) =\n            StreamCtx::new(stream_id, STREAM_CAPACITY);\n\n        if let Some(flow_id) =\n            datagram::extract_flow_id(stream_id, &request.headers)\n        {\n            log::info!(\n                \"creating new flow for MASQUE request\";\n                \"stream_id\" => stream_id,\n                \"flow_id\" => flow_id,\n            );\n            let _ = driver.get_or_insert_flow(flow_id)?;\n            stream_ctx.associated_dgram_flow_id = Some(flow_id);\n        }\n\n        if let Some(body_writer) = request.body_writer {\n            let _ = body_writer.send(send.clone());\n            driver\n                .waiting_streams\n                .push(stream_ctx.wait_for_recv(stream_id));\n        }\n\n        driver.insert_stream(stream_id, stream_ctx);\n        driver\n            .hooks\n            .pending_requests\n            .insert(stream_id, PendingClientRequest { send, recv });\n\n        // Notify the H3Controller that we've allocated a stream_id for a\n        // given request_id.\n        let _ = driver\n            .h3_event_sender\n            .send(ClientH3Event::NewOutboundRequest {\n                stream_id,\n                request_id: request.request_id,\n            });\n\n        Ok(())\n    }\n\n    /// Handles a response from the peer by sending a relevant [`H3Event`] to\n    /// the [ClientH3Controller] for application-level processing.\n    fn handle_response(\n        driver: &mut H3Driver<Self>, headers: InboundHeaders,\n        pending_request: PendingClientRequest,\n    ) -> H3ConnectionResult<()> {\n        let InboundHeaders {\n            stream_id,\n            headers,\n            has_body,\n        } = headers;\n\n        let Some(stream_ctx) = driver.stream_map.get(&stream_id) else {\n            // todo(fisher): send better error to client\n            return Err(H3ConnectionError::NonexistentStream);\n        };\n\n        let headers = IncomingH3Headers {\n            stream_id,\n            headers,\n            send: pending_request.send,\n            recv: pending_request.recv,\n            read_fin: !has_body,\n            h3_audit_stats: Arc::clone(&stream_ctx.audit_stats),\n        };\n\n        driver\n            .h3_event_sender\n            .send(H3Event::IncomingHeaders(headers).into())\n            .map_err(|_| H3ConnectionError::ControllerWentAway)\n    }\n}\n\n#[allow(private_interfaces)]\nimpl DriverHooks for ClientHooks {\n    type Command = ClientH3Command;\n    type Event = ClientH3Event;\n\n    fn new(_settings: &Http3Settings) -> Self {\n        Self {\n            pending_requests: BTreeMap::new(),\n        }\n    }\n\n    fn conn_established(\n        _driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        _handshake_info: &HandshakeInfo,\n    ) -> H3ConnectionResult<()> {\n        assert!(\n            !qconn.is_server(),\n            \"ClientH3Driver requires a client-side QUIC connection\"\n        );\n        Ok(())\n    }\n\n    fn headers_received(\n        driver: &mut H3Driver<Self>, _qconn: &mut QuicheConnection,\n        headers: InboundHeaders,\n    ) -> H3ConnectionResult<()> {\n        let Some(pending_request) =\n            driver.hooks.pending_requests.remove(&headers.stream_id)\n        else {\n            // todo(fisher): better handling when an unknown stream_id is\n            // encountered.\n            return Ok(());\n        };\n        Self::handle_response(driver, headers, pending_request)\n    }\n\n    fn conn_command(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        cmd: Self::Command,\n    ) -> H3ConnectionResult<()> {\n        match cmd {\n            ClientH3Command::Core(c) => driver.handle_core_command(qconn, c),\n            ClientH3Command::ClientRequest(req) =>\n                Self::initiate_request(driver, qconn, req),\n        }\n    }\n}\n\nimpl ClientH3Controller {\n    /// Creates a [`NewClientRequest`] sender for the paired [ClientH3Driver].\n    pub fn request_sender(&self) -> ClientRequestSender {\n        RequestSender {\n            sender: self.cmd_sender.clone(),\n            _r: Default::default(),\n        }\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/connection.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::net::SocketAddr;\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse std::task::Poll;\n\nuse datagram_socket::AsSocketStats;\nuse datagram_socket::QuicAuditStats;\nuse datagram_socket::ShutdownConnection;\nuse datagram_socket::SocketStats;\nuse quiche::ConnectionId;\n\nuse super::client;\nuse super::server;\nuse super::DriverHooks;\nuse super::H3Controller;\nuse crate::quic::QuicConnectionStats;\nuse crate::QuicConnection;\n\npub type ClientH3Connection = H3Connection<client::ClientHooks>;\npub type ServerH3Connection = H3Connection<server::ServerHooks>;\n\n/// A wrapper for an h3-driven [QuicConnection] together with the driver's\n/// [H3Controller].\npub struct H3Connection<H: DriverHooks> {\n    pub quic_connection: QuicConnection,\n    pub h3_controller: H3Controller<H>,\n}\n\nimpl<H: DriverHooks> H3Connection<H> {\n    /// Bundles `quic_connection` and `h3_controller` into a new [H3Connection].\n    pub fn new(\n        quic_connection: QuicConnection, h3_controller: H3Controller<H>,\n    ) -> Self {\n        Self {\n            quic_connection,\n            h3_controller,\n        }\n    }\n\n    /// The local address this connection listens on.\n    pub fn local_addr(&self) -> SocketAddr {\n        self.quic_connection.local_addr()\n    }\n\n    /// The remote address for this connection.\n    pub fn peer_addr(&self) -> SocketAddr {\n        self.quic_connection.peer_addr()\n    }\n\n    /// The [QuicConnection]'s audit stats.\n    pub fn audit_log_stats(&self) -> &Arc<QuicAuditStats> {\n        self.quic_connection.audit_log_stats()\n    }\n\n    /// The [QuicConnection]'s [`quiche`] stats.\n    pub fn stats(&self) -> &Arc<Mutex<QuicConnectionStats>> {\n        self.quic_connection.stats()\n    }\n\n    /// The [QuicConnection]'s source connection ID.\n    pub fn scid(&self) -> &ConnectionId<'static> {\n        self.quic_connection.scid()\n    }\n}\n\nimpl<H: DriverHooks> ShutdownConnection for H3Connection<H> {\n    #[inline]\n    fn poll_shutdown(\n        &mut self, _cx: &mut std::task::Context,\n    ) -> Poll<std::io::Result<()>> {\n        // TODO: does nothing at the moment\n        Poll::Ready(Ok(()))\n    }\n}\n\nimpl<H: DriverHooks> AsSocketStats for H3Connection<H> {\n    #[inline]\n    fn as_socket_stats(&self) -> SocketStats {\n        self.quic_connection.as_socket_stats()\n    }\n\n    #[inline]\n    fn as_quic_stats(&self) -> Option<&Arc<QuicAuditStats>> {\n        self.quic_connection.as_quic_stats()\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/datagram.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::InboundFrame;\nuse crate::buf_factory::BufFactory;\nuse crate::buf_factory::PooledDgram;\nuse crate::quic::QuicheConnection;\nuse quiche::h3::NameValue;\nuse quiche::h3::{\n    self,\n};\n\n/// Extracts the DATAGRAM flow ID proxied over the given `stream_id`,\n/// or `None` if this is not a proxy request.\npub(crate) fn extract_flow_id(\n    stream_id: u64, headers: &[h3::Header],\n) -> Option<u64> {\n    let mut method = None;\n    let mut datagram_flow_id: Option<u64> = None;\n    let mut protocol = None;\n\n    for header in headers {\n        match header.name() {\n            b\":method\" => method = Some(header.value()),\n            b\":protocol\" => protocol = Some(header.value()),\n            b\"datagram-flow-id\" =>\n                datagram_flow_id = std::str::from_utf8(header.value())\n                    .ok()\n                    .and_then(|v| v.parse().ok()),\n            _ => {},\n        };\n\n        // We have all of the information needed to get a flow_id or\n        // quarter_stream_id\n        if method.is_some() && (datagram_flow_id.is_some() || protocol.is_some())\n        {\n            break;\n        }\n    }\n\n    // draft-ietf-masque-connect-udp-03 CONNECT-UDP\n    if method == Some(b\"CONNECT-UDP\") && datagram_flow_id.is_some() {\n        datagram_flow_id\n    // RFC 9298 CONNECT-UDP\n    } else if method == Some(b\"CONNECT\") && protocol.is_some() {\n        // we use the quarter_stream_id for RFC 9297\n        // https://www.rfc-editor.org/rfc/rfc9297.html#name-http-3-datagrams\n        Some(stream_id / 4)\n    } else {\n        None\n    }\n}\n\n/// Sends an HTTP/3 datagram over the QUIC connection with the given `flow_id`.\npub(crate) fn send_h3_dgram(\n    conn: &mut QuicheConnection, flow_id: u64, mut dgram: PooledDgram,\n) -> quiche::Result<()> {\n    let mut prefix = [0u8; 8];\n    let mut buf = octets::OctetsMut::with_slice(&mut prefix);\n    let flow_id = buf.put_varint(flow_id)?;\n\n    if dgram.add_prefix(flow_id) {\n        conn.dgram_send(&dgram)\n    } else {\n        let mut inner = dgram.into_inner().into_vec();\n        inner.splice(..0, flow_id.iter().copied());\n        conn.dgram_send_vec(inner)\n    }\n}\n\n/// Reads the next HTTP/3 datagram from the QUIC connection.\n///\n/// [`quiche::Error::Done`] is returned if there is no datagram to read.\npub(crate) fn receive_h3_dgram(\n    conn: &mut QuicheConnection,\n) -> quiche::Result<(u64, InboundFrame)> {\n    let dgram = conn.dgram_recv_vec()?;\n    let mut buf = octets::Octets::with_slice(&dgram);\n    let flow_id = buf.get_varint()?;\n    let advance = buf.off();\n    let datagram =\n        InboundFrame::Datagram(BufFactory::dgram_from_slice(&dgram[advance..]));\n\n    Ok((flow_id, datagram))\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/hooks.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse quiche::h3;\nuse std::future::Future;\n\nuse super::H3Command;\nuse super::H3ConnectionResult;\nuse super::H3Driver;\nuse super::H3Event;\nuse crate::http3::settings::Http3Settings;\nuse crate::quic::HandshakeInfo;\nuse crate::quic::QuicheConnection;\n\n/// A HEADERS frame received from the [`h3::Connection`], to be processed by\n/// the [DriverHooks].\npub(crate) struct InboundHeaders {\n    pub(crate) stream_id: u64,\n    pub(crate) headers: Vec<h3::Header>,\n    pub(crate) has_body: bool,\n}\n\n/// Private trait to customize [H3Driver] for server or client operations.\n///\n/// Wherever endpoint-specific logic is required, a hook should be created in\n/// this trait and this hook then called in the appropriate [H3Driver] code.\n/// The hook can store its own data inside the [H3Driver] struct.\n#[allow(private_interfaces, unused)]\npub trait DriverHooks: Sized + Send + 'static {\n    /// The type of [`H3Event`]s emitted by an [H3Driver] using these hooks.\n    /// The concrete type is expected to wrap [`H3Event`].\n    type Event: From<H3Event> + Send;\n    /// The type of [`H3Command`]s accepted by an [H3Driver] using these hooks.\n    /// The concrete type is expected to wrap [`H3Command`].\n    type Command: From<H3Command> + Send;\n\n    /// Initializes the storage for these hooks.\n    fn new(settings: &Http3Settings) -> Self;\n\n    /// Called in `ApplicationOverQuic::on_conn_established` after [H3Driver]\n    /// has been initialized. Used to verify connection settings and set up\n    /// post-accept state like timeouts.\n    fn conn_established(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        handshake_info: &HandshakeInfo,\n    ) -> H3ConnectionResult<()>;\n\n    /// Processes any received [`h3::Event::Headers`]. There is no default\n    /// processing of HEADERS frames in [H3Driver].\n    fn headers_received(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        headers: InboundHeaders,\n    ) -> H3ConnectionResult<()>;\n\n    /// Processes any command received from the\n    /// [`H3Controller`](super::H3Controller). May use\n    /// `H3Driver::handle_core_command` to handle regular [`H3Command`]s.\n    fn conn_command(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        cmd: Self::Command,\n    ) -> H3ConnectionResult<()>;\n\n    /// Determines whether the hook's `wait_for_action` future will be polled\n    /// as part of `ApplicationOverQuic::wait_for_data`. Defaults to `false` and\n    /// must be overridden if `wait_for_action` is overridden.\n    fn has_wait_action(driver: &mut H3Driver<Self>) -> bool {\n        false\n    }\n\n    /// Returns a future that will be polled in\n    /// `ApplicationOverQuic::wait_for_data`, along with the other input\n    /// sources for the [H3Driver]. Note that the future will be dropped\n    /// before it resolves if another input is available first.\n    fn wait_for_action(\n        &mut self, qconn: &mut QuicheConnection,\n    ) -> impl Future<Output = H3ConnectionResult<()>> + Send {\n        std::future::pending()\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod client;\n/// Wrapper for running HTTP/3 connections.\npub mod connection;\nmod datagram;\n// `DriverHooks` must stay private to prevent users from creating their own\n// H3Drivers.\nmod hooks;\nmod server;\nmod streams;\n#[cfg(test)]\npub mod test_utils;\n#[cfg(test)]\nmod tests;\n\nuse std::collections::BTreeMap;\nuse std::error::Error;\nuse std::fmt;\nuse std::marker::PhantomData;\nuse std::sync::Arc;\nuse std::time::Instant;\n\nuse datagram_socket::StreamClosureKind;\nuse foundations::telemetry::log;\nuse futures::FutureExt;\nuse futures_util::stream::FuturesUnordered;\nuse quiche::h3;\nuse quiche::h3::WireErrorCode;\nuse tokio::select;\nuse tokio::sync::mpsc;\nuse tokio::sync::mpsc::error::TryRecvError;\nuse tokio::sync::mpsc::error::TrySendError;\nuse tokio::sync::mpsc::UnboundedReceiver;\nuse tokio::sync::mpsc::UnboundedSender;\nuse tokio_stream::StreamExt;\nuse tokio_util::sync::PollSender;\n\nuse self::hooks::DriverHooks;\nuse self::hooks::InboundHeaders;\nuse self::streams::FlowCtx;\nuse self::streams::HaveUpstreamCapacity;\nuse self::streams::ReceivedDownstreamData;\nuse self::streams::StreamCtx;\nuse self::streams::StreamReady;\nuse self::streams::WaitForDownstreamData;\nuse self::streams::WaitForStream;\nuse self::streams::WaitForUpstreamCapacity;\nuse crate::buf_factory::BufFactory;\nuse crate::buf_factory::PooledBuf;\nuse crate::buf_factory::PooledDgram;\nuse crate::http3::settings::Http3Settings;\nuse crate::http3::H3AuditStats;\nuse crate::metrics::Metrics;\nuse crate::quic::HandshakeInfo;\nuse crate::quic::QuicCommand;\nuse crate::quic::QuicheConnection;\nuse crate::ApplicationOverQuic;\nuse crate::QuicResult;\n\npub use self::client::ClientEventStream;\npub use self::client::ClientH3Command;\npub use self::client::ClientH3Controller;\npub use self::client::ClientH3Driver;\npub use self::client::ClientH3Event;\npub use self::client::ClientRequestSender;\npub use self::client::NewClientRequest;\npub use self::server::IsInEarlyData;\npub use self::server::RawPriorityValue;\npub use self::server::ServerEventStream;\npub use self::server::ServerH3Command;\npub use self::server::ServerH3Controller;\npub use self::server::ServerH3Driver;\npub use self::server::ServerH3Event;\n\n// The default priority for HTTP/3 responses if the application didn't provide\n// one.\nconst DEFAULT_PRIO: h3::Priority = h3::Priority::new(3, true);\n\n// For a stream use a channel with 16 entries, which works out to 16 * 64KB =\n// 1MB of max buffered data.\n#[cfg(not(any(test, debug_assertions)))]\nconst STREAM_CAPACITY: usize = 16;\n#[cfg(any(test, debug_assertions))]\nconst STREAM_CAPACITY: usize = 1; // Set to 1 to stress write_pending under test conditions\n\n// For *all* flows use a shared channel with 2048 entries, which works out\n// to 3MB of max buffered data at 1500 bytes per datagram.\nconst FLOW_CAPACITY: usize = 2048;\n\n/// Used by a local task to send [`OutboundFrame`]s to a peer on the\n/// stream or flow associated with this channel.\npub type OutboundFrameSender = PollSender<OutboundFrame>;\n\n/// Used internally to receive [`OutboundFrame`]s which should be sent to a peer\n/// on the stream or flow associated with this channel.\ntype OutboundFrameStream = mpsc::Receiver<OutboundFrame>;\n\n/// Used internally to send [`InboundFrame`]s (data) from the peer to a local\n/// task on the stream or flow associated with this channel.\ntype InboundFrameSender = PollSender<InboundFrame>;\n\n/// Used by a local task to receive [`InboundFrame`]s (data) on the stream or\n/// flow associated with this channel.\npub type InboundFrameStream = mpsc::Receiver<InboundFrame>;\n\n/// The error type used internally in [H3Driver].\n///\n/// Note that [`ApplicationOverQuic`] errors are not exposed to users at this\n/// time. The type is public to document the failure modes in [H3Driver].\n#[derive(Debug, PartialEq, Eq)]\n#[non_exhaustive]\npub enum H3ConnectionError {\n    /// The controller task was shut down and is no longer listening.\n    ControllerWentAway,\n    /// Other error at the connection, but not stream level.\n    H3(h3::Error),\n    /// Received a GOAWAY frame from the peer.\n    GoAway,\n    /// Received data for a stream that was closed or never opened.\n    NonexistentStream,\n    /// The server's post-accept timeout was hit.\n    /// The timeout can be configured in [`Http3Settings`].\n    PostAcceptTimeout,\n}\n\nimpl From<h3::Error> for H3ConnectionError {\n    fn from(err: h3::Error) -> Self {\n        H3ConnectionError::H3(err)\n    }\n}\n\nimpl From<quiche::Error> for H3ConnectionError {\n    fn from(err: quiche::Error) -> Self {\n        H3ConnectionError::H3(h3::Error::TransportError(err))\n    }\n}\n\nimpl Error for H3ConnectionError {}\n\nimpl fmt::Display for H3ConnectionError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        let s: &dyn fmt::Display = match self {\n            Self::ControllerWentAway => &\"controller went away\",\n            Self::H3(e) => e,\n            Self::GoAway => &\"goaway\",\n            Self::NonexistentStream => &\"nonexistent stream\",\n            Self::PostAcceptTimeout => &\"post accept timeout hit\",\n        };\n\n        write!(f, \"H3ConnectionError: {s}\")\n    }\n}\n\ntype H3ConnectionResult<T> = Result<T, H3ConnectionError>;\n\n/// HTTP/3 headers that were received on a stream.\n///\n/// `recv` is used to read the message body, while `send` is used to transmit\n/// data back to the peer.\npub struct IncomingH3Headers {\n    /// Stream ID of the frame.\n    pub stream_id: u64,\n    /// The actual [`h3::Header`]s which were received.\n    pub headers: Vec<h3::Header>,\n    /// An [`OutboundFrameSender`] for streaming body data to the peer. For\n    /// [ClientH3Driver], note that the request body can also be passed a\n    /// cloned sender via [`NewClientRequest`].\n    pub send: OutboundFrameSender,\n    /// An [`InboundFrameStream`] of body data received from the peer.\n    pub recv: InboundFrameStream,\n    /// Whether there is a body associated with the incoming headers.\n    pub read_fin: bool,\n    /// Handle to the [`H3AuditStats`] for the message's stream.\n    pub h3_audit_stats: Arc<H3AuditStats>,\n}\n\nimpl fmt::Debug for IncomingH3Headers {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"IncomingH3Headers\")\n            .field(\"stream_id\", &self.stream_id)\n            .field(\"headers\", &self.headers)\n            .field(\"read_fin\", &self.read_fin)\n            .field(\"h3_audit_stats\", &self.h3_audit_stats)\n            .finish()\n    }\n}\n\n/// [`H3Event`]s are produced by an [H3Driver] to describe HTTP/3 state updates.\n///\n/// Both [ServerH3Driver] and [ClientH3Driver] may extend this enum with\n/// endpoint-specific variants. The events must be consumed by users of the\n/// drivers, like a higher-level `Server` or `Client` controller.\n#[derive(Debug)]\npub enum H3Event {\n    /// A SETTINGS frame was received.\n    IncomingSettings {\n        /// Raw HTTP/3 setting pairs, in the order received from the peer.\n        settings: Vec<(u64, u64)>,\n    },\n\n    /// A HEADERS frame was received on the given stream. This is either a\n    /// request or a response depending on the perspective of the [`H3Event`]\n    /// receiver.\n    IncomingHeaders(IncomingH3Headers),\n\n    /// A DATAGRAM flow was created and associated with the given `flow_id`.\n    /// This event is fired before a HEADERS event for CONNECT[-UDP] requests.\n    NewFlow {\n        /// Flow ID of the new flow.\n        flow_id: u64,\n        /// An [`OutboundFrameSender`] for transmitting datagrams to the peer.\n        send: OutboundFrameSender,\n        /// An [`InboundFrameStream`] for receiving datagrams from the peer.\n        recv: InboundFrameStream,\n    },\n    /// A RST_STREAM frame was seen on the given `stream_id`. The user of the\n    /// driver should clean up any state allocated for this stream.\n    ResetStream { stream_id: u64 },\n    /// The connection has irrecoverably errored and is shutting down.\n    ConnectionError(h3::Error),\n    /// The connection has been shutdown, optionally due to an\n    /// [`H3ConnectionError`].\n    ConnectionShutdown(Option<H3ConnectionError>),\n    /// Body data has been received over a stream.\n    BodyBytesReceived {\n        /// Stream ID of the body data.\n        stream_id: u64,\n        /// Number of bytes received.\n        num_bytes: u64,\n        /// Whether the stream is finished and won't yield any more data.\n        fin: bool,\n    },\n    /// The stream has been closed. This is used to signal stream closures that\n    /// don't result from RST_STREAM frames, unlike the\n    /// [`H3Event::ResetStream`] variant.\n    StreamClosed { stream_id: u64 },\n}\n\nimpl H3Event {\n    /// Generates an event from an applicable [`H3ConnectionError`].\n    fn from_error(err: &H3ConnectionError) -> Option<Self> {\n        Some(match err {\n            H3ConnectionError::H3(e) => Self::ConnectionError(*e),\n            H3ConnectionError::PostAcceptTimeout => Self::ConnectionShutdown(\n                Some(H3ConnectionError::PostAcceptTimeout),\n            ),\n            _ => return None,\n        })\n    }\n}\n\n/// An [`OutboundFrame`] is a data frame that should be sent from a local task\n/// to a peer over a [`quiche::h3::Connection`].\n///\n/// This is used, for example, to send response body data to a peer, or proxied\n/// UDP datagrams.\n#[derive(Debug)]\npub enum OutboundFrame {\n    /// Response headers to be sent to the peer, with optional priority.\n    Headers(Vec<h3::Header>, Option<quiche::h3::Priority>),\n    /// Response body/CONNECT downstream data plus FIN flag.\n    #[cfg(feature = \"zero-copy\")]\n    Body(crate::buf_factory::QuicheBuf, bool),\n    /// Response body/CONNECT downstream data plus FIN flag.\n    #[cfg(not(feature = \"zero-copy\"))]\n    Body(PooledBuf, bool),\n    /// CONNECT-UDP (DATAGRAM) downstream data plus flow ID.\n    Datagram(PooledDgram, u64),\n    /// Close the stream with a trailers, with optional priority.\n    Trailers(Vec<h3::Header>, Option<quiche::h3::Priority>),\n    /// An error encountered when serving the request. Stream should be closed.\n    PeerStreamError,\n    /// DATAGRAM flow explicitly closed.\n    FlowShutdown { flow_id: u64, stream_id: u64 },\n}\n\nimpl OutboundFrame {\n    /// Creates a body frame with the provided buffer.\n    pub fn body(body: PooledBuf, fin: bool) -> Self {\n        #[cfg(feature = \"zero-copy\")]\n        let body = crate::buf_factory::QuicheBuf::new(body);\n\n        OutboundFrame::Body(body, fin)\n    }\n}\n\n/// An [`InboundFrame`] is a data frame that was received from the peer over a\n/// [`quiche::h3::Connection`]. This is used by peers to send body or datagrams\n/// to the local task.\n#[derive(Debug)]\npub enum InboundFrame {\n    /// Request body/CONNECT upstream data plus FIN flag.\n    Body(PooledBuf, bool),\n    /// CONNECT-UDP (DATAGRAM) upstream data.\n    Datagram(PooledDgram),\n}\n\n/// A ready-made [`ApplicationOverQuic`] which can handle HTTP/3 and MASQUE.\n/// Depending on the `DriverHooks` in use, it powers either a client or a\n/// server.\n///\n/// Use the [ClientH3Driver] and [ServerH3Driver] aliases to access the\n/// respective driver types. The driver is passed into an I/O loop and\n/// communicates with the driver's user (e.g., an HTTP client or a server) via\n/// its associated [H3Controller]. The controller allows the application to both\n/// listen for [`H3Event`]s of note and send [`H3Command`]s into the I/O loop.\npub struct H3Driver<H: DriverHooks> {\n    /// Configuration used to initialize `conn`. Created from [`Http3Settings`]\n    /// in the constructor.\n    h3_config: h3::Config,\n    /// The underlying HTTP/3 connection. Initialized in\n    /// `ApplicationOverQuic::on_conn_established`.\n    conn: Option<h3::Connection>,\n    /// State required by the client/server hooks.\n    hooks: H,\n    /// Sends [`H3Event`]s to the [H3Controller] paired with this driver.\n    h3_event_sender: mpsc::UnboundedSender<H::Event>,\n    /// Receives [`H3Command`]s from the [H3Controller] paired with this driver.\n    cmd_recv: mpsc::UnboundedReceiver<H::Command>,\n\n    /// A map of stream IDs to their [StreamCtx]. This is mainly used to\n    /// retrieve the internal Tokio channels associated with the stream.\n    stream_map: BTreeMap<u64, StreamCtx>,\n    /// A map of flow IDs to their [FlowCtx]. This is mainly used to retrieve\n    /// the internal Tokio channels associated with the flow.\n    flow_map: BTreeMap<u64, FlowCtx>,\n    /// Set of [`WaitForStream`] futures. A stream is added to this set if\n    /// we need to send to it and its channel is at capacity, or if we need\n    /// data from its channel and the channel is empty.\n    waiting_streams: FuturesUnordered<WaitForStream>,\n\n    /// Receives [`OutboundFrame`]s from all datagram flows on the connection.\n    dgram_recv: OutboundFrameStream,\n    /// Keeps the datagram channel open such that datagram flows can be created.\n    dgram_send: OutboundFrameSender,\n\n    /// The buffer used to interact with the underlying IoWorker.\n    pooled_buf: PooledBuf,\n    /// The maximum HTTP/3 stream ID seen on this connection.\n    max_stream_seen: u64,\n\n    /// Tracks whether we have forwarded the HTTP/3 SETTINGS frame\n    /// to the [H3Controller] once.\n    settings_received_and_forwarded: bool,\n}\n\nimpl<H: DriverHooks> H3Driver<H> {\n    /// Builds a new [H3Driver] and an associated [H3Controller].\n    ///\n    /// The driver should then be passed to\n    /// [`InitialQuicConnection`](crate::InitialQuicConnection)'s `start`\n    /// method.\n    pub fn new(http3_settings: Http3Settings) -> (Self, H3Controller<H>) {\n        let (dgram_send, dgram_recv) = mpsc::channel(FLOW_CAPACITY);\n        let (cmd_sender, cmd_recv) = mpsc::unbounded_channel();\n        let (h3_event_sender, h3_event_recv) = mpsc::unbounded_channel();\n\n        (\n            H3Driver {\n                h3_config: (&http3_settings).into(),\n                conn: None,\n                hooks: H::new(&http3_settings),\n                h3_event_sender,\n                cmd_recv,\n\n                stream_map: BTreeMap::new(),\n                flow_map: BTreeMap::new(),\n\n                dgram_recv,\n                dgram_send: PollSender::new(dgram_send),\n                pooled_buf: BufFactory::get_max_buf(),\n                max_stream_seen: 0,\n\n                waiting_streams: FuturesUnordered::new(),\n\n                settings_received_and_forwarded: false,\n            },\n            H3Controller {\n                cmd_sender,\n                h3_event_recv: Some(h3_event_recv),\n            },\n        )\n    }\n\n    /// Retrieve the [FlowCtx] associated with the given `flow_id`. If no\n    /// context is found, a new one will be created.\n    fn get_or_insert_flow(\n        &mut self, flow_id: u64,\n    ) -> H3ConnectionResult<&mut FlowCtx> {\n        use std::collections::btree_map::Entry;\n        Ok(match self.flow_map.entry(flow_id) {\n            Entry::Vacant(e) => {\n                // This is a datagram for a new flow we haven't seen before\n                let (flow, recv) = FlowCtx::new(FLOW_CAPACITY);\n                let flow_req = H3Event::NewFlow {\n                    flow_id,\n                    recv,\n                    send: self.dgram_send.clone(),\n                };\n                self.h3_event_sender\n                    .send(flow_req.into())\n                    .map_err(|_| H3ConnectionError::ControllerWentAway)?;\n                e.insert(flow)\n            },\n            Entry::Occupied(e) => e.into_mut(),\n        })\n    }\n\n    /// Adds a [StreamCtx] to the stream map with the given `stream_id`.\n    fn insert_stream(&mut self, stream_id: u64, ctx: StreamCtx) {\n        self.stream_map.insert(stream_id, ctx);\n        self.max_stream_seen = self.max_stream_seen.max(stream_id);\n    }\n\n    /// Fetches body chunks from the [`quiche::h3::Connection`] and forwards\n    /// them to the stream's associated [`InboundFrameStream`].\n    fn process_h3_data(\n        &mut self, qconn: &mut QuicheConnection, stream_id: u64,\n    ) -> H3ConnectionResult<()> {\n        // Split self borrow between conn and stream_map\n        let conn = self.conn.as_mut().ok_or(Self::connection_not_present())?;\n        let ctx = self\n            .stream_map\n            .get_mut(&stream_id)\n            .ok_or(H3ConnectionError::NonexistentStream)?;\n\n        enum StreamStatus {\n            Done { close: bool },\n            Reset { wire_err_code: u64 },\n            Blocked,\n        }\n\n        let status = loop {\n            let Some(sender) = ctx.send.as_ref().and_then(PollSender::get_ref)\n            else {\n                // already waiting for capacity\n                break StreamStatus::Done { close: false };\n            };\n\n            let try_reserve_result = sender.try_reserve();\n            let permit = match try_reserve_result {\n                Ok(permit) => permit,\n                Err(TrySendError::Closed(())) => {\n                    // The channel has closed before we delivered a fin or reset\n                    // to the application.\n                    if !ctx.fin_or_reset_recv &&\n                        ctx.associated_dgram_flow_id.is_none()\n                    // The channel might be closed if the stream was used to\n                    // initiate a datagram exchange.\n                    // TODO: ideally, the application would still shut down the\n                    // stream properly. Once applications code\n                    // is fixed, we can remove this check.\n                    {\n                        let err = h3::WireErrorCode::RequestCancelled as u64;\n                        let _ = qconn.stream_shutdown(\n                            stream_id,\n                            quiche::Shutdown::Read,\n                            err,\n                        );\n                        drop(try_reserve_result); // needed to drop the borrow on ctx.\n                        ctx.handle_sent_stop_sending(err);\n                        // TODO: should we send an H3Event event to\n                        // h3_event_sender? We can only get here if the app\n                        // actively closed or dropped\n                        // the channel so any event we send would be more for\n                        // logging or auditing\n                    }\n                    break StreamStatus::Done {\n                        close: ctx.both_directions_done(),\n                    };\n                },\n                Err(TrySendError::Full(())) => {\n                    if ctx.fin_or_reset_recv || qconn.stream_readable(stream_id) {\n                        break StreamStatus::Blocked;\n                    }\n                    break StreamStatus::Done { close: false };\n                },\n            };\n\n            if ctx.fin_or_reset_recv {\n                // Signal end-of-body to upstream\n                permit\n                    .send(InboundFrame::Body(BufFactory::get_empty_buf(), true));\n                break StreamStatus::Done {\n                    close: ctx.fin_or_reset_sent,\n                };\n            }\n\n            match conn.recv_body(qconn, stream_id, &mut self.pooled_buf) {\n                Ok(n) => {\n                    let mut body = std::mem::replace(\n                        &mut self.pooled_buf,\n                        BufFactory::get_max_buf(),\n                    );\n                    body.truncate(n);\n\n                    ctx.audit_stats.add_downstream_bytes_recvd(n as u64);\n                    let event = H3Event::BodyBytesReceived {\n                        stream_id,\n                        num_bytes: n as u64,\n                        fin: false,\n                    };\n                    let _ = self.h3_event_sender.send(event.into());\n\n                    permit.send(InboundFrame::Body(body, false));\n                },\n                Err(h3::Error::Done) =>\n                    break StreamStatus::Done { close: false },\n                Err(h3::Error::TransportError(quiche::Error::StreamReset(\n                    code,\n                ))) => {\n                    break StreamStatus::Reset {\n                        wire_err_code: code,\n                    };\n                },\n                Err(_) => break StreamStatus::Done { close: true },\n            }\n        };\n\n        match status {\n            StreamStatus::Done { close } => {\n                if close {\n                    return self.cleanup_stream(qconn, stream_id);\n                }\n\n                // The QUIC stream is finished, manually invoke `process_h3_fin`\n                // in case `h3::poll()` is never called again.\n                //\n                // Note that this case will not conflict with StreamStatus::Done\n                // being returned due to the body channel being\n                // blocked. qconn.stream_finished() will guarantee\n                // that we've fully parsed the body as it only returns true\n                // if we've seen a Fin for the read half of the stream.\n                if !ctx.fin_or_reset_recv && qconn.stream_finished(stream_id) {\n                    return self.process_h3_fin(qconn, stream_id);\n                }\n            },\n            StreamStatus::Reset { wire_err_code } => {\n                debug_assert!(ctx.send.is_some());\n                ctx.handle_recvd_reset(wire_err_code);\n                self.h3_event_sender\n                    .send(H3Event::ResetStream { stream_id }.into())\n                    .map_err(|_| H3ConnectionError::ControllerWentAway)?;\n                if ctx.both_directions_done() {\n                    return self.cleanup_stream(qconn, stream_id);\n                }\n            },\n            StreamStatus::Blocked => {\n                self.waiting_streams.push(ctx.wait_for_send(stream_id));\n            },\n        }\n\n        Ok(())\n    }\n\n    /// Processes an end-of-stream event from the [`quiche::h3::Connection`].\n    fn process_h3_fin(\n        &mut self, qconn: &mut QuicheConnection, stream_id: u64,\n    ) -> H3ConnectionResult<()> {\n        let ctx = self\n            .stream_map\n            .get_mut(&stream_id)\n            .filter(|c| !c.fin_or_reset_recv);\n        let Some(ctx) = ctx else {\n            // Stream is already finished, nothing to do\n            return Ok(());\n        };\n\n        ctx.fin_or_reset_recv = true;\n        ctx.audit_stats\n            .set_recvd_stream_fin(StreamClosureKind::Explicit);\n\n        // It's important to send this H3Event before process_h3_data so that\n        // a server can (potentially) generate the control response before the\n        // corresponding receiver drops.\n        let event = H3Event::BodyBytesReceived {\n            stream_id,\n            num_bytes: 0,\n            fin: true,\n        };\n        let _ = self.h3_event_sender.send(event.into());\n\n        // Communicate fin to upstream. Since `ctx.fin_recv` is true now,\n        // there can't be a recursive loop.\n        self.process_h3_data(qconn, stream_id)\n    }\n\n    /// Processes a single [`quiche::h3::Event`] received from the underlying\n    /// [`quiche::h3::Connection`]. Some events are dispatched to helper\n    /// methods.\n    fn process_read_event(\n        &mut self, qconn: &mut QuicheConnection, stream_id: u64, event: h3::Event,\n    ) -> H3ConnectionResult<()> {\n        self.forward_settings()?;\n\n        match event {\n            // Requests/responses are exclusively handled by hooks.\n            h3::Event::Headers { list, more_frames } =>\n                H::headers_received(self, qconn, InboundHeaders {\n                    stream_id,\n                    headers: list,\n                    has_body: more_frames,\n                }),\n\n            h3::Event::Data => self.process_h3_data(qconn, stream_id),\n            h3::Event::Finished => self.process_h3_fin(qconn, stream_id),\n\n            h3::Event::Reset(code) => {\n                if let Some(ctx) = self.stream_map.get_mut(&stream_id) {\n                    ctx.handle_recvd_reset(code);\n                    // See if we are waiting on this stream and close the channel\n                    // if we are. If we are not waiting, `handle_recvd_reset()`\n                    // will have taken care of closing.\n                    for pending in self.waiting_streams.iter_mut() {\n                        match pending {\n                            WaitForStream::Upstream(\n                                WaitForUpstreamCapacity {\n                                    stream_id: id,\n                                    chan: Some(chan),\n                                },\n                            ) if stream_id == *id => {\n                                chan.close();\n                            },\n                            _ => {},\n                        }\n                    }\n\n                    self.h3_event_sender\n                        .send(H3Event::ResetStream { stream_id }.into())\n                        .map_err(|_| H3ConnectionError::ControllerWentAway)?;\n                    if ctx.both_directions_done() {\n                        return self.cleanup_stream(qconn, stream_id);\n                    }\n                }\n\n                // TODO: if we don't have the stream in our map: should we\n                // send the H3Event::ResetStream?\n                Ok(())\n            },\n\n            h3::Event::PriorityUpdate => Ok(()),\n            h3::Event::GoAway => Err(H3ConnectionError::GoAway),\n        }\n    }\n\n    /// The SETTINGS frame can be received at any point, so we\n    /// need to check `peer_settings_raw` to decide if we've received it.\n    ///\n    /// Settings should only be sent once, so we generate a single event\n    /// when `peer_settings_raw` transitions from None to Some.\n    fn forward_settings(&mut self) -> H3ConnectionResult<()> {\n        if self.settings_received_and_forwarded {\n            return Ok(());\n        }\n\n        // capture the peer settings and forward it\n        if let Some(settings) = self.conn_mut()?.peer_settings_raw() {\n            let incoming_settings = H3Event::IncomingSettings {\n                settings: settings.to_vec(),\n            };\n\n            self.h3_event_sender\n                .send(incoming_settings.into())\n                .map_err(|_| H3ConnectionError::ControllerWentAway)?;\n\n            self.settings_received_and_forwarded = true;\n        }\n        Ok(())\n    }\n\n    /// Send an individual frame to the underlying [`quiche::h3::Connection`] to\n    /// be flushed at a later time.\n    ///\n    /// `Self::process_writes` will iterate over all writable streams and call\n    /// this method in a loop for each stream to send all writable packets.\n    fn process_write_frame(\n        conn: &mut h3::Connection, qconn: &mut QuicheConnection,\n        ctx: &mut StreamCtx,\n    ) -> h3::Result<()> {\n        let Some(frame) = &mut ctx.queued_frame else {\n            return Ok(());\n        };\n\n        let audit_stats = &ctx.audit_stats;\n        let stream_id = audit_stats.stream_id();\n\n        match frame {\n            OutboundFrame::Headers(headers, priority) => {\n                let prio = priority.as_ref().unwrap_or(&DEFAULT_PRIO);\n\n                let res = if ctx.initial_headers_sent {\n                    // Initial headers were already sent, send additional\n                    // headers now.\n                    conn.send_additional_headers_with_priority(\n                        qconn, stream_id, headers, prio, false, false,\n                    )\n                } else {\n                    // Send initial headers.\n                    conn.send_response_with_priority(\n                        qconn, stream_id, headers, prio, false,\n                    )\n                    .inspect(|_| ctx.initial_headers_sent = true)\n                };\n\n                if let Err(h3::Error::StreamBlocked) = res {\n                    ctx.first_full_headers_flush_fail_time\n                        .get_or_insert(Instant::now());\n                }\n\n                if res.is_ok() {\n                    if let Some(first) =\n                        ctx.first_full_headers_flush_fail_time.take()\n                    {\n                        ctx.audit_stats.add_header_flush_duration(\n                            Instant::now().duration_since(first),\n                        );\n                    }\n                }\n\n                res\n            },\n\n            OutboundFrame::Body(body, fin) => {\n                let len = body.as_ref().len();\n                if len == 0 && !*fin {\n                    // quiche doesn't allow sending an empty body when the fin\n                    // flag is not set\n                    return Ok(());\n                }\n                if *fin {\n                    // If this is the last body frame, drop the receiver in the\n                    // stream map to signal that we shouldn't receive any more\n                    // frames. NOTE: we can't use `mpsc::Receiver::close()`\n                    // due to an inconsistency in how tokio handles reading\n                    // from a closed mpsc channel https://github.com/tokio-rs/tokio/issues/7631\n                    ctx.recv = None;\n                }\n                #[cfg(feature = \"zero-copy\")]\n                let n = conn.send_body_zc(qconn, stream_id, body, *fin)?;\n\n                #[cfg(not(feature = \"zero-copy\"))]\n                let n = conn.send_body(qconn, stream_id, body, *fin)?;\n\n                audit_stats.add_downstream_bytes_sent(n as _);\n                if n != len {\n                    // Couldn't write the entire body, keep what remains for\n                    // future retry.\n                    #[cfg(not(feature = \"zero-copy\"))]\n                    body.pop_front(n);\n\n                    Err(h3::Error::StreamBlocked)\n                } else {\n                    if *fin {\n                        Self::on_fin_sent(ctx)?;\n                    }\n                    Ok(())\n                }\n            },\n\n            OutboundFrame::Trailers(headers, priority) => {\n                let prio = priority.as_ref().unwrap_or(&DEFAULT_PRIO);\n\n                // trailers always set fin=true\n                let res = conn.send_additional_headers_with_priority(\n                    qconn, stream_id, headers, prio, true, true,\n                );\n\n                if res.is_ok() {\n                    Self::on_fin_sent(ctx)?;\n                }\n                res\n            },\n\n            OutboundFrame::PeerStreamError => Err(h3::Error::MessageError),\n\n            OutboundFrame::FlowShutdown { .. } => {\n                unreachable!(\"Only flows send shutdowns\")\n            },\n\n            OutboundFrame::Datagram(..) => {\n                unreachable!(\"Only flows send datagrams\")\n            },\n        }\n    }\n\n    fn on_fin_sent(ctx: &mut StreamCtx) -> h3::Result<()> {\n        ctx.recv = None;\n        ctx.fin_or_reset_sent = true;\n        ctx.audit_stats\n            .set_sent_stream_fin(StreamClosureKind::Explicit);\n        if ctx.fin_or_reset_recv {\n            // Return a TransportError to trigger stream cleanup\n            // instead of h3::Error::Done\n            Err(h3::Error::TransportError(quiche::Error::Done))\n        } else {\n            Ok(())\n        }\n    }\n\n    /// Resumes reads or writes to the connection when a stream channel becomes\n    /// unblocked.\n    ///\n    /// If we were waiting for more data from a channel, we resume writing to\n    /// the connection. Otherwise, we were blocked on channel capacity and\n    /// continue reading from the connection. `Upstream` in this context is\n    /// the consumer of the stream.\n    fn upstream_ready(\n        &mut self, qconn: &mut QuicheConnection, ready: StreamReady,\n    ) -> H3ConnectionResult<()> {\n        match ready {\n            StreamReady::Downstream(r) => self.upstream_read_ready(qconn, r),\n            StreamReady::Upstream(r) => self.upstream_write_ready(qconn, r),\n        }\n    }\n\n    fn upstream_read_ready(\n        &mut self, qconn: &mut QuicheConnection,\n        read_ready: ReceivedDownstreamData,\n    ) -> H3ConnectionResult<()> {\n        let ReceivedDownstreamData {\n            stream_id,\n            chan,\n            data,\n        } = read_ready;\n\n        match self.stream_map.get_mut(&stream_id) {\n            None => Ok(()),\n            Some(stream) => {\n                stream.recv = Some(chan);\n                stream.queued_frame = data;\n                self.process_writable_stream(qconn, stream_id)\n            },\n        }\n    }\n\n    fn upstream_write_ready(\n        &mut self, qconn: &mut QuicheConnection,\n        write_ready: HaveUpstreamCapacity,\n    ) -> H3ConnectionResult<()> {\n        let HaveUpstreamCapacity {\n            stream_id,\n            mut chan,\n        } = write_ready;\n\n        match self.stream_map.get_mut(&stream_id) {\n            None => Ok(()),\n            Some(stream) => {\n                chan.abort_send(); // Have to do it to release the associated permit\n                stream.send = Some(chan);\n                self.process_h3_data(qconn, stream_id)\n            },\n        }\n    }\n\n    /// Processes all queued outbound datagrams from the `dgram_recv` channel.\n    fn dgram_ready(\n        &mut self, qconn: &mut QuicheConnection, frame: OutboundFrame,\n    ) -> H3ConnectionResult<()> {\n        let mut frame = Ok(frame);\n\n        loop {\n            match frame {\n                Ok(OutboundFrame::Datagram(dgram, flow_id)) => {\n                    // Drop datagrams if there is no capacity\n                    let _ = datagram::send_h3_dgram(qconn, flow_id, dgram);\n                },\n                Ok(OutboundFrame::FlowShutdown { flow_id, stream_id }) => {\n                    self.shutdown_stream(\n                        qconn,\n                        stream_id,\n                        StreamShutdown::Both {\n                            read_error_code: WireErrorCode::NoError as u64,\n                            write_error_code: WireErrorCode::NoError as u64,\n                        },\n                    )?;\n                    self.flow_map.remove(&flow_id);\n                    break;\n                },\n                Ok(_) => unreachable!(\"Flows can't send frame of other types\"),\n                Err(TryRecvError::Empty) => break,\n                Err(TryRecvError::Disconnected) =>\n                    return Err(H3ConnectionError::ControllerWentAway),\n            }\n\n            frame = self.dgram_recv.try_recv();\n        }\n\n        Ok(())\n    }\n\n    /// Return a mutable reference to the driver's HTTP/3 connection.\n    ///\n    /// If the connection doesn't exist yet, this function returns\n    /// a `Self::connection_not_present()` error.\n    fn conn_mut(&mut self) -> H3ConnectionResult<&mut h3::Connection> {\n        self.conn.as_mut().ok_or(Self::connection_not_present())\n    }\n\n    /// Alias for [`quiche::Error::TlsFail`], which is used in the case where\n    /// this driver doesn't have an established HTTP/3 connection attached\n    /// to it yet.\n    const fn connection_not_present() -> H3ConnectionError {\n        H3ConnectionError::H3(h3::Error::TransportError(quiche::Error::TlsFail))\n    }\n\n    /// Cleans up internal state for the indicated HTTP/3 stream.\n    ///\n    /// This function removes the stream from the stream map, closes any pending\n    /// futures, removes associated DATAGRAM flows, and sends a\n    /// [`H3Event::StreamClosed`] event (for servers).\n    fn cleanup_stream(\n        &mut self, qconn: &mut QuicheConnection, stream_id: u64,\n    ) -> H3ConnectionResult<()> {\n        let Some(stream_ctx) = self.stream_map.remove(&stream_id) else {\n            return Ok(());\n        };\n\n        // Find if the stream also has any pending futures associated with it\n        for pending in self.waiting_streams.iter_mut() {\n            match pending {\n                WaitForStream::Downstream(WaitForDownstreamData {\n                    stream_id: id,\n                    chan: Some(chan),\n                }) if stream_id == *id => {\n                    chan.close();\n                },\n                WaitForStream::Upstream(WaitForUpstreamCapacity {\n                    stream_id: id,\n                    chan: Some(chan),\n                }) if stream_id == *id => {\n                    chan.close();\n                },\n                _ => {},\n            }\n        }\n\n        // Close any DATAGRAM-proxying channels when we close the stream, if they\n        // exist\n        if let Some(mapped_flow_id) = stream_ctx.associated_dgram_flow_id {\n            self.flow_map.remove(&mapped_flow_id);\n        }\n\n        if qconn.is_server() {\n            // Signal the server to remove the stream from its map\n            let _ = self\n                .h3_event_sender\n                .send(H3Event::StreamClosed { stream_id }.into());\n        }\n\n        Ok(())\n    }\n\n    /// Shuts down the indicated HTTP/3 stream by sending frames and cleaning\n    /// up then cleans up internal state by calling\n    /// [`Self::cleanup_stream`].\n    fn shutdown_stream(\n        &mut self, qconn: &mut QuicheConnection, stream_id: u64,\n        shutdown: StreamShutdown,\n    ) -> H3ConnectionResult<()> {\n        let Some(stream_ctx) = self.stream_map.get(&stream_id) else {\n            return Ok(());\n        };\n\n        let audit_stats = &stream_ctx.audit_stats;\n\n        match shutdown {\n            StreamShutdown::Read { error_code } => {\n                audit_stats.set_sent_stop_sending_error_code(error_code as _);\n                let _ = qconn.stream_shutdown(\n                    stream_id,\n                    quiche::Shutdown::Read,\n                    error_code,\n                );\n            },\n            StreamShutdown::Write { error_code } => {\n                audit_stats.set_sent_reset_stream_error_code(error_code as _);\n                let _ = qconn.stream_shutdown(\n                    stream_id,\n                    quiche::Shutdown::Write,\n                    error_code,\n                );\n            },\n            StreamShutdown::Both {\n                read_error_code,\n                write_error_code,\n            } => {\n                audit_stats\n                    .set_sent_stop_sending_error_code(read_error_code as _);\n                let _ = qconn.stream_shutdown(\n                    stream_id,\n                    quiche::Shutdown::Read,\n                    read_error_code,\n                );\n                audit_stats\n                    .set_sent_reset_stream_error_code(write_error_code as _);\n                let _ = qconn.stream_shutdown(\n                    stream_id,\n                    quiche::Shutdown::Write,\n                    write_error_code,\n                );\n            },\n        }\n\n        self.cleanup_stream(qconn, stream_id)\n    }\n\n    /// Handles a regular [`H3Command`]. May be called internally by\n    /// [DriverHooks] for non-endpoint-specific [`H3Command`]s.\n    fn handle_core_command(\n        &mut self, qconn: &mut QuicheConnection, cmd: H3Command,\n    ) -> H3ConnectionResult<()> {\n        match cmd {\n            H3Command::QuicCmd(cmd) => cmd.execute(qconn),\n            H3Command::GoAway => {\n                let max_id = self.max_stream_seen;\n                self.conn_mut()\n                    .expect(\"connection should be established\")\n                    .send_goaway(qconn, max_id)?;\n            },\n            H3Command::ShutdownStream {\n                stream_id,\n                shutdown,\n            } => {\n                self.shutdown_stream(qconn, stream_id, shutdown)?;\n            },\n        }\n        Ok(())\n    }\n}\n\nimpl<H: DriverHooks> H3Driver<H> {\n    /// Reads all buffered datagrams out of `qconn` and distributes them to\n    /// their flow channels.\n    fn process_available_dgrams(\n        &mut self, qconn: &mut QuicheConnection,\n    ) -> H3ConnectionResult<()> {\n        loop {\n            match datagram::receive_h3_dgram(qconn) {\n                Ok((flow_id, dgram)) => {\n                    self.get_or_insert_flow(flow_id)?.send_best_effort(dgram);\n                },\n                Err(quiche::Error::Done) => return Ok(()),\n                Err(err) => return Err(H3ConnectionError::from(err)),\n            }\n        }\n    }\n\n    /// Flushes any queued-up frames for `stream_id` into `qconn` until either\n    /// there is no more capacity in `qconn` or no more frames to send.\n    fn process_writable_stream(\n        &mut self, qconn: &mut QuicheConnection, stream_id: u64,\n    ) -> H3ConnectionResult<()> {\n        // Split self borrow between conn and stream_map\n        let conn = self.conn.as_mut().ok_or(Self::connection_not_present())?;\n        let Some(ctx) = self.stream_map.get_mut(&stream_id) else {\n            return Ok(()); // Unknown stream_id\n        };\n\n        loop {\n            // Process each writable frame, queue the next frame for processing\n            // and shut down any errored streams.\n            match Self::process_write_frame(conn, qconn, ctx) {\n                Ok(()) => ctx.queued_frame = None,\n                Err(h3::Error::StreamBlocked | h3::Error::Done) => break,\n                Err(h3::Error::MessageError) => {\n                    return self.shutdown_stream(\n                        qconn,\n                        stream_id,\n                        StreamShutdown::Both {\n                            read_error_code: WireErrorCode::MessageError as u64,\n                            write_error_code: WireErrorCode::MessageError as u64,\n                        },\n                    );\n                },\n                Err(h3::Error::TransportError(quiche::Error::StreamStopped(\n                    e,\n                ))) => {\n                    ctx.handle_recvd_stop_sending(e);\n                    if ctx.both_directions_done() {\n                        return self.cleanup_stream(qconn, stream_id);\n                    } else {\n                        return Ok(());\n                    }\n                },\n                Err(h3::Error::TransportError(\n                    quiche::Error::InvalidStreamState(stream),\n                )) => {\n                    return self.cleanup_stream(qconn, stream);\n                },\n                Err(_) => {\n                    return self.cleanup_stream(qconn, stream_id);\n                },\n            }\n\n            let Some(recv) = ctx.recv.as_mut() else {\n                // This stream is already waiting for data or we wrote a fin and\n                // closed the channel.\n                debug_assert!(\n                    ctx.queued_frame.is_none(),\n                    \"We MUST NOT have a queued frame if we are already waiting on \n                    more data from the channel\"\n                );\n                return Ok(());\n            };\n\n            // Attempt to queue the next frame for processing. The corresponding\n            // sender is created at the same time as the `StreamCtx`\n            // and ultimately ends up in an `H3Body`. The body then\n            // determines which frames to send to the peer via\n            // this processing loop.\n            match recv.try_recv() {\n                Ok(frame) => ctx.queued_frame = Some(frame),\n                Err(TryRecvError::Disconnected) => {\n                    if !ctx.fin_or_reset_sent &&\n                        ctx.associated_dgram_flow_id.is_none()\n                    // The channel might be closed if the stream was used to\n                    // initiate a datagram exchange.\n                    // TODO: ideally, the application would still shut down the\n                    // stream properly. Once applications code\n                    // is fixed, we can remove this check.\n                    {\n                        // The channel closed without having written a fin. Send a\n                        // RESET_STREAM to indicate we won't be writing anything\n                        // else\n                        let err = h3::WireErrorCode::RequestCancelled as u64;\n                        let _ = qconn.stream_shutdown(\n                            stream_id,\n                            quiche::Shutdown::Write,\n                            err,\n                        );\n                        ctx.handle_sent_reset(err);\n                        if ctx.both_directions_done() {\n                            return self.cleanup_stream(qconn, stream_id);\n                        }\n                    }\n                    break;\n                },\n                Err(TryRecvError::Empty) => {\n                    self.waiting_streams.push(ctx.wait_for_recv(stream_id));\n                    break;\n                },\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Tests `qconn` for either a local or peer error and increments\n    /// the associated HTTP/3 or QUIC error counter.\n    fn record_quiche_error(qconn: &mut QuicheConnection, metrics: &impl Metrics) {\n        // split metrics between local/peer and QUIC/HTTP/3 level errors\n        if let Some(err) = qconn.local_error() {\n            if err.is_app {\n                metrics.local_h3_conn_close_error_count(err.error_code.into())\n            } else {\n                metrics.local_quic_conn_close_error_count(err.error_code.into())\n            }\n            .inc();\n        } else if let Some(err) = qconn.peer_error() {\n            if err.is_app {\n                metrics.peer_h3_conn_close_error_count(err.error_code.into())\n            } else {\n                metrics.peer_quic_conn_close_error_count(err.error_code.into())\n            }\n            .inc();\n        }\n    }\n}\n\nimpl<H: DriverHooks> ApplicationOverQuic for H3Driver<H> {\n    fn on_conn_established(\n        &mut self, quiche_conn: &mut QuicheConnection,\n        handshake_info: &HandshakeInfo,\n    ) -> QuicResult<()> {\n        let conn = h3::Connection::with_transport(quiche_conn, &self.h3_config)?;\n        self.conn = Some(conn);\n\n        H::conn_established(self, quiche_conn, handshake_info)?;\n        Ok(())\n    }\n\n    #[inline]\n    fn should_act(&self) -> bool {\n        self.conn.is_some()\n    }\n\n    #[inline]\n    fn buffer(&mut self) -> &mut [u8] {\n        &mut self.pooled_buf\n    }\n\n    /// Poll the underlying [`quiche::h3::Connection`] for\n    /// [`quiche::h3::Event`]s and DATAGRAMs, delegating processing to\n    /// `Self::process_read_event`.\n    ///\n    /// If a DATAGRAM is found, it is sent to the receiver on its channel.\n    fn process_reads(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()> {\n        loop {\n            match self.conn_mut()?.poll(qconn) {\n                Ok((stream_id, event)) =>\n                    self.process_read_event(qconn, stream_id, event)?,\n                Err(h3::Error::Done) => break,\n                Err(err) => {\n                    // Don't bubble error up, instead keep the worker loop going\n                    // until quiche reports the connection is\n                    // closed.\n                    log::debug!(\"connection closed due to h3 protocol error\"; \"error\"=>?err);\n                    return Ok(());\n                },\n            };\n        }\n\n        self.process_available_dgrams(qconn)?;\n        Ok(())\n    }\n\n    /// Write as much data as possible into the [`quiche::h3::Connection`] from\n    /// all sources. This will attempt to write any queued frames into their\n    /// respective streams, if writable.\n    fn process_writes(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()> {\n        while let Some(stream_id) = qconn.stream_writable_next() {\n            self.process_writable_stream(qconn, stream_id)?;\n        }\n\n        // Also optimistically check for any ready streams\n        while let Some(Some(ready)) = self.waiting_streams.next().now_or_never() {\n            self.upstream_ready(qconn, ready)?;\n        }\n\n        Ok(())\n    }\n\n    /// Reports connection-level error metrics and forwards\n    /// IOWorker errors to the associated [H3Controller].\n    fn on_conn_close<M: Metrics>(\n        &mut self, quiche_conn: &mut QuicheConnection, metrics: &M,\n        work_loop_result: &QuicResult<()>,\n    ) {\n        let max_stream_seen = self.max_stream_seen;\n        metrics\n            .maximum_writable_streams()\n            .observe(max_stream_seen as f64);\n\n        let Err(work_loop_error) = work_loop_result else {\n            return;\n        };\n\n        Self::record_quiche_error(quiche_conn, metrics);\n\n        let Some(h3_err) = work_loop_error.downcast_ref::<H3ConnectionError>()\n        else {\n            log::error!(\"Found non-H3ConnectionError\"; \"error\" => %work_loop_error);\n            return;\n        };\n\n        if matches!(h3_err, H3ConnectionError::ControllerWentAway) {\n            // Inform client that we won't (can't) respond anymore\n            let _ = quiche_conn.close(true, WireErrorCode::NoError as u64, &[]);\n            return;\n        }\n\n        if let Some(ev) = H3Event::from_error(h3_err) {\n            let _ = self.h3_event_sender.send(ev.into());\n            #[expect(clippy::needless_return)]\n            return; // avoid accidental fallthrough in the future\n        }\n    }\n\n    /// Wait for incoming data from the [H3Controller]. The next iteration of\n    /// the I/O loop commences when one of the `select!`ed futures triggers.\n    #[inline]\n    async fn wait_for_data(\n        &mut self, qconn: &mut QuicheConnection,\n    ) -> QuicResult<()> {\n        select! {\n            biased;\n            Some(ready) = self.waiting_streams.next() => self.upstream_ready(qconn, ready),\n            Some(dgram) = self.dgram_recv.recv() => self.dgram_ready(qconn, dgram),\n            Some(cmd) = self.cmd_recv.recv() => H::conn_command(self, qconn, cmd),\n            r = self.hooks.wait_for_action(qconn), if H::has_wait_action(self) => r,\n        }?;\n\n        // Make sure controller is not starved, but also not prioritized in the\n        // biased select. So poll it last, however also perform a try_recv\n        // each iteration.\n        if let Ok(cmd) = self.cmd_recv.try_recv() {\n            H::conn_command(self, qconn, cmd)?;\n        }\n\n        Ok(())\n    }\n}\n\nimpl<H: DriverHooks> Drop for H3Driver<H> {\n    fn drop(&mut self) {\n        for stream in self.stream_map.values() {\n            stream\n                .audit_stats\n                .set_recvd_stream_fin(StreamClosureKind::Implicit);\n        }\n    }\n}\n\n/// [`H3Command`]s are sent by the [H3Controller] to alter the [H3Driver]'s\n/// state.\n///\n/// Both [ServerH3Driver] and [ClientH3Driver] may extend this enum with\n/// endpoint-specific variants.\n#[derive(Debug)]\npub enum H3Command {\n    /// A connection-level command that executes directly on the\n    /// [`quiche::Connection`].\n    QuicCmd(QuicCommand),\n    /// Send a GOAWAY frame to the peer to initiate a graceful connection\n    /// shutdown.\n    GoAway,\n    /// Shuts down a stream in the specified direction(s) and removes it from\n    /// local state.\n    ///\n    /// This removes the stream from local state and sends a `RESET_STREAM`\n    /// frame (for write direction) and/or a `STOP_SENDING` frame (for read\n    /// direction) to the peer. See [`quiche::Connection::stream_shutdown`]\n    /// for details.\n    ShutdownStream {\n        stream_id: u64,\n        shutdown: StreamShutdown,\n    },\n}\n\n/// Specifies which direction(s) of a stream to shut down.\n///\n/// Used with [`H3Controller::shutdown_stream`] and the internal\n/// `shutdown_stream` function to control whether to send a `STOP_SENDING` frame\n/// (read direction), and/or a `RESET_STREAM` frame (write direction)\n///\n/// Note: Despite its name, \"shutdown\" here refers to signaling the peer about\n/// stream termination, not sending a FIN flag. `STOP_SENDING` asks the peer to\n/// stop sending data, while `RESET_STREAM` abruptly terminates the write side.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum StreamShutdown {\n    /// Shut down only the read direction (sends `STOP_SENDING` frame with the\n    /// given error code).\n    Read { error_code: u64 },\n    /// Shut down only the write direction (sends `RESET_STREAM` frame with the\n    /// given error code).\n    Write { error_code: u64 },\n    /// Shut down both directions (sends both `STOP_SENDING` and `RESET_STREAM`\n    /// frames).\n    Both {\n        read_error_code: u64,\n        write_error_code: u64,\n    },\n}\n\n/// Sends [`H3Command`]s to an [H3Driver]. The sender is typed and internally\n/// wraps instances of `T` in the appropriate `H3Command` variant.\npub struct RequestSender<C, T> {\n    sender: UnboundedSender<C>,\n    // Required to work around dangling type parameter\n    _r: PhantomData<fn() -> T>,\n}\n\nimpl<C, T: Into<C>> RequestSender<C, T> {\n    /// Send a request to the [H3Driver]. This can only fail if the driver is\n    /// gone.\n    #[inline(always)]\n    pub fn send(&self, v: T) -> Result<(), mpsc::error::SendError<C>> {\n        self.sender.send(v.into())\n    }\n}\n\nimpl<C, T> Clone for RequestSender<C, T> {\n    fn clone(&self) -> Self {\n        Self {\n            sender: self.sender.clone(),\n            _r: Default::default(),\n        }\n    }\n}\n\n/// Interface to communicate with a paired [H3Driver].\n///\n/// An [H3Controller] receives [`H3Event`]s from its driver, which must be\n/// consumed by the application built on top of the driver to react to incoming\n/// events. The controller also allows the application to send ad-hoc\n/// [`H3Command`]s to the driver, which will be processed when the driver waits\n/// for incoming data.\npub struct H3Controller<H: DriverHooks> {\n    /// Sends [`H3Command`]s to the [H3Driver], like [`QuicCommand`]s or\n    /// outbound HTTP requests.\n    cmd_sender: UnboundedSender<H::Command>,\n    /// Receives [`H3Event`]s from the [H3Driver]. Can be extracted and\n    /// used independently of the [H3Controller].\n    h3_event_recv: Option<UnboundedReceiver<H::Event>>,\n}\n\nimpl<H: DriverHooks> H3Controller<H> {\n    /// Gets a mut reference to the [`H3Event`] receiver for the paired\n    /// [H3Driver].\n    pub fn event_receiver_mut(&mut self) -> &mut UnboundedReceiver<H::Event> {\n        self.h3_event_recv\n            .as_mut()\n            .expect(\"No event receiver on H3Controller\")\n    }\n\n    /// Takes the [`H3Event`] receiver for the paired [H3Driver].\n    pub fn take_event_receiver(&mut self) -> UnboundedReceiver<H::Event> {\n        self.h3_event_recv\n            .take()\n            .expect(\"No event receiver on H3Controller\")\n    }\n\n    /// Creates a [`QuicCommand`] sender for the paired [H3Driver].\n    pub fn cmd_sender(&self) -> RequestSender<H::Command, QuicCommand> {\n        RequestSender {\n            sender: self.cmd_sender.clone(),\n            _r: Default::default(),\n        }\n    }\n\n    /// Sends a GOAWAY frame to initiate a graceful connection shutdown.\n    pub fn send_goaway(&self) {\n        let _ = self.cmd_sender.send(H3Command::GoAway.into());\n    }\n\n    /// Creates an [`H3Command`] sender for the paired [H3Driver].\n    pub fn h3_cmd_sender(&self) -> RequestSender<H::Command, H3Command> {\n        RequestSender {\n            sender: self.cmd_sender.clone(),\n            _r: Default::default(),\n        }\n    }\n\n    /// Shuts down a stream in the specified direction(s) and removes it from\n    /// local state.\n    ///\n    /// This removes the stream from local state and sends a `RESET_STREAM`\n    /// frame (for write direction) and/or a `STOP_SENDING` frame (for read\n    /// direction) to the peer, depending on the [`StreamShutdown`] variant.\n    pub fn shutdown_stream(&self, stream_id: u64, shutdown: StreamShutdown) {\n        let _ = self.cmd_sender.send(\n            H3Command::ShutdownStream {\n                stream_id,\n                shutdown,\n            }\n            .into(),\n        );\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/server.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::ops::Deref;\nuse std::sync::Arc;\n\nuse tokio::sync::mpsc;\n\nuse super::datagram;\nuse super::DriverHooks;\nuse super::H3Command;\nuse super::H3ConnectionError;\nuse super::H3ConnectionResult;\nuse super::H3Controller;\nuse super::H3Driver;\nuse super::H3Event;\nuse super::InboundHeaders;\nuse super::IncomingH3Headers;\nuse super::StreamCtx;\nuse super::STREAM_CAPACITY;\nuse crate::http3::settings::Http3Settings;\nuse crate::http3::settings::Http3SettingsEnforcer;\nuse crate::http3::settings::Http3TimeoutType;\nuse crate::http3::settings::TimeoutKey;\nuse crate::quic::HandshakeInfo;\nuse crate::quic::QuicCommand;\nuse crate::quic::QuicheConnection;\n\n/// An [H3Driver] for a server-side HTTP/3 connection. See [H3Driver] for\n/// details. Emits [`ServerH3Event`]s and expects [`ServerH3Command`]s for\n/// control.\npub type ServerH3Driver = H3Driver<ServerHooks>;\n/// The [H3Controller] type paired with [ServerH3Driver]. See [H3Controller] for\n/// details.\npub type ServerH3Controller = H3Controller<ServerHooks>;\n/// Receives [`ServerH3Event`]s from a [ServerH3Driver]. This is the control\n/// stream which describes what is happening on the connection, but does not\n/// transfer data.\npub type ServerEventStream = mpsc::UnboundedReceiver<ServerH3Event>;\n\n#[derive(Clone, Debug)]\npub struct RawPriorityValue(Vec<u8>);\n\nimpl From<Vec<u8>> for RawPriorityValue {\n    fn from(value: Vec<u8>) -> Self {\n        RawPriorityValue(value)\n    }\n}\n\nimpl Deref for RawPriorityValue {\n    type Target = [u8];\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\n/// The request was received during early data (0-RTT).\n#[derive(Clone, Debug)]\npub struct IsInEarlyData(bool);\n\nimpl IsInEarlyData {\n    fn new(is_in_early_data: bool) -> Self {\n        IsInEarlyData(is_in_early_data)\n    }\n}\n\nimpl Deref for IsInEarlyData {\n    type Target = bool;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\n/// Events produced by [ServerH3Driver].\n#[derive(Debug)]\npub enum ServerH3Event {\n    Core(H3Event),\n\n    Headers {\n        incoming_headers: IncomingH3Headers,\n        /// The latest PRIORITY_UPDATE frame value, if any.\n        priority: Option<RawPriorityValue>,\n        is_in_early_data: IsInEarlyData,\n    },\n}\n\nimpl From<H3Event> for ServerH3Event {\n    fn from(ev: H3Event) -> Self {\n        match ev {\n            H3Event::IncomingHeaders(incoming_headers) => {\n                // Server `incoming_headers` are exclusively created in\n                // `ServerHooks::handle_request`, which correctly serializes the\n                // RawPriorityValue and IsInEarlyData values.\n                //\n                // See `H3Driver::process_read_event` for implementation details.\n                Self::Headers {\n                    incoming_headers,\n                    priority: None,\n                    is_in_early_data: IsInEarlyData::new(false),\n                }\n            },\n            _ => Self::Core(ev),\n        }\n    }\n}\n\n/// Commands accepted by [ServerH3Driver].\n#[derive(Debug)]\npub enum ServerH3Command {\n    Core(H3Command),\n}\n\nimpl From<H3Command> for ServerH3Command {\n    fn from(cmd: H3Command) -> Self {\n        Self::Core(cmd)\n    }\n}\n\nimpl From<QuicCommand> for ServerH3Command {\n    fn from(cmd: QuicCommand) -> Self {\n        Self::Core(H3Command::QuicCmd(cmd))\n    }\n}\n\n// Quiche urgency is an 8-bit space. Internally, quiche reserves 0 for HTTP/3\n// control streams and request are shifted up by 124. Any value in that range is\n// suitable here.\nconst PRE_HEADERS_BOOSTED_PRIORITY_URGENCY: u8 = 64;\n// Non-incremental streams are served in stream ID order, matching the client\n// FIFO expectation.\nconst PRE_HEADERS_BOOSTED_PRIORITY_INCREMENTAL: bool = false;\n\npub struct ServerHooks {\n    /// Helper to enforce limits and timeouts on an HTTP/3 connection.\n    settings_enforcer: Http3SettingsEnforcer,\n    /// Tracks the number of requests that have been handled by this driver.\n    requests: u64,\n\n    /// Handle to the post-accept timeout entry. If present, the server must\n    /// receive a HEADERS frame before this timeout.\n    post_accept_timeout: Option<TimeoutKey>,\n}\n\nimpl ServerHooks {\n    /// Handles a new request, creating a stream context, checking for a\n    /// potential DATAGRAM flow (CONNECT-{UDP,IP}) and sending a relevant\n    /// [`H3Event`] to the [ServerH3Controller] for application-level\n    /// processing.\n    fn handle_request(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        headers: InboundHeaders,\n    ) -> H3ConnectionResult<()> {\n        let InboundHeaders {\n            stream_id,\n            headers,\n            has_body,\n        } = headers;\n\n        // Multiple HEADERS frames can be received on a single stream, but only\n        // the first one is an actual request. For now ignore any additional\n        // HEADERS (e.g. \"trailers\").\n        if driver.stream_map.contains_key(&stream_id) {\n            return Ok(());\n        }\n\n        let (mut stream_ctx, send, recv) =\n            StreamCtx::new(stream_id, STREAM_CAPACITY);\n\n        if let Some(flow_id) = datagram::extract_flow_id(stream_id, &headers) {\n            let _ = driver.get_or_insert_flow(flow_id)?;\n            stream_ctx.associated_dgram_flow_id = Some(flow_id);\n        }\n\n        let latest_priority_update: Option<RawPriorityValue> = driver\n            .conn_mut()?\n            .take_last_priority_update(stream_id)\n            .ok()\n            .map(|v| v.into());\n\n        // Boost the priority of the stream until we write response headers via\n        // process_write_frame(), which will set the desired priority. Since it\n        // will get set later, just swallow any error here.\n        qconn\n            .stream_priority(\n                stream_id,\n                PRE_HEADERS_BOOSTED_PRIORITY_URGENCY,\n                PRE_HEADERS_BOOSTED_PRIORITY_INCREMENTAL,\n            )\n            .ok();\n\n        let headers = IncomingH3Headers {\n            stream_id,\n            headers,\n            send,\n            recv,\n            read_fin: !has_body,\n            h3_audit_stats: Arc::clone(&stream_ctx.audit_stats),\n        };\n\n        driver\n            .waiting_streams\n            .push(stream_ctx.wait_for_recv(stream_id));\n        driver.insert_stream(stream_id, stream_ctx);\n\n        driver\n            .h3_event_sender\n            .send(ServerH3Event::Headers {\n                incoming_headers: headers,\n                priority: latest_priority_update,\n                is_in_early_data: IsInEarlyData::new(qconn.is_in_early_data()),\n            })\n            .map_err(|_| H3ConnectionError::ControllerWentAway)?;\n        driver.hooks.requests += 1;\n\n        Ok(())\n    }\n}\n\n#[allow(private_interfaces)]\nimpl DriverHooks for ServerHooks {\n    type Command = ServerH3Command;\n    type Event = ServerH3Event;\n\n    fn new(settings: &Http3Settings) -> Self {\n        Self {\n            settings_enforcer: settings.into(),\n            requests: 0,\n            post_accept_timeout: None,\n        }\n    }\n\n    fn conn_established(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        handshake_info: &HandshakeInfo,\n    ) -> H3ConnectionResult<()> {\n        assert!(\n            qconn.is_server(),\n            \"ServerH3Driver requires a server-side QUIC connection\"\n        );\n\n        if let Some(post_accept_timeout) =\n            driver.hooks.settings_enforcer.post_accept_timeout()\n        {\n            let remaining = post_accept_timeout\n                .checked_sub(handshake_info.elapsed())\n                .ok_or(H3ConnectionError::PostAcceptTimeout)?;\n\n            let key = driver\n                .hooks\n                .settings_enforcer\n                .add_timeout(Http3TimeoutType::PostAccept, remaining);\n            driver.hooks.post_accept_timeout = Some(key);\n        }\n\n        Ok(())\n    }\n\n    fn headers_received(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        headers: InboundHeaders,\n    ) -> H3ConnectionResult<()> {\n        if driver\n            .hooks\n            .settings_enforcer\n            .enforce_requests_limit(driver.hooks.requests)\n        {\n            let _ =\n                qconn.close(true, quiche::h3::WireErrorCode::NoError as u64, &[]);\n            return Ok(());\n        }\n\n        if let Some(timeout) = driver.hooks.post_accept_timeout.take() {\n            // We've seen the first Headers event for the connection,\n            // so we can abort the post-accept timeout\n            driver.hooks.settings_enforcer.cancel_timeout(timeout);\n        }\n\n        Self::handle_request(driver, qconn, headers)\n    }\n\n    fn conn_command(\n        driver: &mut H3Driver<Self>, qconn: &mut QuicheConnection,\n        cmd: Self::Command,\n    ) -> H3ConnectionResult<()> {\n        let ServerH3Command::Core(cmd) = cmd;\n        driver.handle_core_command(qconn, cmd)\n    }\n\n    fn has_wait_action(driver: &mut H3Driver<Self>) -> bool {\n        driver.hooks.settings_enforcer.has_pending_timeouts()\n    }\n\n    async fn wait_for_action(\n        &mut self, qconn: &mut QuicheConnection,\n    ) -> H3ConnectionResult<()> {\n        self.settings_enforcer.enforce_timeouts(qconn).await?;\n        Err(H3ConnectionError::PostAcceptTimeout)\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/streams.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::sync::Arc;\nuse std::task::Context;\nuse std::task::Poll;\nuse std::time::Instant;\n\nuse tokio::sync::mpsc;\nuse tokio_util::sync::PollSender;\n\nuse super::InboundFrame;\nuse super::InboundFrameSender;\nuse super::InboundFrameStream;\nuse super::OutboundFrame;\nuse super::OutboundFrameSender;\nuse super::OutboundFrameStream;\nuse crate::http3::H3AuditStats;\n\npub(crate) struct StreamCtx {\n    /// Sends [`InboundFrame`]s to a local task, for example an `H3Body`.\n    pub(crate) send: Option<InboundFrameSender>,\n    /// Receives [`OutboundFrame`]s from a local task.\n    pub(crate) recv: Option<OutboundFrameStream>,\n    /// Stores the next [`OutboundFrame`] to write to the connection.\n    /// This is used as temporary storage when waiting for `recv`.\n    pub(crate) queued_frame: Option<OutboundFrame>,\n    pub(crate) audit_stats: Arc<H3AuditStats>,\n    /// Indicates the stream sent initial headers.\n    pub(crate) initial_headers_sent: bool,\n    /// First time that a HEADERS frame was not fully flushed.\n    pub(crate) first_full_headers_flush_fail_time: Option<Instant>,\n    /// Indicates the stream received fin or reset. No more data will be\n    /// received.\n    pub(crate) fin_or_reset_recv: bool,\n    /// Indicates the stream sent fin or reset. No more data will be sent.\n    pub(crate) fin_or_reset_sent: bool,\n    /// The flow ID for proxying datagrams over this stream. If `None`,\n    /// the stream has no associated DATAGRAM flow.\n    pub(crate) associated_dgram_flow_id: Option<u64>,\n}\n\nimpl StreamCtx {\n    /// Creates a new [StreamCtx]. This method returns the [StreamCtx] itself\n    /// as well as the sender/receiver that it communicates with.\n    pub(crate) fn new(\n        stream_id: u64, capacity: usize,\n    ) -> (Self, OutboundFrameSender, InboundFrameStream) {\n        let (forward_sender, forward_receiver) = mpsc::channel(capacity);\n        let (backward_sender, backward_receiver) = mpsc::channel(capacity);\n\n        let ctx = StreamCtx {\n            send: Some(PollSender::new(forward_sender)),\n            recv: Some(backward_receiver),\n            queued_frame: None,\n            audit_stats: Arc::new(H3AuditStats::new(stream_id)),\n\n            initial_headers_sent: false,\n            first_full_headers_flush_fail_time: None,\n\n            fin_or_reset_recv: false,\n            fin_or_reset_sent: false,\n\n            associated_dgram_flow_id: None,\n        };\n\n        (ctx, PollSender::new(backward_sender), forward_receiver)\n    }\n\n    /// Creates a [Future] that resolves when `send` has capacity again.\n    pub(crate) fn wait_for_send(&mut self, stream_id: u64) -> WaitForStream {\n        WaitForStream::Upstream(WaitForUpstreamCapacity {\n            stream_id,\n            chan: self.send.take(),\n        })\n    }\n\n    /// Creates a [Future] that resolves when `recv` has data again.\n    pub(crate) fn wait_for_recv(&mut self, stream_id: u64) -> WaitForStream {\n        WaitForStream::Downstream(WaitForDownstreamData {\n            stream_id,\n            chan: self.recv.take(),\n        })\n    }\n\n    /// Handle the case when we received a STOP_SENDING frame. Note, that\n    /// we'll only learn about a STOP_SENDING frame from the write path.\n    /// Also note, that quiche will automatically send a RESET_STREAM frame\n    /// in response when it receives a STOP_SENDING (as recommended by the\n    /// RFC)\n    pub(crate) fn handle_recvd_stop_sending(&mut self, wire_err_code: u64) {\n        debug_assert!(!self.fin_or_reset_sent);\n        // We received a STOP_SENDING frame. This indicates that the\n        // write direction has closed. We still need to continue\n        // reading from the stream until we receive a RESET_FRAME or\n        // `fin`.\n        self.audit_stats\n            .set_recvd_stop_sending_error_code(wire_err_code as i64);\n        self.fin_or_reset_sent = true;\n        // Drop any pending data and close the write side.\n        // We can't accept additional frames\n        self.queued_frame = None;\n        debug_assert!(self.recv.is_some());\n        self.recv = None;\n    }\n\n    pub(crate) fn handle_recvd_reset(&mut self, wire_err_code: u64) {\n        debug_assert!(!self.fin_or_reset_recv);\n        // We received a RESET_STREAM frame, which closes the read direction\n        // but not the write direction. If the peer wants to shut down write,\n        // it must also send STOP_SENDING\n        self.audit_stats\n            .set_recvd_reset_stream_error_code(wire_err_code as i64);\n        self.fin_or_reset_recv = true;\n        self.send = None;\n    }\n\n    pub(crate) fn handle_sent_reset(&mut self, wire_err_code: u64) {\n        debug_assert!(!self.fin_or_reset_sent);\n        self.audit_stats\n            .set_sent_reset_stream_error_code(wire_err_code as i64);\n        self.fin_or_reset_sent = true;\n    }\n\n    pub(crate) fn handle_sent_stop_sending(&mut self, wire_err_code: u64) {\n        debug_assert!(!self.fin_or_reset_recv);\n        self.audit_stats\n            .set_sent_stop_sending_error_code(wire_err_code as i64);\n        // It is ok for us to set the `fin_reset_recv` flag here.  While the peer\n        // must still send a fin or reset_stream with its final size, we don't\n        // need to read it from the stream. Quiche will take care of that.\n        self.fin_or_reset_recv = true;\n        self.send = None;\n    }\n\n    pub(crate) fn both_directions_done(&self) -> bool {\n        self.fin_or_reset_recv && self.fin_or_reset_sent\n    }\n}\n\npub(crate) struct FlowCtx {\n    /// Sends inbound datagrams to a local task.\n    send: mpsc::Sender<InboundFrame>,\n    // No `recv`: all outbound datagrams are sent on a shared channel in H3Driver\n}\n\nimpl FlowCtx {\n    /// Creates a new [FlowCtx]. This method returns the context itself\n    /// as well as the datagram receiver for this flow.\n    pub(crate) fn new(capacity: usize) -> (Self, InboundFrameStream) {\n        let (forward_sender, forward_receiver) = mpsc::channel(capacity);\n        let ctx = FlowCtx {\n            send: forward_sender,\n        };\n        (ctx, forward_receiver)\n    }\n\n    /// Tries to send a datagram to the flow receiver, but drops it if the\n    /// channel is full.\n    pub(crate) fn send_best_effort(&self, datagram: InboundFrame) {\n        let _ = self.send.try_send(datagram);\n    }\n}\n\npub(crate) enum WaitForStream {\n    Downstream(WaitForDownstreamData),\n    Upstream(WaitForUpstreamCapacity),\n}\n\npub(crate) enum StreamReady {\n    Downstream(ReceivedDownstreamData),\n    Upstream(HaveUpstreamCapacity),\n}\n\nimpl Future for WaitForStream {\n    type Output = StreamReady;\n\n    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n        match self.get_mut() {\n            WaitForStream::Downstream(d) =>\n                Pin::new(d).poll(cx).map(StreamReady::Downstream),\n            WaitForStream::Upstream(u) =>\n                Pin::new(u).poll(cx).map(StreamReady::Upstream),\n        }\n    }\n}\n\npub(crate) struct WaitForDownstreamData {\n    pub(crate) stream_id: u64,\n    pub(crate) chan: Option<OutboundFrameStream>,\n}\n\npub(crate) struct ReceivedDownstreamData {\n    pub(crate) stream_id: u64,\n    pub(crate) chan: OutboundFrameStream,\n    pub(crate) data: Option<OutboundFrame>,\n}\n\nimpl Future for WaitForDownstreamData {\n    type Output = ReceivedDownstreamData;\n\n    fn poll(\n        mut self: Pin<&mut Self>, cx: &mut Context<'_>,\n    ) -> Poll<Self::Output> {\n        // Unwraps below are Ok because chan will only be None after first\n        // Poll::Ready, which is fine to panic for non fused future.\n        self.chan.as_mut().unwrap().poll_recv(cx).map(|data| {\n            ReceivedDownstreamData {\n                stream_id: self.stream_id,\n                chan: self.chan.take().unwrap(),\n                data,\n            }\n        })\n    }\n}\n\npub(crate) struct WaitForUpstreamCapacity {\n    pub(crate) stream_id: u64,\n    pub(crate) chan: Option<InboundFrameSender>,\n}\n\npub(crate) struct HaveUpstreamCapacity {\n    pub(crate) stream_id: u64,\n    pub(crate) chan: InboundFrameSender,\n}\n\nimpl Future for WaitForUpstreamCapacity {\n    type Output = HaveUpstreamCapacity;\n\n    fn poll(\n        mut self: Pin<&mut Self>, cx: &mut Context<'_>,\n    ) -> Poll<Self::Output> {\n        // Unwraps below are Ok because chan will only be None after first\n        // Poll::Ready, which is fine to panic for non fused future.\n        match self.chan.as_mut().unwrap().poll_reserve(cx) {\n            Poll::Ready(_) => Poll::Ready(HaveUpstreamCapacity {\n                stream_id: self.stream_id,\n                chan: self.chan.take().unwrap(),\n            }),\n            Poll::Pending => Poll::Pending,\n        }\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/test_utils.rs",
    "content": "use std::time::Instant;\n\nuse anyhow::Context;\nuse futures::FutureExt as _;\nuse quiche::h3;\nuse quiche::h3::Header;\nuse tokio::sync::mpsc::error::TryRecvError;\nuse tokio::sync::oneshot;\n\nuse crate::http3::driver::client::ClientHooks;\nuse crate::http3::driver::hooks::DriverHooks;\nuse crate::http3::driver::server::ServerHooks;\nuse crate::http3::driver::ClientH3Event;\nuse crate::http3::driver::H3Controller;\nuse crate::http3::driver::H3Driver;\nuse crate::http3::driver::H3Event;\nuse crate::http3::driver::InboundFrame;\nuse crate::http3::driver::InboundFrameStream;\nuse crate::http3::driver::NewClientRequest;\nuse crate::http3::driver::OutboundFrameSender;\nuse crate::http3::driver::ServerH3Event;\nuse crate::http3::settings::Http3Settings;\nuse crate::quic::HandshakeInfo;\nuse crate::ApplicationOverQuic as _;\nuse quiche::test_utils::Pipe;\n\npub fn default_quiche_config() -> quiche::Config {\n    let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n    config\n        .load_cert_chain_from_pem_file(\"examples/cert.crt\")\n        .unwrap();\n    config\n        .load_priv_key_from_pem_file(\"examples/cert.key\")\n        .unwrap();\n    config.set_application_protos(&[b\"h3\"]).unwrap();\n    config.set_initial_max_data(1500);\n    config.set_initial_max_stream_data_bidi_local(150);\n    config.set_initial_max_stream_data_bidi_remote(150);\n    config.set_initial_max_stream_data_uni(150);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(5);\n    config.verify_peer(false);\n    config\n}\n\npub fn make_request_headers(method: &str) -> Vec<Header> {\n    vec![\n        Header::new(b\":method\", method.as_bytes()),\n        Header::new(b\":scheme\", b\"https\"),\n        Header::new(b\":authority\", b\"quic.tech\"),\n        Header::new(b\":path\", b\"/test\"),\n    ]\n}\n\npub fn make_response_headers() -> Vec<Header> {\n    vec![\n        Header::new(b\":status\", b\"200\"),\n        Header::new(b\"server\", b\"quiche-test\"),\n    ]\n}\n\npub fn make_response_trailers() -> Vec<Header> {\n    vec![Header::new(b\"trailers\", b\"1\")]\n}\n\n/// Helper trait to get the either right `quiche::Connection` for\n/// ourselvers (to use with the `H3Driver`) or our peer (to use\n/// with `quiche::H3::Connection`\npub trait GetConnectionForHook {\n    fn qconn(pipe: &mut Pipe) -> &mut quiche::Connection;\n    fn peer_qconn(pipe: &mut Pipe) -> &mut quiche::Connection;\n}\n\nimpl GetConnectionForHook for ClientHooks {\n    fn qconn(pipe: &mut Pipe) -> &mut quiche::Connection {\n        &mut pipe.client\n    }\n\n    fn peer_qconn(pipe: &mut Pipe) -> &mut quiche::Connection {\n        &mut pipe.server\n    }\n}\n\nimpl GetConnectionForHook for ServerHooks {\n    fn qconn(pipe: &mut Pipe) -> &mut quiche::Connection {\n        &mut pipe.server\n    }\n\n    fn peer_qconn(pipe: &mut Pipe) -> &mut quiche::Connection {\n        &mut pipe.client\n    }\n}\n\n/// Similar to `quiche::test_utils::Pipe`, a wrapper with helper functions\n/// for a client and server endpoint. One endpoint is driven by an H3Driver\n/// to allow testing the H3Driver logic. The other endpoint (the peer) is\n/// driven directly by an `quiche::h3::Connection`.\npub struct DriverTestHelper<H: DriverHooks + GetConnectionForHook> {\n    pub pipe: quiche::test_utils::Pipe,\n    pub driver: H3Driver<H>,\n    pub controller: H3Controller<H>,\n    /// Our peer, not using a driver, just the h3::Connection directly\n    pub peer: h3::Connection,\n}\n\nimpl<H: DriverHooks + GetConnectionForHook> DriverTestHelper<H> {\n    pub fn new() -> anyhow::Result<Self> {\n        Self::with_pipe_and_http3_settings(\n            Pipe::with_config(&mut default_quiche_config())?,\n            Http3Settings::default(),\n        )\n    }\n\n    pub fn with_pipe(pipe: Pipe) -> anyhow::Result<Self> {\n        Self::with_pipe_and_http3_settings(pipe, Http3Settings::default())\n    }\n\n    pub fn with_pipe_and_http3_settings(\n        mut pipe: Pipe, h3_settings: Http3Settings,\n    ) -> anyhow::Result<Self> {\n        pipe.handshake().context(\"Failed to handshake pipe\")?;\n        let (driver, controller) = H3Driver::<H>::new(h3_settings);\n        let peer = h3::Connection::with_transport(\n            H::peer_qconn(&mut pipe),\n            &h3::Config::new().unwrap(),\n        )\n        .context(\"create H3 peer connection\")?;\n        Ok(Self {\n            pipe,\n            driver,\n            controller,\n            peer,\n        })\n    }\n\n    /// Advance the pipe and run work_loop_iterations.\n    /// TODO: We just run a couple of times to \"make sure\" all pending work has\n    /// been processed. Ideally, we'd have some feedback from `work_loop_iter()`\n    /// to decide if need to run `advance()` / `work_loop_iter()` instead of\n    /// blindly calling it a couple of time...\n    pub fn advance_and_run_loop(&mut self) -> anyhow::Result<()> {\n        self.pipe.advance()?;\n        self.work_loop_iter()?;\n        self.pipe.advance()?;\n        self.work_loop_iter()?;\n        self.pipe.advance()?;\n        self.work_loop_iter()?;\n        self.pipe.advance()?;\n        Ok(())\n    }\n\n    /// call `on_conn_established()` on the driver and advance to pipe to\n    /// complete the H3 handshake\n    pub fn complete_handshake(&mut self) -> anyhow::Result<()> {\n        self.driver\n            .on_conn_established(\n                H::qconn(&mut self.pipe),\n                &HandshakeInfo::new(Instant::now(), None),\n            )\n            .map_err(anyhow::Error::from_boxed)\n            .context(\"on_conn_established\")?;\n        // advance pipe to complete H3 handshake\n        self.pipe.advance().context(\"advance pipe\")?;\n        self.driver.settings_received_and_forwarded = true;\n        Ok(())\n    }\n\n    /// call `forward_settings()` on the driver. This will enqueue an\n    /// IncomingSettings event for the controller\n    /// deal with IncomingSettings events\n    pub fn forward_settings(&mut self) -> anyhow::Result<()> {\n        Ok(self.driver.forward_settings()?)\n    }\n\n    /// Run one iteration of the work loop *without* advancing the pipe\n    pub fn work_loop_iter(&mut self) -> anyhow::Result<()> {\n        let qconn = H::qconn(&mut self.pipe);\n        self.driver\n            .process_reads(qconn)\n            .map_err(anyhow::Error::from_boxed)\n            .context(\"process_reads\")?;\n        self.driver\n            .process_writes(qconn)\n            .map_err(anyhow::Error::from_boxed)\n            .context(\"process_writes\")?;\n        tokio::task::unconstrained(self.driver.wait_for_data(qconn))\n            .now_or_never()\n            .unwrap_or(Ok(()))\n            .map_err(anyhow::Error::from_boxed)\n            .context(\"wait_for_data\")?;\n        self.forward_settings()?;\n\n        Ok(())\n    }\n\n    /// process any commands the driver might have received, returns\n    /// the nubmer of commands processed\n    /// Note that commands will also be processed by\n    /// [`Self::work_loop_iter()`], but this function allows one to\n    /// only process the events.\n    pub fn process_commands(&mut self) -> anyhow::Result<u64> {\n        let mut iter = 0;\n        while let Ok(cmd) = self.driver.cmd_recv.try_recv() {\n            H::conn_command(&mut self.driver, H::qconn(&mut self.pipe), cmd)\n                .with_context(|| format!(\"H::conn_command iter={}\", iter))?;\n            iter += 1;\n        }\n        Ok(iter)\n    }\n\n    /// Call [`h3::Connection::poll()`] on the peer\n    fn poll_peer(&mut self) -> h3::Result<(u64, h3::Event)> {\n        self.peer.poll(H::peer_qconn(&mut self.pipe))\n    }\n\n    /// Send a body from the peer\n    fn peer_send_body(\n        &mut self, stream_id: u64, body: &[u8], fin: bool,\n    ) -> h3::Result<usize> {\n        self.peer\n            .send_body(H::peer_qconn(&mut self.pipe), stream_id, body, fin)\n    }\n\n    /// Call `h3::Connection::recv_body()` on the peer and read at most\n    /// `max_read` bytes into a Vector, returns the Vec.\n    fn peer_recv_body_vec(\n        &mut self, stream_id: u64, max_read: usize,\n    ) -> h3::Result<Vec<u8>> {\n        let mut buf = vec![0; max_read];\n\n        let written = self.peer.recv_body(\n            H::peer_qconn(&mut self.pipe),\n            stream_id,\n            &mut buf,\n        )?;\n        buf.truncate(written);\n        Ok(buf)\n    }\n\n    // Repeately calls try_recv() on the given receiver and work_loop_iter()\n    // until the receiver returns an empty or disconnected.\n    // Merges all received parts into a single Vec.\n    pub fn driver_try_recv_body(\n        &mut self, recv: &mut InboundFrameStream,\n    ) -> (Vec<u8>, bool, TryRecvError) {\n        let mut buf = Vec::new();\n        let mut had_fin = false;\n        loop {\n            match recv.try_recv() {\n                Ok(InboundFrame::Body(pooled, fin)) => {\n                    if had_fin {\n                        panic!(\"Received data after fin\");\n                    }\n                    buf.extend_from_slice(&pooled);\n                    had_fin = fin;\n                },\n                Ok(InboundFrame::Datagram(..)) => {\n                    panic!(\"Unexepected InboundFrame::Datagram\");\n                },\n                Err(err) => return (buf, had_fin, err),\n            }\n            self.work_loop_iter().unwrap();\n        }\n    }\n}\n\nimpl DriverTestHelper<ClientHooks> {\n    /// Sends a new client request, by enqueuing a `NewClientRequest`\n    /// command, processing it, and receiving the `NewOutboundRequest` from\n    /// the controllers. It returns `stream_id`.\n    ///\n    /// This function assumes that there are no commands or events queued\n    /// when called.\n    pub fn driver_send_request(\n        &mut self, headers: Vec<Header>, fin: bool,\n    ) -> anyhow::Result<u64> {\n        let body_writer_oneshot_tx = if fin {\n            None\n        } else {\n            let (tx, _) = oneshot::channel();\n            Some(tx)\n        };\n        self.driver_enqueue_request(1, headers, body_writer_oneshot_tx);\n        anyhow::ensure!(\n            self.process_commands()? == 1,\n            \"More than one command processed\"\n        );\n        match self.driver_recv_client_event() {\n            Ok(ClientH3Event::NewOutboundRequest {\n                stream_id,\n                request_id: 1,\n            }) => Ok(stream_id),\n            other => Err(anyhow::anyhow!(\"Unexpected result: {other:?}\")),\n        }\n    }\n\n    /// enqueue a `NewClientRequest` command\n    pub fn driver_enqueue_request(\n        &mut self, request_id: u64, headers: Vec<Header>,\n        body_writer: Option<oneshot::Sender<OutboundFrameSender>>,\n    ) {\n        self.controller\n            .request_sender()\n            .send(NewClientRequest {\n                request_id,\n                headers: headers.clone(),\n                body_writer,\n            })\n            .unwrap();\n    }\n\n    /// Try to receive an event from the controller, returns an error if\n    /// the receive fails\n    pub fn driver_recv_core_event(&mut self) -> anyhow::Result<H3Event> {\n        match self.controller.event_receiver_mut().try_recv()? {\n            ClientH3Event::Core(h3_event) => Ok(h3_event),\n            ev => Err(anyhow::anyhow!(\"Not a core event: {ev:?}\")),\n        }\n    }\n\n    /// Try to receive a `ClientH3Event` from the controller's event receiver\n    pub fn driver_recv_client_event(&mut self) -> anyhow::Result<ClientH3Event> {\n        Ok(self.controller.event_receiver_mut().try_recv()?)\n    }\n\n    /// Sends a response from server with default headers.\n    ///\n    /// On success it returns the headers.\n    pub fn peer_server_send_response(\n        &mut self, stream: u64, fin: bool,\n    ) -> h3::Result<Vec<Header>> {\n        let resp = vec![\n            Header::new(b\":status\", b\"200\"),\n            Header::new(b\"server\", b\"quiche-test\"),\n        ];\n\n        self.peer\n            .send_response(&mut self.pipe.server, stream, &resp, fin)?;\n\n        Ok(resp)\n    }\n\n    pub fn peer_server_poll(&mut self) -> h3::Result<(u64, h3::Event)> {\n        self.poll_peer()\n    }\n\n    /// Send a body from the server\n    pub fn peer_server_send_body(\n        &mut self, stream_id: u64, body: &[u8], fin: bool,\n    ) -> h3::Result<usize> {\n        self.peer_send_body(stream_id, body, fin)\n    }\n\n    /// Receive at most `max_read` body bytes and return the read\n    /// bytes as a `Vec`\n    pub fn peer_server_recv_body_vec(\n        &mut self, stream_id: u64, max_read: usize,\n    ) -> h3::Result<Vec<u8>> {\n        self.peer_recv_body_vec(stream_id, max_read)\n    }\n}\n\nimpl DriverTestHelper<ServerHooks> {\n    /// Sends a new client request\n    pub fn peer_client_send_request(\n        &mut self, headers: Vec<Header>, fin: bool,\n    ) -> anyhow::Result<u64> {\n        Ok(self\n            .peer\n            .send_request(&mut self.pipe.client, &headers, fin)?)\n    }\n\n    /// Try to receive an event from the controller, returns an error if\n    /// the receive fails\n    pub fn driver_recv_core_event(&mut self) -> anyhow::Result<H3Event> {\n        match self.controller.event_receiver_mut().try_recv()? {\n            ServerH3Event::Core(h3_event) => Ok(h3_event),\n            ev => Err(anyhow::anyhow!(\"Not a core event: {ev:?}\")),\n        }\n    }\n\n    /// Try to receive a `ServerH3Event` from the controller's event receiver\n    pub fn driver_recv_server_event(&mut self) -> anyhow::Result<ServerH3Event> {\n        Ok(self.controller.event_receiver_mut().try_recv()?)\n    }\n\n    pub fn peer_client_poll(&mut self) -> h3::Result<(u64, h3::Event)> {\n        self.poll_peer()\n    }\n\n    /// Send a body from the server\n    pub fn peer_client_send_body(\n        &mut self, stream_id: u64, body: &[u8], fin: bool,\n    ) -> h3::Result<usize> {\n        self.peer_send_body(stream_id, body, fin)\n    }\n\n    /// Receive at most `max_read` body bytes and return the read\n    /// bytes as a `Vec`\n    pub fn peer_client_recv_body_vec(\n        &mut self, stream_id: u64, max_read: usize,\n    ) -> h3::Result<Vec<u8>> {\n        self.peer_recv_body_vec(stream_id, max_read)\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/driver/tests.rs",
    "content": "use crate::http3::driver::client::ClientHooks;\nuse crate::http3::driver::server::ServerHooks;\nuse assert_matches::assert_matches;\n\nuse super::test_utils::*;\nuse super::*;\n\n/// Tests that use an H3Driver for the client side. We mostly focus on testing\n/// the driver's handling of stream state, and data, rather than H3 semantics.\n/// Note that most of these tests could have just as easily been written for\n/// the server side.\nmod client_side_driver {\n    use super::*;\n\n    #[test]\n    fn client_fin_before_server_body() {\n        let mut helper = DriverTestHelper::<ClientHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request\n        let stream_id = helper\n            .driver_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_server_poll().unwrap(),\n            (0, h3::Event::Headers { .. })\n        );\n        helper.peer_server_send_response(0, false).unwrap();\n\n        helper.advance_and_run_loop().unwrap();\n\n        // Client receives response headers\n        let resp = assert_matches!(\n            helper.driver_recv_core_event().unwrap(),\n            H3Event::IncomingHeaders(headers) => { headers }\n        );\n        assert_eq!(resp.stream_id, stream_id);\n        assert!(!resp.read_fin);\n        let to_server = resp.send.get_ref().unwrap().clone();\n        let mut from_server = resp.recv;\n        // client sends body\n        to_server\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[1; 5]),\n                false,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives client body\n        assert_eq!(helper.peer_server_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(helper.peer_server_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_server_recv_body_vec(0, 1024), Ok(vec![1; 5]));\n\n        // client sends fin, server sends body and fin\n        to_server\n            .try_send(OutboundFrame::Body(BufFactory::get_empty_buf(), true))\n            .unwrap();\n        helper.peer_server_send_body(0, &[2; 10], true).unwrap();\n\n        // Server reads fin\n        helper.advance_and_run_loop().unwrap();\n        // TODO: the server sees an h3::Event::Data, but it's for an empty buffer.\n        // Ideally, it wouldn't do that.\n        assert_eq!(helper.peer_server_poll(), Ok((0, h3::Event::Data)));\n        // No data to be read\n        assert_eq!(\n            helper.peer_server_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n        assert_eq!(helper.peer_server_poll(), Ok((0, h3::Event::Finished)));\n        assert_eq!(helper.peer_server_poll(), Err(h3::Error::Done));\n        helper.advance_and_run_loop().unwrap();\n\n        // client receives the server body\n        assert_matches!(from_server.try_recv(), Ok(InboundFrame::Body(buf, fin)) => {\n            assert_eq!(buf.into_inner().into_vec(), vec![2; 10]);\n            // TODO: it would be nice if we could receive the fin here, but that's not\n            // how quiche::h3 works. Instead we need another receive call on the channel\n            assert!(!fin);\n        });\n        helper.work_loop_iter().unwrap();\n\n        // FIXME: This is an edge case. We should not see a `Disconnected` error\n        // here. The `from_server` / `InboudFrame` channel is set to 1 in tests.\n        // What happens, is the driver reads the previous body frame, then it\n        // sees an `Event::Finished` and calls `process_h3_fin`, which sets\n        // `ctx.fin_recv`. Then it processes the pending write that sends the fin\n        // from client to server. The driver now sees both ctx.fin_read &&\n        // ctx.fin_sent and drops the context and thus the channel. Application\n        // code (H3Body) is not affected by -- it treats a disconnected channel\n        // like receiving a fin. It's a different question if it should treat it\n        // as such\n\n        // assert_matches!(from_server.try_recv(), Ok(InboundFrame::Body(buf,\n        // fin)) => {\n        //    assert_eq!(buf.into_inner().into_vec().len(), 0);\n        //    assert!(fin);\n        //});\n        assert_matches!(from_server.try_recv(), Err(TryRecvError::Disconnected));\n        assert_eq!(helper.driver.stream_map.len(), 0);\n    }\n    /// Test that dropping the OutboundFrame channel causes the driver to\n    /// send a RESET_STREAM frame to the peer.\n    #[test]\n    fn client_send_reset_stream_when_outbound_frame_channel_drops() {\n        let mut helper = DriverTestHelper::<ClientHooks>::new().unwrap();\n        const REQUEST_CANCELED_ERR: u64 =\n            h3::WireErrorCode::RequestCancelled as u64;\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // The client uses H3Driver\n        // client sends a request\n        let stream_id = helper\n            .driver_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_server_poll().unwrap(),\n            (0, h3::Event::Headers { .. })\n        );\n        helper.peer_server_send_response(0, false).unwrap();\n\n        helper.advance_and_run_loop().unwrap();\n\n        // Client receives response headers\n        let resp = assert_matches!(\n            helper.driver_recv_core_event().unwrap(),\n            H3Event::IncomingHeaders(headers) => { headers }\n        );\n        assert_eq!(resp.stream_id, stream_id);\n        assert!(!resp.read_fin);\n        // the stream is waiting on writes\n        assert_eq!(helper.driver.waiting_streams.len(), 1);\n        // take the InboundFrame receiver and stats\n        let mut from_server = resp.recv;\n        let audit_stats = resp.h3_audit_stats.clone();\n        // ... and drop the outbound frame\n        drop(resp.send);\n\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives the reset\n        assert_eq!(\n            helper.peer_server_poll(),\n            Ok((0, h3::Event::Reset(REQUEST_CANCELED_ERR)))\n        );\n        assert_eq!(helper.peer_server_poll(), Err(h3::Error::Done));\n\n        helper.peer_server_send_body(0, &[2; 10], true).unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client receives the server body\n        assert_matches!(from_server.try_recv(), Ok(InboundFrame::Body(buf, fin)) => {\n            assert_eq!(buf.into_inner().into_vec(), vec![2; 10]);\n            // TODO: it would be nice if we could receive the fin here, but that's not\n            // how quiche::h3 works. Instead we need another receive call on the channel\n            assert!(!fin);\n        });\n        helper.work_loop_iter().unwrap();\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), -1);\n        assert_eq!(\n            audit_stats.sent_reset_stream_error_code(),\n            REQUEST_CANCELED_ERR as i64\n        );\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 10);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 0);\n    }\n\n    /// Test that dropping the OutboundFrame channel causes the driver to\n    /// send a RESET_STREAM frame to the peer.\n    #[test]\n    fn client_send_reset_stream_when_outbound_frame_channel_drops_2() {\n        let mut helper = DriverTestHelper::<ClientHooks>::new().unwrap();\n        const REQUEST_CANCELED_ERR: u64 =\n            h3::WireErrorCode::RequestCancelled as u64;\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // The client uses H3Driver\n        // client sends a request\n        let stream_id = helper\n            .driver_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers, body, and fin\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_server_poll().unwrap(),\n            (0, h3::Event::Headers { .. })\n        );\n        helper.peer_server_send_response(0, false).unwrap();\n        helper.peer_server_send_body(0, &[2; 10], true).unwrap();\n\n        helper.advance_and_run_loop().unwrap();\n\n        // Client receives response headers\n        let mut resp = assert_matches!(\n            helper.driver_recv_core_event().unwrap(),\n            H3Event::IncomingHeaders(headers) => { headers }\n        );\n        assert_eq!(resp.stream_id, stream_id);\n        assert!(!resp.read_fin);\n        // take the InboundFrame receiver and stats\n        let mut from_server = resp.recv;\n        let audit_stats = resp.h3_audit_stats.clone();\n        let (body, fin, _) = helper.driver_try_recv_body(&mut from_server);\n        assert_eq!(body, vec![2; 10]);\n        assert!(fin);\n        helper.advance_and_run_loop().unwrap();\n\n        // clsoe the channel.\n        resp.send.close();\n\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives the reset\n        assert_eq!(\n            helper.peer_server_poll(),\n            Ok((0, h3::Event::Reset(REQUEST_CANCELED_ERR)))\n        );\n        assert_eq!(helper.peer_server_poll(), Err(h3::Error::Done));\n\n        helper.advance_and_run_loop().unwrap();\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), -1);\n        assert_eq!(\n            audit_stats.sent_reset_stream_error_code(),\n            REQUEST_CANCELED_ERR as i64\n        );\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 10);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 0);\n    }\n\n    /// Send data until the stream is no longer writable, then drop the\n    /// OutboundFrame channel to trigger a RESET_STREAM\n    #[test]\n    fn client_send_reset_stream_with_full_stream() {\n        let mut config = default_quiche_config();\n        config.set_initial_max_stream_data_bidi_local(30);\n        config.set_initial_max_stream_data_bidi_remote(30);\n        let mut helper = DriverTestHelper::<ClientHooks>::with_pipe(\n            quiche::test_utils::Pipe::with_config(&mut config).unwrap(),\n        )\n        .unwrap();\n        const REQUEST_CANCELED_ERR: u64 =\n            h3::WireErrorCode::RequestCancelled as u64;\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // The client uses H3Driver\n        // client sends a request\n        let stream_id = helper\n            .driver_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers, and fin\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_server_poll().unwrap(),\n            (0, h3::Event::Headers { .. })\n        );\n        helper.peer_server_send_response(0, true).unwrap();\n\n        helper.advance_and_run_loop().unwrap();\n\n        // Client receives response headers\n        let resp = assert_matches!(\n            helper.driver_recv_core_event().unwrap(),\n            H3Event::IncomingHeaders(headers) => { headers }\n        );\n        assert_eq!(resp.stream_id, stream_id);\n        assert!(resp.read_fin);\n        let audit_stats = resp.h3_audit_stats.clone();\n        // send a body the to server, but not enough flow control for the full\n        // body\n        resp.send\n            .get_ref()\n            .unwrap()\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[23; 50]),\n                false,\n            ))\n            .unwrap();\n        assert_eq!(helper.driver.waiting_streams.len(), 1);\n        // run `work_loop_iter()` to write the body into quiche\n        helper.work_loop_iter().unwrap();\n        // make sure we couldn't write the full body\n        assert!(audit_stats.downstream_bytes_sent() < 50);\n        let written = audit_stats.downstream_bytes_sent();\n        // advance the pipe, the stream is writable again, but\n        // don't advance the work_loop yet.\n        helper.pipe.advance().unwrap();\n        while helper.peer_server_poll().is_ok() {}\n        assert_eq!(\n            helper.peer_server_recv_body_vec(0, 1024).unwrap().len(),\n            written as usize\n        );\n        helper.pipe.advance().unwrap();\n        assert_eq!(helper.driver.waiting_streams.len(), 0);\n        assert!(helper.driver.stream_map.get(&0).unwrap().recv.is_some());\n        assert!(helper\n            .driver\n            .stream_map\n            .get(&0)\n            .unwrap()\n            .queued_frame\n            .is_some());\n\n        // clsoe the channel.\n        drop(resp.send);\n\n        helper.work_loop_iter().unwrap();\n        assert_eq!(\n            audit_stats.sent_reset_stream_error_code(),\n            REQUEST_CANCELED_ERR as i64\n        );\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives the reset\n        assert_eq!(\n            helper.peer_server_poll(),\n            Ok((0, h3::Event::Reset(REQUEST_CANCELED_ERR)))\n        );\n        assert_eq!(helper.peer_server_poll(), Err(h3::Error::Done));\n\n        helper.advance_and_run_loop().unwrap();\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), -1);\n        assert_eq!(\n            audit_stats.sent_reset_stream_error_code(),\n            REQUEST_CANCELED_ERR as i64\n        );\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::None);\n    }\n\n    /// Test that dropping the OutboundFrame channel after we've send a fin\n    /// is a no-op.\n    #[test]\n    fn client_drop_outbound_frame_channel_after_fin_no_reset() {\n        let mut helper = DriverTestHelper::<ClientHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // The client uses H3Driver\n        // client sends a request\n        let stream_id = helper\n            .driver_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers, body, and fin\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_server_poll().unwrap(),\n            (0, h3::Event::Headers { .. })\n        );\n        helper.peer_server_send_response(0, false).unwrap();\n\n        helper.advance_and_run_loop().unwrap();\n\n        // Client receives response headers\n        let mut resp = assert_matches!(\n            helper.driver_recv_core_event().unwrap(),\n            H3Event::IncomingHeaders(headers) => { headers }\n        );\n        assert_eq!(resp.stream_id, stream_id);\n        assert!(!resp.read_fin);\n        // take the InboundFrame receiver and stats\n        let mut from_server = resp.recv;\n        let audit_stats = resp.h3_audit_stats.clone();\n        helper.advance_and_run_loop().unwrap();\n        resp.send\n            .get_ref()\n            .unwrap()\n            .try_send(OutboundFrame::Body(BufFactory::get_empty_buf(), true))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // clsoe the channel.\n        resp.send.close();\n\n        helper.advance_and_run_loop().unwrap();\n        assert_eq!(helper.peer_server_send_body(0, &[42], true), Ok(1));\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives the fin\n        assert_eq!(helper.peer_server_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(\n            helper.peer_server_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n        assert_eq!(helper.peer_server_poll(), Ok((0, h3::Event::Finished)));\n        assert_eq!(helper.peer_server_poll(), Err(h3::Error::Done));\n\n        helper.advance_and_run_loop().unwrap();\n\n        // client receives the body and fin\n        let (body, fin, _err) = helper.driver_try_recv_body(&mut from_server);\n        assert_eq!(body, &[42]);\n        assert!(fin);\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 1);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 0);\n    }\n}\n\n/// Tests that use an H3Driver for the server side. We mostly focus on testing\n/// the driver's handling of stream state, and data, rather than H3 semantics.\n/// Note that most of these tests could have just as easily been written for\n/// the client side.\nmod server_side_driver {\n    use super::*;\n\n    #[test]\n    fn client_fin_before_server_body() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let mut from_client = req.recv;\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n\n        // client reads response and sends body and fin\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_send_body(0, &[1; 5], true), Ok(5));\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives body\n        let (body, fin, _err) = helper.driver_try_recv_body(&mut from_client);\n        assert_eq!(body, vec![1; 5]);\n        assert!(fin);\n\n        // server sends body and fin\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[42]),\n                true,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_recv_body_vec(0, 1024), Ok(vec![42]));\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Finished)));\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n    }\n\n    // This test verifies https://github.com/cloudflare/quiche/pull/2162\n    #[test]\n    fn verify_pr_2162() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request but NO FIN.\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let mut from_client = req.recv;\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n        helper.work_loop_iter().unwrap();\n        // server sends body and fin. This caused an infinite loop before #2162\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[42]),\n                true,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends body and fin\n        helper.advance_and_run_loop().unwrap();\n        assert_eq!(helper.peer_client_send_body(0, &[1; 5], true), Ok(5));\n        helper.advance_and_run_loop().unwrap();\n\n        let (body, fin, _err) = helper.driver_try_recv_body(&mut from_client);\n        assert_eq!(body, &[1; 5]);\n        assert!(fin);\n\n        // Stream is done\n        assert_eq!(helper.driver.stream_map.len(), 0);\n    }\n\n    /// Test the case where the client sends a STOP_SENDING quiche frame.\n    #[test]\n    fn client_sends_stop_sending() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let mut from_client = req.recv;\n        let audit_stats = req.h3_audit_stats;\n\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n\n        // client sends a STOP_SENDING\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(\n            helper\n                .pipe\n                .client\n                .stream_shutdown(0, quiche::Shutdown::Read, 4242),\n            Ok(())\n        );\n        helper.advance_and_run_loop().unwrap();\n\n        // the client didn't send any additional data, a try_recv on the server\n        // returns empty\n        assert_matches!(from_client.try_recv(), Err(TryRecvError::Empty));\n        // The way quiche is implemented, we need to attempt a write to the stream\n        // to learn that it's closed. So we add an OutboundFrame to the\n        // channel and let the driver write it. The driver gets a\n        // StreamStopped back and closes the channel.\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[23; 10]),\n                false,\n            ))\n            .unwrap();\n        helper.work_loop_iter().unwrap();\n        assert!(to_client.is_closed());\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), 4242);\n        helper.work_loop_iter().unwrap();\n\n        // STOP_SENDING only closes one half of the stream. The client\n        // can still send data and it MUST send a `fin` to close the\n        // other half.\n        helper.peer_client_send_body(0, &[1, 2, 3], true).unwrap();\n        helper.advance_and_run_loop().unwrap();\n        let (body, fin, _err) = helper.driver_try_recv_body(&mut from_client);\n        assert_eq!(body, &[1, 2, 3]);\n        assert!(fin);\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), 4242);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        // technically quiche will automatically respond to a STOP_SENDING\n        // frame with a STREAM_RESET echoing the error code, but the user\n        // didn't *actively* send a STREAM_RESET.\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 3);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 0);\n    }\n\n    /// Test the case where the client sends a RESET_STREAM quiche frame.\n    /// The peer sends its reset before we send a fin\n    #[test]\n    fn client_sends_reset_stream_before_server_fin() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client (peer) sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let from_client = req.recv;\n        let audit_stats = req.h3_audit_stats;\n\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n\n        // client sends a RESET_STREAM frame\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(\n            helper\n                .pipe\n                .client\n                .stream_shutdown(0, quiche::Shutdown::Write, 4242),\n            Ok(())\n        );\n        helper.advance_and_run_loop().unwrap();\n\n        // The channel is closed because the peer send us the reset.\n        assert!(from_client.is_closed());\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), 4242);\n        assert_matches!(\n            helper.driver_recv_core_event(),\n            Ok(H3Event::ResetStream { stream_id: 0 })\n        );\n\n        // We can still write to the peer and in fact, we must eventually send a\n        // fin.\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[5; 4]),\n                false,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[6; 4]),\n                true,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Ok(vec![5, 5, 5, 5, 6, 6, 6, 6])\n        );\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), 4242);\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 0);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 8);\n    }\n\n    /// Test the case where the client sends a RESET_STREAM quiche frame.\n    /// We send a fin before the client sends reset\n    #[test]\n    fn client_sends_reset_stream_after_server_fin() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client (peer) sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let from_client = req.recv;\n        let audit_stats = req.h3_audit_stats;\n\n        // Send response, body, and fin to client\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n        helper.work_loop_iter().unwrap();\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(b\"foobar 42\"),\n                true,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a RESET_STREAM frame\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_matches!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        helper.peer_client_recv_body_vec(0, 1024).unwrap();\n        assert_eq!(\n            helper\n                .pipe\n                .client\n                .stream_shutdown(0, quiche::Shutdown::Write, 4242),\n            Ok(())\n        );\n        helper.advance_and_run_loop().unwrap();\n\n        // The channel is closed because the peer send us the reset.\n        assert!(from_client.is_closed());\n        assert_matches!(\n            helper.driver_recv_core_event(),\n            Ok(H3Event::ResetStream { stream_id: 0 })\n        );\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), 4242);\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 0);\n        assert_eq!(\n            audit_stats.downstream_bytes_sent(),\n            b\"foobar 42\".len() as u64\n        );\n    }\n\n    /// Test the case where the client sends a RESET_STREAM quiche frame while\n    /// we're in the middle of reading data. We want to excercise the\n    /// code-path where `upstream_ready` is called before `process_reads`.\n    /// If `process_reads()` is called first, it will get the Reset event.\n    /// If `upstream_ready()` is called first, it will attempt to read from\n    /// the h3::Connection and will get a\n    /// `TransportError(StreamReset(code))`\n    #[test]\n    fn client_sends_reset_stream_while_reading_wait_for_data() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client (peer) sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers and some body bytes\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let mut from_client = req.recv;\n        let audit_stats = req.h3_audit_stats;\n\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n        helper.work_loop_iter().unwrap();\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[1, 2, 3, 4]),\n                false,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends data\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_matches!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_send_body(0, &[1; 10], false), Ok(10));\n\n        // Advance the pipe and let the driver read a part of the body and\n        // put it into the `from_client` channel\n        helper.pipe.advance().unwrap();\n        // Limit the amount of data we read from the stream.\n        helper.driver.pooled_buf = BufFactory::buf_from_slice(&[0; 5]);\n        helper.work_loop_iter().unwrap();\n        assert_matches!(from_client.try_recv(), Ok(InboundFrame::Body(buf, fin)) => {\n            assert_eq!(buf.into_inner().into_vec(), &[1; 5]);\n            assert!(!fin);\n        });\n        assert_matches!(\n            helper.driver_recv_core_event(),\n            Ok(H3Event::BodyBytesReceived {\n                stream_id: 0,\n                num_bytes: 5,\n                fin: false\n            })\n        );\n        assert_matches!(\n            helper.controller.event_receiver_mut().try_recv(),\n            Err(TryRecvError::Empty)\n        );\n\n        // client sends a reset.\n        // TODO: This is a bit finnicky to test properly. We don't want to\n        // run a full `work_loop_iter()` because that would call `process_reads()`\n        // first.\n        helper.pipe.advance().unwrap();\n        assert_eq!(\n            helper\n                .pipe\n                .client\n                .stream_shutdown(0, quiche::Shutdown::Write, 4242),\n            Ok(())\n        );\n        helper.pipe.advance().unwrap();\n        tokio::task::unconstrained(\n            helper.driver.wait_for_data(&mut helper.pipe.server),\n        )\n        .now_or_never()\n        .unwrap_or(Ok(()))\n        .unwrap();\n\n        // The channel is closed because the peer send us the reset.\n        assert!(from_client.is_closed());\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), 4242);\n        assert_matches!(\n            helper.driver_recv_core_event(),\n            Ok(H3Event::ResetStream { stream_id: 0 })\n        );\n\n        // We can still write to the peer and in fact, we must eventually send a\n        // fin.\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[6; 4]),\n                true,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Ok(vec![1, 2, 3, 4, 6, 6, 6, 6])\n        );\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Finished)));\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), 4242);\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 5);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 8);\n    }\n\n    /// Test the case where the client sends a RESET_STREAM quiche frame while\n    /// we're in the middle of reading data. We want to excercise the\n    /// code-path where where we call `process_reads` before\n    /// `upstream_ready()`.\n    #[test]\n    fn server_sends_reset_stream_while_reading_process_reads() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client (peer) sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let mut from_client = req.recv;\n        let audit_stats = req.h3_audit_stats;\n\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends data\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_send_body(0, &[1; 10], false), Ok(10));\n\n        // Advance the pipe and let the driver read a part of the body and\n        // put it into the `from_client` channel\n        helper.pipe.advance().unwrap();\n        // Limit the amount of data we read from the stream.\n        helper.driver.pooled_buf = BufFactory::buf_from_slice(&[0; 5]);\n        helper.work_loop_iter().unwrap();\n        assert_matches!(from_client.try_recv(), Ok(InboundFrame::Body(buf, fin)) => {\n            assert_eq!(buf.into_inner().into_vec(), &[1; 5]);\n            assert!(!fin);\n        });\n        assert_matches!(\n            helper.driver_recv_core_event(),\n            Ok(H3Event::BodyBytesReceived {\n                stream_id: 0,\n                num_bytes: 5,\n                fin: false\n            })\n        );\n\n        // client sends a reset.\n        assert_eq!(\n            helper\n                .pipe\n                .client\n                .stream_shutdown(0, quiche::Shutdown::Write, 4242),\n            Ok(())\n        );\n        helper.advance_and_run_loop().unwrap();\n\n        // The channel is closed because the peer send us the reset.\n        assert!(from_client.is_closed());\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), 4242);\n        assert_matches!(\n            helper.driver_recv_core_event(),\n            Ok(H3Event::ResetStream { stream_id: 0 })\n        );\n\n        // send fin to client\n        to_client\n            .try_send(OutboundFrame::Body(BufFactory::get_empty_buf(), true))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Finished)));\n\n        assert_eq!(helper.driver.stream_map.len(), 0);\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), 4242);\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 5);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 0);\n    }\n\n    #[test]\n    fn server_driver_send_stop_sending_after_channel_drop() {\n        const REQUEST_CANCELED_ERR: u64 =\n            h3::WireErrorCode::RequestCancelled as u64;\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        let audit_stats = req.h3_audit_stats.clone();\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let mut from_client = req.recv;\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n\n        // client reads response and sends body without fin\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_send_body(0, &[1; 5], false), Ok(5));\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives body\n        let (body, fin, _err) = helper.driver_try_recv_body(&mut from_client);\n        assert_eq!(body, vec![1; 5]);\n        assert!(!fin);\n\n        // peer (client) sends more data\n        assert_eq!(helper.peer_client_send_body(0, &[1; 6], false), Ok(6));\n        // advance the pipe only\n        helper.pipe.advance().unwrap();\n        // we drop the channel.\n        drop(from_client);\n        helper.advance_and_run_loop().unwrap();\n\n        assert_matches!(\n            helper.driver_recv_core_event(),\n            Ok(H3Event::BodyBytesReceived {\n                stream_id: 0,\n                num_bytes: 5,\n                fin: false\n            })\n        );\n        assert_matches!(\n            helper.controller.event_receiver_mut().try_recv(),\n            Err(TryRecvError::Empty)\n        );\n\n        // Make sure the peer has received our STOP_SENDING frame\n        assert_eq!(\n            helper.peer_client_send_body(0, &[1; 7], false),\n            Err(h3::Error::TransportError(quiche::Error::StreamStopped(\n                REQUEST_CANCELED_ERR\n            )))\n        );\n        helper.advance_and_run_loop().unwrap();\n\n        // we still need to send a fin\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[42]),\n                true,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_recv_body_vec(0, 1024), Ok(vec![42]));\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Finished)));\n\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(\n            audit_stats.sent_stop_sending_error_code(),\n            REQUEST_CANCELED_ERR as i64\n        );\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 5);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 1);\n        assert_eq!(helper.driver.stream_map.len(), 0);\n    }\n\n    // Verify we don't send a STOP_SENDING frame if we've already processed a\n    // fin\n    #[test]\n    fn server_driver_drop_channel_after_fin() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        let audit_stats = req.h3_audit_stats.clone();\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let mut from_client = req.recv;\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n\n        // client reads response and sends body WITH fin\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_send_body(0, &[1; 5], true), Ok(5));\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives body\n        let (body, fin, _err) = helper.driver_try_recv_body(&mut from_client);\n        assert_eq!(body, vec![1; 5]);\n        assert!(fin);\n\n        helper.advance_and_run_loop().unwrap();\n        // we drop the channel.\n        drop(from_client);\n        helper.advance_and_run_loop().unwrap();\n\n        // we still need to send a fin\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[42]),\n                true,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_recv_body_vec(0, 1024), Ok(vec![42]));\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Finished)));\n\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 5);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 1);\n        assert_eq!(helper.driver.stream_map.len(), 0);\n    }\n\n    // Test the edge case where the driver has read a fin from the stream but\n    // hasn't been able to deliver it before the channel is dropped.\n    #[test]\n    fn server_driver_drop_channel_after_fin_2() {\n        const REQUEST_CANCELED_ERR: u64 =\n            h3::WireErrorCode::RequestCancelled as u64;\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        let audit_stats = req.h3_audit_stats.clone();\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n\n        // client reads response and sends body without fin\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_send_body(0, &[1; 5], false), Ok(5));\n        helper.advance_and_run_loop().unwrap();\n\n        // peer (client) sends more data and fin\n        assert_eq!(helper.peer_client_send_body(0, &[1; 6], true), Ok(6));\n        helper.advance_and_run_loop().unwrap();\n        // we drop the channel.\n        drop(req.recv);\n        helper.advance_and_run_loop().unwrap();\n\n        assert_matches!(\n            helper.driver_recv_core_event(),\n            Ok(H3Event::BodyBytesReceived {\n                stream_id: 0,\n                num_bytes: 5,\n                fin: false\n            })\n        );\n        assert_matches!(\n            helper.controller.event_receiver_mut().try_recv(),\n            Err(TryRecvError::Empty)\n        );\n\n        // we still need to send a fin\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[42]),\n                true,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_recv_body_vec(0, 1024), Ok(vec![42]));\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Finished)));\n\n        assert_eq!(audit_stats.recvd_stop_sending_error_code(), -1);\n        assert_eq!(audit_stats.recvd_reset_stream_error_code(), -1);\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(\n            audit_stats.sent_stop_sending_error_code(),\n            REQUEST_CANCELED_ERR as i64\n        );\n        assert_eq!(audit_stats.recvd_stream_fin(), StreamClosureKind::None);\n        assert_eq!(audit_stats.sent_stream_fin(), StreamClosureKind::Explicit);\n        assert_eq!(audit_stats.downstream_bytes_recvd(), 5);\n        assert_eq!(audit_stats.downstream_bytes_sent(), 1);\n        assert_eq!(helper.driver.stream_map.len(), 0);\n    }\n\n    #[test]\n    fn server_send_trailers() {\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // servers reads request and sends response headers\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        assert!(!req.read_fin);\n        let to_client = req.send.get_ref().unwrap().clone();\n        let mut from_client = req.recv;\n        to_client\n            .try_send(OutboundFrame::Headers(make_response_headers(), None))\n            .unwrap();\n\n        // client reads response and sends body and fin\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n        assert_eq!(helper.peer_client_send_body(0, &[1; 5], true), Ok(5));\n        helper.advance_and_run_loop().unwrap();\n\n        // server receives body\n        let (body, fin, _err) = helper.driver_try_recv_body(&mut from_client);\n        assert_eq!(body, vec![1; 5]);\n        assert!(fin);\n\n        // server sends body\n        to_client\n            .try_send(OutboundFrame::Body(\n                BufFactory::buf_from_slice(&[42]),\n                false,\n            ))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Data)));\n        assert_eq!(helper.peer_client_recv_body_vec(0, 1024), Ok(vec![42]));\n        assert_eq!(\n            helper.peer_client_recv_body_vec(0, 1024),\n            Err(h3::Error::Done)\n        );\n\n        // server sends trailers\n        to_client\n            .try_send(OutboundFrame::Trailers(make_response_trailers(), None))\n            .unwrap();\n        helper.advance_and_run_loop().unwrap();\n        assert_matches!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Headers { .. }))\n        );\n\n        assert_eq!(helper.peer_client_poll(), Ok((0, h3::Event::Finished)));\n        assert_eq!(helper.peer_client_poll(), Err(h3::Error::Done));\n    }\n\n    /// Test that calling `H3Controller::shutdown_stream` with\n    /// `StreamShutdown::Write` sends a RESET_STREAM frame to the peer.\n    #[test]\n    fn server_shutdown_stream_write_direction() {\n        use crate::http3::driver::StreamShutdown;\n        const CUSTOM_ERROR_CODE: u64 = 0x1234;\n\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"GET\"), false)\n            .unwrap();\n\n        // server reads request\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        let audit_stats = req.h3_audit_stats.clone();\n\n        // Server calls shutdown_stream via the controller\n        helper\n            .controller\n            .shutdown_stream(stream_id, StreamShutdown::Write {\n                error_code: CUSTOM_ERROR_CODE,\n            });\n\n        helper.advance_and_run_loop().unwrap();\n\n        // Client should receive the RESET_STREAM\n        // Note: quiche::h3 reports RESET_STREAM via a TransportError when\n        // trying to read from the stream\n        assert_eq!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Reset(CUSTOM_ERROR_CODE)))\n        );\n\n        // Verify stats\n        assert_eq!(\n            audit_stats.sent_reset_stream_error_code(),\n            CUSTOM_ERROR_CODE as i64\n        );\n        assert_eq!(audit_stats.sent_stop_sending_error_code(), -1);\n    }\n\n    /// Test that calling `H3Controller::shutdown_stream` with\n    /// `StreamShutdown::Read` sends a STOP_SENDING frame to the peer.\n    #[test]\n    fn server_shutdown_stream_read_direction() {\n        use crate::http3::driver::StreamShutdown;\n        const CUSTOM_ERROR_CODE: u64 = 0x5678;\n\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request without fin (expecting to send body)\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"POST\"), false)\n            .unwrap();\n\n        // server reads request\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        let audit_stats = req.h3_audit_stats.clone();\n\n        // Server calls shutdown_stream with Read direction (STOP_SENDING)\n        helper\n            .controller\n            .shutdown_stream(stream_id, StreamShutdown::Read {\n                error_code: CUSTOM_ERROR_CODE,\n            });\n\n        helper.advance_and_run_loop().unwrap();\n\n        // Client tries to send body - should get StreamStopped error\n        assert_eq!(\n            helper.peer_client_send_body(0, &[1, 2, 3], false),\n            Err(h3::Error::TransportError(quiche::Error::StreamStopped(\n                CUSTOM_ERROR_CODE\n            )))\n        );\n\n        // Verify stats\n        assert_eq!(audit_stats.sent_reset_stream_error_code(), -1);\n        assert_eq!(\n            audit_stats.sent_stop_sending_error_code(),\n            CUSTOM_ERROR_CODE as i64\n        );\n    }\n\n    /// Test that calling `H3Controller::shutdown_stream` with\n    /// `StreamShutdown::Both` sends both RESET_STREAM and STOP_SENDING\n    /// frames to the peer.\n    #[test]\n    fn server_shutdown_stream_both_directions() {\n        use crate::http3::driver::StreamShutdown;\n        const READ_ERROR_CODE: u64 = 0xAAAA;\n        const WRITE_ERROR_CODE: u64 = 0xBBBB;\n\n        let mut helper = DriverTestHelper::<ServerHooks>::new().unwrap();\n        helper.complete_handshake().unwrap();\n        helper.advance_and_run_loop().unwrap();\n\n        // client sends a request without fin\n        let stream_id = helper\n            .peer_client_send_request(make_request_headers(\"POST\"), false)\n            .unwrap();\n\n        // server reads request\n        helper.advance_and_run_loop().unwrap();\n        let req = assert_matches!(\n            helper.driver_recv_server_event().unwrap(),\n            ServerH3Event::Headers{incoming_headers, ..} => { incoming_headers }\n        );\n        assert_eq!(req.stream_id, stream_id);\n        let audit_stats = req.h3_audit_stats.clone();\n\n        // Server calls shutdown_stream with Both directions\n        helper\n            .controller\n            .shutdown_stream(stream_id, StreamShutdown::Both {\n                read_error_code: READ_ERROR_CODE,\n                write_error_code: WRITE_ERROR_CODE,\n            });\n\n        helper.advance_and_run_loop().unwrap();\n\n        // Client should see STOP_SENDING when trying to send\n        assert_eq!(\n            helper.peer_client_send_body(0, &[1, 2, 3], false),\n            Err(h3::Error::TransportError(quiche::Error::StreamStopped(\n                READ_ERROR_CODE\n            )))\n        );\n\n        // Client should see RESET_STREAM when trying to receive\n        assert_eq!(\n            helper.peer_client_poll(),\n            Ok((0, h3::Event::Reset(WRITE_ERROR_CODE)))\n        );\n\n        // Verify stats\n        assert_eq!(\n            audit_stats.sent_reset_stream_error_code(),\n            WRITE_ERROR_CODE as i64\n        );\n        assert_eq!(\n            audit_stats.sent_stop_sending_error_code(),\n            READ_ERROR_CODE as i64\n        );\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! HTTP/3 integrations for tokio-quiche.\n\n/// An [`ApplicationOverQuic`](crate::ApplicationOverQuic) to build clients\n/// and servers on top of.\npub mod driver;\n/// Configuration for HTTP/3 connections.\npub mod settings;\nmod stats;\n\npub use self::driver::connection::ClientH3Connection;\npub use self::driver::connection::ServerH3Connection;\npub use self::stats::H3AuditStats;\n"
  },
  {
    "path": "tokio-quiche/src/http3/settings.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::future::poll_fn;\nuse std::task::Context;\nuse std::task::Poll;\nuse std::time::Duration;\n\nuse crate::http3::driver::H3ConnectionError;\nuse crate::quic::QuicheConnection;\n\nuse foundations::telemetry::log;\nuse tokio_util::time::delay_queue::DelayQueue;\nuse tokio_util::time::delay_queue::{\n    self,\n};\n\n/// Unified configuration parameters for\n/// [H3Driver](crate::http3::driver::H3Driver)s.\n#[derive(Default, Clone, Debug)]\npub struct Http3Settings {\n    /// Maximum number of requests a\n    /// [ServerH3Driver](crate::http3::driver::ServerH3Driver) allows per\n    /// connection.\n    pub max_requests_per_connection: Option<u64>,\n    /// Maximum size of a single HEADERS frame, in bytes.\n    pub max_header_list_size: Option<u64>,\n    /// Maximum value the QPACK encoder is permitted to set for the dynamic\n    /// table capcity. See <https://www.rfc-editor.org/rfc/rfc9204.html#name-maximum-dynamic-table-capac>\n    pub qpack_max_table_capacity: Option<u64>,\n    /// Upper bound on the number of streams that can be blocked on the QPACK\n    /// decoder. See <https://www.rfc-editor.org/rfc/rfc9204.html#name-blocked-streams>\n    pub qpack_blocked_streams: Option<u64>,\n    /// Timeout between starting the QUIC handshake and receiving the first\n    /// request on a connection. Only applicable to\n    /// [ServerH3Driver](crate::http3::driver::ServerH3Driver).\n    pub post_accept_timeout: Option<Duration>,\n    /// Set the `SETTINGS_ENABLE_CONNECT_PROTOCOL` HTTP/3 setting.\n    /// See <https://www.rfc-editor.org/rfc/rfc9220#section-3-2>\n    pub enable_extended_connect: bool,\n}\n\nimpl From<&Http3Settings> for quiche::h3::Config {\n    fn from(value: &Http3Settings) -> Self {\n        let mut config = Self::new().unwrap();\n\n        if let Some(v) = value.max_header_list_size {\n            config.set_max_field_section_size(v);\n        }\n\n        if let Some(v) = value.qpack_max_table_capacity {\n            config.set_qpack_max_table_capacity(v);\n        }\n\n        if let Some(v) = value.qpack_blocked_streams {\n            config.set_qpack_blocked_streams(v);\n        }\n\n        if value.enable_extended_connect {\n            config.enable_extended_connect(value.enable_extended_connect)\n        }\n\n        config\n    }\n}\n\n/// Opaque handle to an entry in [`Http3Timeouts`].\npub(crate) struct TimeoutKey(delay_queue::Key);\n\npub(crate) struct Http3SettingsEnforcer {\n    limits: Http3Limits,\n    timeouts: Http3Timeouts,\n}\n\nimpl From<&Http3Settings> for Http3SettingsEnforcer {\n    fn from(value: &Http3Settings) -> Self {\n        Self {\n            limits: Http3Limits {\n                max_requests_per_connection: value.max_requests_per_connection,\n            },\n            timeouts: Http3Timeouts {\n                post_accept_timeout: value.post_accept_timeout,\n                delay_queue: DelayQueue::new(),\n            },\n        }\n    }\n}\n\nimpl Http3SettingsEnforcer {\n    /// Returns a boolean indicating whether or not the connection should be\n    /// closed due to a violation of the request count limit.\n    pub fn enforce_requests_limit(&self, request_count: u64) -> bool {\n        if let Some(limit) = self.limits.max_requests_per_connection {\n            return request_count >= limit;\n        }\n\n        false\n    }\n\n    /// Returns the configured post-accept timeout.\n    pub fn post_accept_timeout(&self) -> Option<Duration> {\n        self.timeouts.post_accept_timeout\n    }\n\n    /// Registers a timeout of `typ` in this [Http3SettingsEnforcer].\n    pub fn add_timeout(\n        &mut self, typ: Http3TimeoutType, duration: Duration,\n    ) -> TimeoutKey {\n        let key = self.timeouts.delay_queue.insert(typ, duration);\n        TimeoutKey(key)\n    }\n\n    /// Checks whether the [Http3SettingsEnforcer] has any pending timeouts.\n    /// This should be used to selectively poll `enforce_timeouts`.\n    pub fn has_pending_timeouts(&self) -> bool {\n        !self.timeouts.delay_queue.is_empty()\n    }\n\n    /// Checks which timeouts have expired.\n    fn poll_timeouts(&mut self, cx: &mut Context) -> Poll<TimeoutCheckResult> {\n        let mut changed = false;\n        let mut result = TimeoutCheckResult::default();\n\n        while let Poll::Ready(Some(exp)) =\n            self.timeouts.delay_queue.poll_expired(cx)\n        {\n            changed |= result.set_expired(exp.into_inner());\n        }\n\n        if changed {\n            return Poll::Ready(result);\n        }\n        Poll::Pending\n    }\n\n    /// Waits for at least one registered timeout to expire.\n    ///\n    /// This function will automatically call `close()` on the underlying\n    /// [quiche::Connection].\n    pub async fn enforce_timeouts(\n        &mut self, qconn: &mut QuicheConnection,\n    ) -> Result<(), H3ConnectionError> {\n        let result = poll_fn(|cx| self.poll_timeouts(cx)).await;\n\n        if result.connection_timed_out {\n            log::debug!(\"connection timed out due to post-accept-timeout\"; \"scid\" => ?qconn.source_id());\n            qconn.close(true, quiche::h3::WireErrorCode::NoError as u64, &[])?;\n        }\n\n        Ok(())\n    }\n\n    /// Cancels a timeout that was previously registered with `add_timeout`.\n    pub fn cancel_timeout(&mut self, key: TimeoutKey) {\n        self.timeouts.delay_queue.remove(&key.0);\n    }\n}\n\n// TODO(rmehra): explore if these should really be Options, or if we\n// should enforce sane defaults\nstruct Http3Limits {\n    max_requests_per_connection: Option<u64>,\n}\n\nstruct Http3Timeouts {\n    post_accept_timeout: Option<Duration>,\n    delay_queue: DelayQueue<Http3TimeoutType>,\n}\n\n#[derive(Clone, Copy, Debug)]\npub(crate) enum Http3TimeoutType {\n    PostAccept,\n}\n\n#[derive(Default, Eq, PartialEq)]\nstruct TimeoutCheckResult {\n    connection_timed_out: bool,\n}\n\nimpl TimeoutCheckResult {\n    fn set_expired(&mut self, typ: Http3TimeoutType) -> bool {\n        use Http3TimeoutType::*;\n        let field = match typ {\n            PostAccept => &mut self.connection_timed_out,\n        };\n\n        *field = true;\n        true\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/http3/stats.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::sync::atomic::AtomicI64;\nuse std::sync::atomic::AtomicU64;\nuse std::sync::atomic::Ordering;\nuse std::time::Duration;\n\nuse crossbeam::atomic::AtomicCell;\nuse datagram_socket::StreamClosureKind;\n\n/// Stream-level HTTP/3 audit statistics recorded by\n/// [H3Driver](crate::http3::driver::H3Driver).\n#[derive(Debug)]\npub struct H3AuditStats {\n    /// The stream ID of this session.\n    stream_id: u64,\n    /// The number of bytes sent over the stream.\n    downstream_bytes_sent: AtomicU64,\n    /// The number of bytes received over the stream.\n    downstream_bytes_recvd: AtomicU64,\n    /// A STOP_SENDING error code received from the peer.\n    ///\n    /// -1 indicates that this error code was not received yet.\n    recvd_stop_sending_error_code: AtomicI64,\n    /// A RESET_STREAM error code received from the peer.\n    ///\n    /// -1 indicates that this error code was not received yet.\n    recvd_reset_stream_error_code: AtomicI64,\n    /// A STOP_SENDING error code sent to the peer.\n    ///\n    /// -1 indicates that this error code was not received yet.\n    sent_stop_sending_error_code: AtomicI64,\n    /// A RESET_STREAM error code sent to the peer.\n    ///\n    /// -1 indicates that this error code was not received yet.\n    sent_reset_stream_error_code: AtomicI64,\n    /// Stream FIN received from the peer.\n    recvd_stream_fin: AtomicCell<StreamClosureKind>,\n    /// Stream FIN sent to the peer.\n    sent_stream_fin: AtomicCell<StreamClosureKind>,\n    /// Cumulative time between HEADERS failed flush and complete.\n    ///\n    /// Measured as the duration between the first moment a HEADERS frame was\n    /// not flushed in full, and the moment that it was completely flushed.\n    /// Measured across all HEADERS frames sent on the stream. A value of 0\n    /// indicates there was no failed flushing.\n    headers_flush_duration: AtomicCell<Duration>,\n}\n\nimpl H3AuditStats {\n    pub fn new(stream_id: u64) -> Self {\n        Self {\n            stream_id,\n            downstream_bytes_sent: AtomicU64::new(0),\n            downstream_bytes_recvd: AtomicU64::new(0),\n            recvd_stop_sending_error_code: AtomicI64::new(-1),\n            recvd_reset_stream_error_code: AtomicI64::new(-1),\n            sent_stop_sending_error_code: AtomicI64::new(-1),\n            sent_reset_stream_error_code: AtomicI64::new(-1),\n            recvd_stream_fin: AtomicCell::new(StreamClosureKind::None),\n            sent_stream_fin: AtomicCell::new(StreamClosureKind::None),\n            headers_flush_duration: AtomicCell::new(Duration::from_secs(0)),\n        }\n    }\n\n    /// The stream ID of this session.\n    #[inline]\n    pub fn stream_id(&self) -> u64 {\n        self.stream_id\n    }\n\n    /// The number of bytes sent over the stream.\n    #[inline]\n    pub fn downstream_bytes_sent(&self) -> u64 {\n        self.downstream_bytes_sent.load(Ordering::SeqCst)\n    }\n\n    /// The number of bytes received over the stream.\n    #[inline]\n    pub fn downstream_bytes_recvd(&self) -> u64 {\n        self.downstream_bytes_recvd.load(Ordering::SeqCst)\n    }\n\n    /// A STOP_SENDING error code received from the peer.\n    ///\n    /// -1 indicates that this error code was not received yet.\n    #[inline]\n    pub fn recvd_stop_sending_error_code(&self) -> i64 {\n        self.recvd_stop_sending_error_code.load(Ordering::SeqCst)\n    }\n\n    /// A RESET_STREAM error code received from the peer.\n    ///\n    /// -1 indicates that this error code was not received yet.\n    #[inline]\n    pub fn recvd_reset_stream_error_code(&self) -> i64 {\n        self.recvd_reset_stream_error_code.load(Ordering::SeqCst)\n    }\n\n    /// A STOP_SENDING error code sent to the peer.\n    ///\n    /// -1 indicates that this error code was not received yet.\n    #[inline]\n    pub fn sent_stop_sending_error_code(&self) -> i64 {\n        self.sent_stop_sending_error_code.load(Ordering::SeqCst)\n    }\n\n    /// A RESET_STREAM error code sent to the peer.\n    ///\n    /// -1 indicates that this error code was not received yet.\n    #[inline]\n    pub fn sent_reset_stream_error_code(&self) -> i64 {\n        self.sent_reset_stream_error_code.load(Ordering::SeqCst)\n    }\n\n    /// Stream FIN received from the peer.\n    #[inline]\n    pub fn recvd_stream_fin(&self) -> StreamClosureKind {\n        self.recvd_stream_fin.load()\n    }\n\n    /// Stream FIN sent to the peer.\n    #[inline]\n    pub fn sent_stream_fin(&self) -> StreamClosureKind {\n        self.sent_stream_fin.load()\n    }\n\n    /// Cumulative time between HEADERS failed flush and complete.\n    ///\n    /// Measured as the duration between the first moment a HEADERS frame was\n    /// not flushed in full, and the moment that it was completely flushed.\n    /// Measured across all HEADERS frames sent on the stream. A value of 0\n    /// indicates there was no failed flushing.\n    #[inline]\n    pub fn headers_flush_duration(&self) -> Duration {\n        self.headers_flush_duration.load()\n    }\n\n    #[inline]\n    pub fn add_downstream_bytes_sent(&self, bytes_sent: u64) {\n        self.downstream_bytes_sent\n            .fetch_add(bytes_sent, Ordering::SeqCst);\n    }\n\n    #[inline]\n    pub fn add_downstream_bytes_recvd(&self, bytes_recvd: u64) {\n        self.downstream_bytes_recvd\n            .fetch_add(bytes_recvd, Ordering::SeqCst);\n    }\n\n    #[inline]\n    pub fn set_recvd_stop_sending_error_code(\n        &self, recvd_stop_sending_error_code: i64,\n    ) {\n        self.recvd_stop_sending_error_code\n            .store(recvd_stop_sending_error_code, Ordering::SeqCst);\n    }\n\n    #[inline]\n    pub fn set_recvd_reset_stream_error_code(\n        &self, recvd_reset_stream_error_code: i64,\n    ) {\n        self.recvd_reset_stream_error_code\n            .store(recvd_reset_stream_error_code, Ordering::SeqCst);\n    }\n\n    #[inline]\n    pub fn set_sent_stop_sending_error_code(\n        &self, sent_stop_sending_error_code: i64,\n    ) {\n        self.sent_stop_sending_error_code\n            .store(sent_stop_sending_error_code, Ordering::SeqCst);\n    }\n\n    #[inline]\n    pub fn set_sent_reset_stream_error_code(\n        &self, sent_reset_stream_error_code: i64,\n    ) {\n        self.sent_reset_stream_error_code\n            .store(sent_reset_stream_error_code, Ordering::SeqCst);\n    }\n\n    #[inline]\n    pub fn set_recvd_stream_fin(&self, recvd_stream_fin: StreamClosureKind) {\n        self.recvd_stream_fin.store(recvd_stream_fin);\n    }\n\n    #[inline]\n    pub fn set_sent_stream_fin(&self, sent_stream_fin: StreamClosureKind) {\n        self.sent_stream_fin.store(sent_stream_fin);\n    }\n\n    #[inline]\n    pub fn add_header_flush_duration(&self, duration: Duration) {\n        // NB: load and store may not be atomic but we aren't accessing the\n        // object from any other thread so things should be ok.\n        let current = self.headers_flush_duration.load();\n        self.headers_flush_duration.store(current + duration);\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/lib.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Bridging the gap between [quiche] and [tokio].\n//!\n//! tokio-quiche connects [quiche::Connection]s and [quiche::h3::Connection]s to\n//! tokio's event loop. Users have the choice between implementing their own,\n//! custom [`ApplicationOverQuic`] or using the ready-made\n//! [H3Driver](crate::http3::driver::H3Driver) for HTTP/3 clients and servers.\n//!\n//! # Starting an HTTP/3 Server\n//!\n//! A server [`listen`]s on a UDP socket for QUIC connections and spawns a new\n//! tokio task to handle each individual connection.\n//!\n//! ```\n//! use futures::stream::StreamExt;\n//! use tokio_quiche::http3::settings::Http3Settings;\n//! use tokio_quiche::listen;\n//! use tokio_quiche::metrics::DefaultMetrics;\n//! use tokio_quiche::quic::SimpleConnectionIdGenerator;\n//! use tokio_quiche::ConnectionParams;\n//! use tokio_quiche::ServerH3Driver;\n//!\n//! # async fn example() -> tokio_quiche::QuicResult<()> {\n//! let socket = tokio::net::UdpSocket::bind(\"0.0.0.0:443\").await?;\n//! let mut listeners =\n//!     listen([socket], ConnectionParams::default(), DefaultMetrics)?;\n//! let mut accept_stream = &mut listeners[0];\n//!\n//! while let Some(conn) = accept_stream.next().await {\n//!     let (driver, mut controller) =\n//!         ServerH3Driver::new(Http3Settings::default());\n//!     conn?.start(driver);\n//!\n//!     tokio::spawn(async move {\n//!         // `controller` is the handle to our established HTTP/3 connection.\n//!         // For example, inbound requests are available as H3Events via:\n//!         let event = controller.event_receiver_mut().recv().await;\n//!     });\n//! }\n//! # Ok(())\n//! # }\n//! ```\n//!\n//! For client-side use cases, check out our [`connect`](crate::quic::connect)\n//! API.\n//!\n//! # Feature Flags\n//!\n//! tokio-quiche supports a number of feature flags to enable experimental\n//! features, performance enhancements, and additional telemetry. By default, no\n//! feature flags are enabled.\n//!\n//! - `rpk`: Support for raw public keys (RPK) in QUIC handshakes (via\n//!   [boring]).\n//! - `gcongestion`: Replace quiche's original congestion control implementation\n//!   with one adapted from google/quiche.\n//! - `zero-copy`: Use zero-copy sends with quiche (implies `gcongestion`).\n//! - `perf-quic-listener-metrics`: Extra telemetry for QUIC handshake\n//!   durations, including protocol overhead and network delays.\n//! - `tokio-task-metrics`: Scheduling & poll duration histograms for tokio\n//!   tasks.\n//!\n//! Other parts of the crate are enabled by separate build flags instead, to be\n//! controlled by the final binary:\n//!\n//! - `--cfg capture_keylogs`: Optional `SSLKEYLOGFILE` capturing for QUIC\n//!   connections.\n\npub extern crate quiche;\n\npub mod buf_factory;\npub mod http3;\npub mod metrics;\npub mod quic;\nmod result;\npub mod settings;\npub mod socket;\n\npub use buffer_pool;\npub use datagram_socket;\n\nuse foundations::telemetry::settings::LogVerbosity;\nuse std::io;\nuse std::sync::Arc;\nuse std::sync::Once;\nuse tokio::net::UdpSocket;\nuse tokio_stream::wrappers::ReceiverStream;\n\nuse crate::metrics::Metrics;\nuse crate::socket::QuicListener;\n\npub use crate::http3::driver::ClientH3Controller;\npub use crate::http3::driver::ClientH3Driver;\npub use crate::http3::driver::ServerH3Controller;\npub use crate::http3::driver::ServerH3Driver;\npub use crate::http3::ClientH3Connection;\npub use crate::http3::ServerH3Connection;\npub use crate::quic::connection::ApplicationOverQuic;\npub use crate::quic::connection::ConnectionIdGenerator;\npub use crate::quic::connection::InitialQuicConnection;\npub use crate::quic::connection::QuicConnection;\npub use crate::result::BoxError;\npub use crate::result::QuicResult;\npub use crate::settings::ConnectionParams;\n\n#[doc(hidden)]\npub use crate::result::QuicResultExt;\n\n/// A stream of accepted [`InitialQuicConnection`]s from a [`listen`] call.\n///\n/// Errors from processing the client's QUIC initials can also be emitted on\n/// this stream. These do not indicate that the listener itself has failed.\npub type QuicConnectionStream<M> =\n    ReceiverStream<io::Result<InitialQuicConnection<UdpSocket, M>>>;\n\n/// Starts listening for inbound QUIC connections on the given\n/// [`QuicListener`]s.\n///\n/// Each socket starts a separate tokio task to process and route inbound\n/// packets. This task emits connections on the respective\n/// [`QuicConnectionStream`] after receiving the client's QUIC initial and\n/// (optionally) validating its IP address.\n///\n/// The task shuts down when the returned stream is closed (or dropped) and all\n/// previously-yielded connections are closed.\npub fn listen_with_capabilities<M>(\n    sockets: impl IntoIterator<Item = QuicListener>, params: ConnectionParams,\n    metrics: M,\n) -> io::Result<Vec<QuicConnectionStream<M>>>\nwhere\n    M: Metrics,\n{\n    if params.settings.capture_quiche_logs {\n        capture_quiche_logs();\n    }\n\n    sockets\n        .into_iter()\n        .map(|s| crate::quic::start_listener(s, &params, metrics.clone()))\n        .collect()\n}\n\n/// Starts listening for inbound QUIC connections on the given `sockets`.\n///\n/// Each socket is converted into a [`QuicListener`] with defaulted socket\n/// parameters. The listeners are then passed to [`listen_with_capabilities`].\npub fn listen<S, M>(\n    sockets: impl IntoIterator<Item = S>, params: ConnectionParams, metrics: M,\n) -> io::Result<Vec<QuicConnectionStream<M>>>\nwhere\n    S: TryInto<QuicListener, Error = io::Error>,\n    M: Metrics,\n{\n    let quic_sockets: Vec<QuicListener> = sockets\n        .into_iter()\n        .map(|s| {\n            #[cfg_attr(not(target_os = \"linux\"), expect(unused_mut))]\n            let mut socket = s.try_into()?;\n            #[cfg(target_os = \"linux\")]\n            socket.apply_max_capabilities();\n            Ok(socket)\n        })\n        .collect::<io::Result<_>>()?;\n\n    listen_with_capabilities(quic_sockets, params, metrics)\n}\n\nstatic GLOBAL_LOGGER_ONCE: Once = Once::new();\n\n/// Forward Quiche logs into the slog::Drain currently used by Foundations\n///\n/// # Warning\n///\n/// This should **only be used for local debugging**. Quiche can potentially\n/// emit lots (and lots, and lots) of logs (the TRACE level emits a log record\n/// on every packet and frame) and you can very easily overwhelm your logging\n/// pipeline.\n///\n/// # Note\n///\n/// Quiche uses the `env_logger` crate, which uses `log` under the hood. `log`\n/// requires that you only set the global logger once. That means that we have\n/// to register the logger at `listen()` time for servers - for clients, we\n/// should register loggers when the `quiche::Connection` is established.\npub(crate) fn capture_quiche_logs() {\n    GLOBAL_LOGGER_ONCE.call_once(|| {\n        use foundations::telemetry::log as foundations_log;\n        use log::Level as std_level;\n\n        let curr_logger =\n            Arc::clone(&foundations_log::slog_logger()).read().clone();\n        let scope_guard = slog_scope::set_global_logger(curr_logger);\n\n        // Convert slog::Level from Foundations settings to log::Level\n        let normalized_level = match foundations_log::verbosity() {\n            LogVerbosity::Critical | LogVerbosity::Error => std_level::Error,\n            LogVerbosity::Warning => std_level::Warn,\n            LogVerbosity::Info => std_level::Info,\n            LogVerbosity::Debug => std_level::Debug,\n            LogVerbosity::Trace => std_level::Trace,\n        };\n\n        slog_stdlog::init_with_level(normalized_level).unwrap();\n\n        // The slog Drain becomes `slog::Discard` when the scope_guard is dropped,\n        // and you can't set the global logger again because of a mandate\n        // in the `log` crate. We have to manually `forget` the scope\n        // guard so that the logger remains registered for the duration of the\n        // process.\n        std::mem::forget(scope_guard)\n    });\n}\n"
  },
  {
    "path": "tokio-quiche/src/metrics/labels.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Labels for crate metrics.\n\nuse serde::Serialize;\nuse serde::Serializer;\n\nuse crate::quic;\nuse crate::BoxError;\n\n/// Type of handshake latency that was measured by a metric.\n#[derive(Clone, Eq, Hash, PartialEq, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum QuicHandshakeStage {\n    // The time spent in kernel processing a packet plus the time waiting\n    // for the QUIC handler to be polled by tokio worker.\n    QueueWaiting,\n    // Time spent on protocol processing of a single handshake packet (not\n    // including queue waiting and scheduling delay of I/O worker to write\n    // data out)\n    HandshakeProtocol,\n    // Time between receiving a handshake in the kernel and flushing its\n    // response to the socket. Ideally we can ask kernel to report TX stamp,\n    // but right now tx latency is not a major source of problem, so we omit\n    // that.\n    HandshakeResponse,\n}\n\n/// Type of UDP [`send(2)`](https://man7.org/linux/man-pages/man2/send.2.html) error observed.\n#[derive(Clone, Eq, Hash, PartialEq, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum QuicWriteError {\n    Err,\n    Partial,\n    WouldBlock,\n}\n\n/// Category of error that caused the QUIC handshake to fail.\n#[derive(Clone, Eq, Hash, PartialEq, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum HandshakeError {\n    CryptoFail,\n    TlsFail,\n    Timeout,\n    Disconnect,\n    Other,\n}\n\nimpl From<&quiche::Error> for HandshakeError {\n    fn from(err: &quiche::Error) -> Self {\n        match err {\n            quiche::Error::CryptoFail => Self::CryptoFail,\n            quiche::Error::TlsFail => Self::TlsFail,\n            _ => Self::Other,\n        }\n    }\n}\n\nimpl From<&quic::HandshakeError> for HandshakeError {\n    fn from(err: &quic::HandshakeError) -> Self {\n        match err {\n            quic::HandshakeError::Timeout => Self::Timeout,\n            quic::HandshakeError::ConnectionClosed => Self::Disconnect,\n        }\n    }\n}\n\nimpl From<&BoxError> for HandshakeError {\n    fn from(err: &BoxError) -> Self {\n        if let Some(e) = err.downcast_ref::<quic::HandshakeError>() {\n            Self::from(e)\n        } else if let Some(e) = err.downcast_ref::<quiche::Error>() {\n            Self::from(e)\n        } else {\n            Self::Other\n        }\n    }\n}\n\n/// Reason why a QUIC Initial was discarded by the packet router.\n#[derive(Clone, Debug, Eq, PartialEq)]\npub enum QuicInvalidInitialPacketError {\n    TokenValidationFail,\n    FailedToParse,\n    WrongType(quiche::Type),\n    AcceptQueueOverflow,\n    Unexpected,\n}\n\nimpl std::fmt::Display for QuicInvalidInitialPacketError {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        match self {\n            Self::FailedToParse => f.write_str(\"failed to parse packet\"),\n            Self::TokenValidationFail => f.write_str(\"token validation fail\"),\n            Self::WrongType(ty) => write!(f, \"wrong type: {ty:?}\"),\n            Self::AcceptQueueOverflow => f.write_str(\"accept queue overflow\"),\n            Self::Unexpected => f.write_str(\"unexpected error\"),\n        }\n    }\n}\n\nimpl Serialize for QuicInvalidInitialPacketError {\n    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        serializer.collect_str(self)\n    }\n}\n\nimpl std::hash::Hash for QuicInvalidInitialPacketError {\n    fn hash<H>(&self, state: &mut H)\n    where\n        H: std::hash::Hasher,\n    {\n        std::mem::discriminant(self).hash(state);\n        if let Self::WrongType(ty) = self {\n            std::mem::discriminant(ty).hash(state);\n        }\n    }\n}\n\nimpl std::error::Error for QuicInvalidInitialPacketError {}\n\nimpl From<QuicInvalidInitialPacketError> for std::io::Error {\n    fn from(e: QuicInvalidInitialPacketError) -> Self {\n        std::io::Error::other(e)\n    }\n}\n\n/// HTTP/3 error code (from IANA registry).\n#[derive(Clone, Eq, Hash, PartialEq)]\npub struct H3Error(u64);\n\nimpl Serialize for H3Error {\n    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        let code = self.0;\n\n        // https://www.iana.org/assignments/http3-parameters/http3-parameters.xhtml\n        let v = if code > 0x21 && (code - 0x21) % 0x1f == 0 {\n            \"H3_GREASE\"\n        } else {\n            match code {\n                0x33 => \"H3_DATAGRAM_ERROR\",\n\n                0x100 => \"H3_NO_ERROR\",\n                0x101 => \"H3_GENERAL_PROTOCOL_ERROR\",\n                0x102 => \"H3_INTERNAL_ERROR\",\n                0x103 => \"H3_STREAM_CREATION_ERROR\",\n                0x104 => \"H3_CLOSED_CRITICAL_STREAM\",\n                0x105 => \"H3_FRAME_UNEXPECTED\",\n                0x106 => \"H3_FRAME_ERROR\",\n                0x107 => \"H3_EXCESSIVE_LOAD\",\n                0x108 => \"H3_ID_ERROR\",\n                0x109 => \"H3_SETTINGS_ERROR\",\n                0x10a => \"H3_MISSING_SETTINGS\",\n                0x10b => \"H3_REQUEST_REJECTED\",\n                0x10c => \"H3_REQUEST_CANCELLED\",\n                0x10d => \"H3_REQUEST_INCOMPLETE\",\n                0x10e => \"H3_MESSAGE_ERROR\",\n                0x10f => \"H3_CONNECT_ERROR\",\n                0x110 => \"H3_VERSION_FALLBACK\",\n\n                0x200 => \"QPACK_DECOMPRESSION_FAILED\",\n                0x201 => \"QPACK_ENCODER_STREAM_ERROR\",\n                0x202 => \"QPACK_DECODER_STREAM_ERROR\",\n\n                _ => \"H3_UNKNOWN\",\n            }\n        };\n        serializer.serialize_str(v)\n    }\n}\n\nimpl From<u64> for H3Error {\n    fn from(code: u64) -> Self {\n        Self(code)\n    }\n}\n\n/// QUIC error code (from IANA registry).\n#[derive(Clone, Eq, Hash, PartialEq)]\npub struct QuicError(u64);\n\nimpl Serialize for QuicError {\n    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {\n        // https://www.iana.org/assignments/quic/quic.xhtml\n        let v = match self.0 {\n            0x0 => \"NO_ERROR\",\n            0x1 => \"INTERNAL_ERROR\",\n            0x2 => \"CONNECTION_REFUSED\",\n            0x3 => \"FLOW_CONTROL_ERROR\",\n            0x4 => \"STREAM_LIMIT_ERROR\",\n            0x5 => \"STREAM_STATE_ERROR\",\n            0x6 => \"FINAL_SIZE_ERROR\",\n            0x7 => \"FRAME_ENCODING_ERROR\",\n            0x8 => \"TRANSPORT_PARAMETER_ERROR\",\n            0x9 => \"CONNECTION_ID_LIMIT_ERROR\",\n            0xa => \"PROTOCOL_VIOLATION\",\n            0xb => \"INVALID_TOKEN\",\n            0xc => \"APPLICATION_ERROR\",\n            0xd => \"CRYPTO_BUFFER_EXCEEDED\",\n            0xe => \"KEY_UPDATE_ERROR\",\n            0xf => \"AEAD_LIMIT_REACHED\",\n            0x10 => \"NO_VIABLE_PATH\",\n            0x11 => \"VERSION_NEGOTIATION_ERROR\",\n            0x100..=0x1ff => \"CRYPTO_ERROR\",\n\n            _ => \"QUIC_UNKNOWN\",\n        };\n        serializer.serialize_str(v)\n    }\n}\n\nimpl From<u64> for QuicError {\n    fn from(code: u64) -> Self {\n        Self(code)\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/metrics/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Metrics collected across QUIC connections.\n\npub mod labels;\npub mod tokio_task;\n\nuse foundations::telemetry::metrics::metrics;\nuse foundations::telemetry::metrics::Counter;\nuse foundations::telemetry::metrics::Gauge;\nuse foundations::telemetry::metrics::Histogram;\nuse foundations::telemetry::metrics::HistogramBuilder;\nuse foundations::telemetry::metrics::TimeHistogram;\nuse std::net::IpAddr;\nuse std::sync::Arc;\n\n/// Trait to direct the metrics emitted by the crate to a Prometheus registry.\npub trait Metrics: Send + Sync + Clone + Unpin + 'static {\n    /// Number of QUIC connections currently in memory\n    fn connections_in_memory(&self) -> Gauge;\n\n    /// Maximum number of writable QUIC streams in a connection\n    fn maximum_writable_streams(&self) -> Histogram;\n\n    /// Overhead of QUIC handshake processing stage\n    fn handshake_time_seconds(\n        &self, stage: labels::QuicHandshakeStage,\n    ) -> TimeHistogram;\n\n    /// Number of error and partial writes while sending QUIC packets\n    fn write_errors(&self, reason: labels::QuicWriteError) -> Counter;\n\n    /// Record timing information from sendmsg calls that return\n    /// WouldBlock and are retried in a loop.\n    fn send_to_wouldblock_duration_s(&self) -> TimeHistogram;\n\n    /// Number of mid-handshake flush operations that were skipped due to future\n    /// cancellation.\n    fn skipped_mid_handshake_flush_count(&self) -> Counter;\n\n    /// Number of QUIC packets received where the CID could not be verified.\n    fn invalid_cid_packet_count(&self, reason: crate::BoxError) -> Counter;\n\n    /// Number of accepted QUIC Initial packets\n    fn accepted_initial_packet_count(&self) -> Counter;\n\n    /// Number of accepted QUIC Initial packets using expensive label(s)\n    fn expensive_accepted_initial_packet_count(&self, peer_ip: IpAddr)\n        -> Counter;\n\n    /// Number of QUIC packets received but not associated with an active\n    /// connection\n    fn rejected_initial_packet_count(\n        &self, reason: labels::QuicInvalidInitialPacketError,\n    ) -> Counter;\n\n    /// Number of QUIC packets received but not associated with an active\n    /// connection using expensive label(s)\n    fn expensive_rejected_initial_packet_count(\n        &self, reason: labels::QuicInvalidInitialPacketError, peer_ip: IpAddr,\n    ) -> Counter;\n\n    /// Combined utilized bandwidth of all open connections (max over the past\n    /// two minutes)\n    fn utilized_bandwidth(&self) -> Gauge;\n\n    /// The highest utilized bandwidh reported during the lifetime of the\n    /// connection\n    fn max_bandwidth_mbps(&self) -> Histogram;\n\n    /// The highest momentary loss reported during the lifetime of the\n    /// connection\n    fn max_loss_pct(&self) -> Histogram;\n\n    /// Number of UDP packets dropped when receiving\n    fn udp_drop_count(&self) -> Counter;\n\n    /// Number of failed quic handshakes\n    fn failed_handshakes(&self, reason: labels::HandshakeError) -> Counter;\n\n    /// Number of HTTP/3 connection closures generated locally\n    fn local_h3_conn_close_error_count(&self, reason: labels::H3Error)\n        -> Counter;\n\n    /// Number of QUIC connection closures generated locally\n    fn local_quic_conn_close_error_count(\n        &self, reason: labels::QuicError,\n    ) -> Counter;\n\n    /// Number of HTTP/3 connection closures generated by peer\n    fn peer_h3_conn_close_error_count(&self, reason: labels::H3Error) -> Counter;\n\n    /// Number of QUIC connection closures generated by peer\n    fn peer_quic_conn_close_error_count(\n        &self, reason: labels::QuicError,\n    ) -> Counter;\n\n    // ==== tokio runtime metrics ====\n\n    /// Histogram of task schedule delays\n    fn tokio_runtime_task_schedule_delay_histogram(\n        &self, task: &Arc<str>,\n    ) -> TimeHistogram;\n\n    /// Histogram of task poll durations\n    fn tokio_runtime_task_poll_duration_histogram(\n        &self, task: &Arc<str>,\n    ) -> TimeHistogram;\n\n    /// Helps us get a rough idea of if our waker is causing issues.\n    fn tokio_runtime_task_total_poll_time_micros(\n        &self, task: &Arc<str>,\n    ) -> Counter;\n}\n\n/// Standard implementation of [`Metrics`] using\n/// [`foundations::telemetry::metrics`].\n#[derive(Default, Clone)]\npub struct DefaultMetrics;\n\nimpl Metrics for DefaultMetrics {\n    fn connections_in_memory(&self) -> Gauge {\n        quic::connections_in_memory()\n    }\n\n    fn maximum_writable_streams(&self) -> Histogram {\n        quic::maximum_writable_streams()\n    }\n\n    fn handshake_time_seconds(\n        &self, stage: labels::QuicHandshakeStage,\n    ) -> TimeHistogram {\n        quic::handshake_time_seconds(stage)\n    }\n\n    fn write_errors(&self, reason: labels::QuicWriteError) -> Counter {\n        quic::write_errors(reason)\n    }\n\n    fn send_to_wouldblock_duration_s(&self) -> TimeHistogram {\n        quic::send_to_wouldblock_duration_s()\n    }\n\n    fn skipped_mid_handshake_flush_count(&self) -> Counter {\n        quic::skipped_mid_handshake_flush_count()\n    }\n\n    fn invalid_cid_packet_count(&self, reason: crate::BoxError) -> Counter {\n        quic::invalid_cid_packet_count(reason.to_string())\n    }\n\n    fn accepted_initial_packet_count(&self) -> Counter {\n        quic::accepted_initial_packet_count()\n    }\n\n    fn expensive_accepted_initial_packet_count(\n        &self, peer_ip: IpAddr,\n    ) -> Counter {\n        quic::expensive_accepted_initial_packet_count(peer_ip)\n    }\n\n    fn rejected_initial_packet_count(\n        &self, reason: labels::QuicInvalidInitialPacketError,\n    ) -> Counter {\n        quic::rejected_initial_packet_count(reason)\n    }\n\n    fn expensive_rejected_initial_packet_count(\n        &self, reason: labels::QuicInvalidInitialPacketError, peer_ip: IpAddr,\n    ) -> Counter {\n        quic::expensive_rejected_initial_packet_count(reason, peer_ip)\n    }\n\n    fn utilized_bandwidth(&self) -> Gauge {\n        quic::utilized_bandwidth()\n    }\n\n    fn max_bandwidth_mbps(&self) -> Histogram {\n        quic::max_bandwidth_mbps()\n    }\n\n    fn max_loss_pct(&self) -> Histogram {\n        quic::max_loss_pct()\n    }\n\n    fn udp_drop_count(&self) -> Counter {\n        quic::udp_drop_count()\n    }\n\n    fn failed_handshakes(&self, reason: labels::HandshakeError) -> Counter {\n        quic::failed_handshakes(reason)\n    }\n\n    fn local_h3_conn_close_error_count(\n        &self, reason: labels::H3Error,\n    ) -> Counter {\n        quic::local_h3_conn_close_error_count(reason)\n    }\n\n    fn local_quic_conn_close_error_count(\n        &self, reason: labels::QuicError,\n    ) -> Counter {\n        quic::local_quic_conn_close_error_count(reason)\n    }\n\n    fn peer_h3_conn_close_error_count(&self, reason: labels::H3Error) -> Counter {\n        quic::peer_h3_conn_close_error_count(reason)\n    }\n\n    fn peer_quic_conn_close_error_count(\n        &self, reason: labels::QuicError,\n    ) -> Counter {\n        quic::peer_quic_conn_close_error_count(reason)\n    }\n\n    // ==== tokio runtime metrics ====\n\n    /// Histogram of task schedule delays\n    fn tokio_runtime_task_schedule_delay_histogram(\n        &self, task: &Arc<str>,\n    ) -> TimeHistogram {\n        tokio::runtime_task_schedule_delay_histogram(task)\n    }\n\n    /// Histogram of task poll durations\n    fn tokio_runtime_task_poll_duration_histogram(\n        &self, task: &Arc<str>,\n    ) -> TimeHistogram {\n        tokio::runtime_task_poll_duration_histogram(task)\n    }\n\n    /// Helps us get a rough idea of if our waker is causing issues.\n    fn tokio_runtime_task_total_poll_time_micros(\n        &self, task: &Arc<str>,\n    ) -> Counter {\n        tokio::runtime_task_total_poll_time_micros(task)\n    }\n}\n\n#[metrics]\npub(crate) mod quic {\n    /// Number of QUIC connections currently in memory\n    pub fn connections_in_memory() -> Gauge;\n\n    /// Maximum number of writable QUIC streams in a connection\n    #[optional]\n    #[ctor = HistogramBuilder { buckets: &[0.0, 5.0, 10.0, 100.0, 1000.0, 2000.0, 3000.0, 10000.0, 20000.0, 50000.0], }]\n    pub fn maximum_writable_streams() -> Histogram;\n\n    /// Overhead of QUIC handshake processing stage\n    #[ctor = HistogramBuilder { buckets: &[1E-5, 2E-5, 5E-5, 1E-4, 2E-4, 5E-4, 1E-3, 2E-3, 5E-3, 1E-2, 2E-2, 5E-2, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0], }]\n    pub fn handshake_time_seconds(\n        stage: labels::QuicHandshakeStage,\n    ) -> TimeHistogram;\n\n    /// Number of error and partial writes while sending QUIC packets\n    pub fn write_errors(reason: labels::QuicWriteError) -> Counter;\n\n    /// Record timing information from sendmsg calls that return\n    /// WouldBlock and are retried in a loop.\n    #[ctor = HistogramBuilder { buckets: &[1E-6, 1E-5, 1E-4, 1E-3, 5E-3, 1E-2, 2E-2, 4E-2, 8E-2, 16E-2, 1.0], }]\n    pub fn send_to_wouldblock_duration_s() -> TimeHistogram;\n\n    /// Number of mid-handshake flush operations that were skipped due to future\n    /// cancellation.\n    pub fn skipped_mid_handshake_flush_count() -> Counter;\n\n    /// Number of QUIC packets received where the CID could not be verified.\n    pub fn invalid_cid_packet_count(reason: String) -> Counter;\n\n    /// Number of accepted QUIC Initial packets\n    pub fn accepted_initial_packet_count() -> Counter;\n\n    /// Number of accepted QUIC Initial packets using expensive label(s)\n    #[optional]\n    pub fn expensive_accepted_initial_packet_count(peer_ip: IpAddr) -> Counter;\n\n    /// Number of QUIC packets received but not associated with an active\n    /// connection\n    pub fn rejected_initial_packet_count(\n        reason: labels::QuicInvalidInitialPacketError,\n    ) -> Counter;\n\n    /// Number of QUIC packets received but not associated with an active\n    /// connection using expensive label(s)\n    #[optional]\n    pub fn expensive_rejected_initial_packet_count(\n        reason: labels::QuicInvalidInitialPacketError, peer_ip: IpAddr,\n    ) -> Counter;\n\n    /// Combined utilized bandwidth of all open connections (max over the past\n    /// two minutes)\n    pub fn utilized_bandwidth() -> Gauge;\n\n    /// The highest utilized bandwidh reported during the lifetime of the\n    /// connection\n    #[ctor = HistogramBuilder { buckets: &[0., 1., 2., 5., 10., 20., 50., 100., 200., 300., 500., 750., 1000., 1500., 2000., 2500., 3000., 3500., 4000., 4500., 5000., 6000., 7000., 10000.], }]\n    pub fn max_bandwidth_mbps() -> Histogram;\n\n    /// The highest momentary loss reported during the lifetime of the\n    /// connection\n    #[ctor = HistogramBuilder { buckets: &[0.0, 0.1, 0.2, 0.5, 1., 2., 3., 4., 5., 10., 15., 20., 25., 50., 100.], }]\n    pub fn max_loss_pct() -> Histogram;\n\n    /// Number of UDP packets dropped when receiving\n    pub fn udp_drop_count() -> Counter;\n\n    /// Number of failed quic handshakes\n    pub fn failed_handshakes(reason: labels::HandshakeError) -> Counter;\n\n    /// Number of HTTP/3 connection closures generated locally\n    pub fn local_h3_conn_close_error_count(reason: labels::H3Error) -> Counter;\n\n    /// Number of QUIC connection closures generated locally\n    pub fn local_quic_conn_close_error_count(\n        reason: labels::QuicError,\n    ) -> Counter;\n\n    /// Number of HTTP/3 connection closures generated by peer\n    pub fn peer_h3_conn_close_error_count(reason: labels::H3Error) -> Counter;\n\n    /// Number of QUIC connection closures generated by peer\n    pub fn peer_quic_conn_close_error_count(reason: labels::QuicError)\n        -> Counter;\n}\n\n#[metrics]\nmod tokio {\n    /// Histogram of task schedule delays\n    #[ctor = HistogramBuilder { buckets: &[0.0, 1E-4, 2E-4, 3E-4, 4E-4, 5E-4, 6E-4, 7E-4, 8E-4, 9E-4, 1E-3, 1E-2, 2E-2, 4E-2, 8E-2, 1E-1, 1.0], }]\n    pub fn runtime_task_schedule_delay_histogram(\n        task: &Arc<str>,\n    ) -> TimeHistogram;\n\n    /// Histogram of task poll durations\n    #[ctor = HistogramBuilder { buckets: &[0.0, 1E-4, 2E-4, 3E-4, 4E-4, 5E-4, 6E-4, 7E-4, 8E-4, 9E-4, 1E-3, 1E-2, 2E-2, 4E-2, 8E-2, 1E-1, 1.0], }]\n    pub fn runtime_task_poll_duration_histogram(task: &Arc<str>)\n        -> TimeHistogram;\n\n    /// Helps us get a rough idea of if our waker is causing issues.\n    pub fn runtime_task_total_poll_time_micros(task: &Arc<str>) -> Counter;\n}\n\npub(crate) fn quic_expensive_metrics_ip_reduce(ip: IpAddr) -> Option<IpAddr> {\n    const QUIC_INITIAL_METRICS_V4_PREFIX: u8 = 20;\n    const QUIC_INITIAL_METRICS_V6_PREFIX: u8 = 32;\n\n    let prefix = if ip.is_ipv4() {\n        QUIC_INITIAL_METRICS_V4_PREFIX\n    } else {\n        QUIC_INITIAL_METRICS_V6_PREFIX\n    };\n\n    if let Ok(ip_net) = ipnetwork::IpNetwork::new(ip, prefix) {\n        Some(ip_net.network())\n    } else {\n        None\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/metrics/tokio_task.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Instrumentation and metrics for spawned tokio tasks.\n//!\n//! Currently, this is implemented by creating wrapper futures and wakers for\n//! the future inside of a spawned task. Ideally we would be able to move at\n//! least some of this work into tokio proper at some point, but this should be\n//! sufficient for now.\n//!\n//! This does *not* rely on the tokio-metrics crate, as that has more overhead\n//! than we would like.\n\nuse crate::metrics::Metrics;\nuse foundations::telemetry::TelemetryContext;\nuse pin_project::pin_project;\nuse std::future::Future;\nuse std::pin::Pin;\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse std::task::Context;\nuse std::task::Poll;\nuse std::task::Wake;\nuse std::task::Waker;\nuse std::time::Instant;\nuse task_killswitch::spawn_with_killswitch as killswitch_spawn;\nuse tokio::task::JoinHandle;\n\n/// An instrumented future.\n///\n/// It's important to keep overhead low here, especially where contention is\n/// concerned.\n#[pin_project]\nstruct Instrumented<F, M> {\n    #[pin]\n    future: F,\n    name: Arc<str>,\n    timer: Arc<Mutex<Option<Instant>>>,\n    metrics: M,\n}\n\n/// An instrumented waker for our instrumented future.\n///\n/// It's very important to keep overhead low here, especially where contention\n/// is concerned.\nstruct InstrumentedWaker {\n    timer: Arc<Mutex<Option<Instant>>>,\n    waker: Waker,\n}\n\nimpl Wake for InstrumentedWaker {\n    fn wake(self: Arc<Self>) {\n        self.wake_by_ref()\n    }\n\n    fn wake_by_ref(self: &Arc<Self>) {\n        // let's scope the guard's lifespan in case the inner waker is slow\n        // this is still highly unlikely to be contended ever\n        {\n            let mut guard = self.timer.lock().unwrap();\n\n            if guard.is_none() {\n                *guard = Some(Instant::now())\n            }\n        }\n\n        self.waker.wake_by_ref();\n    }\n}\n\nimpl<F, M> Instrumented<F, M>\nwhere\n    M: Metrics,\n{\n    fn new(name: &str, metrics: M, future: F) -> Self {\n        let name = Arc::from(name);\n\n        Self {\n            future,\n            name,\n            metrics,\n            timer: Arc::new(Mutex::new(Some(Instant::now()))),\n        }\n    }\n}\n\nimpl<F: Future, M: Metrics> Future for Instrumented<F, M> {\n    type Output = F::Output;\n\n    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n        let total_timer = Instant::now();\n\n        // if we were to hold the lock over the poll boundary, self-wakes would\n        // deadlock us, so we won't do that.\n        //\n        // this is unlikely to be contended much otherwise.\n        let maybe_schedule_timer = self.timer.lock().unwrap().take();\n\n        // for various reasons related to how rust does lifetime things, we will\n        // not acquire the lock in the if statement\n        if let Some(schedule_timer) = maybe_schedule_timer {\n            let elapsed = schedule_timer.elapsed();\n\n            self.metrics\n                .tokio_runtime_task_schedule_delay_histogram(&self.name)\n                .observe(elapsed.as_nanos() as u64);\n        }\n\n        let projected = self.project();\n\n        let waker = Waker::from(Arc::new(InstrumentedWaker {\n            timer: Arc::clone(projected.timer),\n            waker: cx.waker().clone(),\n        }));\n\n        let mut new_cx = Context::from_waker(&waker);\n\n        let timer = Instant::now();\n\n        let output = projected.future.poll(&mut new_cx);\n\n        let elapsed = timer.elapsed();\n\n        projected\n            .metrics\n            .tokio_runtime_task_poll_duration_histogram(projected.name)\n            .observe(elapsed.as_nanos() as u64);\n\n        let total_elapsed = total_timer.elapsed();\n\n        projected\n            .metrics\n            .tokio_runtime_task_total_poll_time_micros(projected.name)\n            .inc_by(total_elapsed.as_micros() as u64);\n\n        output\n    }\n}\n\n/// Spawn a potentially instrumented task.\n///\n/// Depending on whether the `tokio-task-metrics` feature is enabled, this may\n/// instrument the task and collect metrics for it.\n#[track_caller]\npub fn spawn<M, T>(name: &str, metrics: M, future: T) -> JoinHandle<T::Output>\nwhere\n    T: Future + Send + 'static,\n    T::Output: Send + 'static,\n    M: Metrics,\n{\n    let ctx = TelemetryContext::current();\n\n    if cfg!(feature = \"tokio-task-metrics\") {\n        tokio::spawn(Instrumented::new(name, metrics, ctx.apply(future)))\n    } else {\n        tokio::spawn(ctx.apply(future))\n    }\n}\n\n/// Spawn a potentially instrumented, long-lived task. Integrates with\n/// [task-killswitch](task_killswitch).\n///\n/// Depending on whether the `tokio-task-metrics` feature is enabled, this may\n/// instrument the task and collect metrics for it.\n#[track_caller]\npub fn spawn_with_killswitch<M, T>(name: &str, metrics: M, future: T)\nwhere\n    T: Future<Output = ()> + Send + 'static,\n    M: Metrics,\n{\n    let ctx = TelemetryContext::current();\n\n    if cfg!(feature = \"tokio-task-metrics\") {\n        killswitch_spawn(Instrumented::new(name, metrics, ctx.apply(future)));\n    } else {\n        killswitch_spawn(ctx.apply(future));\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/AGENTS.md",
    "content": "# tokio-quiche/src/quic/\n\n## OVERVIEW\n\nAsync QUIC connection management. Splits socket into recv-half (one `InboundPacketRouter` task) and send-half (shared by many `IoWorker` tasks). Entrypoints: `connect()`/`connect_with_config()` for clients, `start_listener()` for servers. `raw` submodule bypasses the router for manual packet injection.\n\n## STRUCTURE\n\n```\nmod.rs                        # Entrypoints: connect, connect_with_config, start_listener\nraw.rs                        # wrap_quiche_conn(): bypass router, manual packet feed\nhooks.rs                      # ConnectionHook trait (custom SslContextBuilder)\naddr_validation_token.rs      # RETRY token generation/validation for server\n\nconnection/\n  mod.rs                      # InitialQuicConnection, QuicConnection, ApplicationOverQuic trait\n  error.rs                    # HandshakeError enum, make_handshake_result()\n  id.rs                       # ConnectionIdGenerator trait, SimpleConnectionIdGenerator\n  map.rs                      # ConnectionMap: BTreeMap<CidOwned, mpsc::Sender<Incoming>>\n\nio/\n  connection_stage.rs         # ConnectionStage trait + stages: Handshake, RunningApplication, Close\n  worker.rs                   # IoWorker<Tx,M,S>: per-connection recv→process→send loop\n  gso.rs                      # GSO/GRO send_to(), UDP_MAX_GSO_PACKET_SIZE\n  utilization_estimator.rs    # BandwidthReporter for max bandwidth/loss tracking\n\nrouter/\n  mod.rs                      # InboundPacketRouter: Future, demux by DCID, ConnectionMapCommand\n  acceptor.rs                 # ConnectionAcceptor: server-side InitialPacketHandler (RETRY flow)\n  connector.rs                # ClientConnector: client-side InitialPacketHandler (handshake FSM)\n```\n\n## WHERE TO LOOK\n\n| Task | File | Symbol/Line |\n|------|------|-------------|\n| Add lifecycle callback | `connection/mod.rs` | `ApplicationOverQuic` trait (~:663) |\n| Connection state machine | `io/connection_stage.rs` | `ConnectionStage` trait, `Handshake`/`RunningApplication`/`Close` |\n| Worker main loop | `io/worker.rs` | `IoWorker::work_loop()` (~:216) |\n| Packet routing/demux | `router/mod.rs` | `InboundPacketRouter::on_incoming()` (~:229) |\n| CID management | `io/worker.rs` | `fill_available_scids()`, `refresh_connection_ids()` |\n| Server accept flow | `router/acceptor.rs` | `ConnectionAcceptor::handle_initials()` |\n| Client connect flow | `router/connector.rs` | `ClientConnector::on_incoming()` |\n| Connection spawn path | `connection/mod.rs` | `InitialQuicConnection::start()` / `handshake()` / `resume()` |\n| CID-to-connection map | `connection/map.rs` | `ConnectionMap` (optimized `CidOwned` for v1 CIDs) |\n| Raw/manual connections | `raw.rs` | `wrap_quiche_conn()`, `ConnCloseReceiver` |\n\n## ANTI-PATTERNS\n\n- **`InitialQuicConnection` is `#[must_use]`** -- dropping silently discards connection. Always call `.start()`, `.handshake()`, or `.handshake_fut()`.\n- **`handshake()` spawns with `AbortOnDropHandle`** -- dropping the future kills the handshake task. Hold the handle.\n- **`fill_available_scids` sends `ConnectionMapCommand::MapCid` to router** -- silently fails if router channel dropped. Don't assume CID registration succeeded.\n- **`transmute` of `Instant` in `gso.rs`** -- fragile platform-dependent hack. Do not extend or replicate.\n- **One client connection per socket** -- `connect()` docs explicitly warn. Sharing socket loses packets.\n- **`#[cfg(feature = \"zero-copy\")]` gates `QuicheConnection` type alias** -- `quiche::Connection<BufFactory>` vs `quiche::Connection`. Check both paths when modifying connection creation.\n- **`wait_for_quiche` returns `TlsFail` on gather error** -- misleading sentinel error, don't propagate this pattern.\n\n## NOTES\n\n- `IoWorker` is generic: `IoWorker<Tx, M, S: ConnectionStage>`. Stage transitions consume the worker via `From<IoWorker<..>> for IoWorkerParams<..>` and construct a new `IoWorker` with the next stage.\n- `select!` in `work_loop` is **biased** -- timeout arm must stay first to prevent starvation.\n- `ConnectionMap` uses `CidOwned::Optimized([u64; 3])` for v1 CIDs (<=20 bytes) to avoid heap allocation on lookup.\n- `InboundPacketRouter` implements `Future` directly (not async fn) -- polled as a spawned task.\n- `short_dcid()` fast-path extracts DCID from short header packets without full `Header::from_slice`.\n- Router tests are `#[cfg(all(test, unix))]` in `router/mod.rs` -- not Windows-compatible.\n"
  },
  {
    "path": "tokio-quiche/src/quic/addr_validation_token.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse quiche::ConnectionId;\nuse std::io::Write;\nuse std::io::{\n    self,\n};\nuse std::net::IpAddr;\nuse std::net::SocketAddr;\n\nuse crate::QuicResultExt;\n\nconst HMAC_KEY_LEN: usize = 32;\nconst HMAC_TAG_LEN: usize = 32;\n\npub(crate) struct AddrValidationTokenManager {\n    sign_key: [u8; HMAC_KEY_LEN],\n}\n\nimpl Default for AddrValidationTokenManager {\n    fn default() -> Self {\n        let mut key_bytes = [0; HMAC_KEY_LEN];\n        boring::rand::rand_bytes(&mut key_bytes).unwrap();\n\n        AddrValidationTokenManager {\n            sign_key: key_bytes,\n        }\n    }\n}\n\nimpl AddrValidationTokenManager {\n    pub(super) fn gen(\n        &self, original_dcid: &[u8], client_addr: SocketAddr,\n    ) -> Vec<u8> {\n        let ip_bytes = match client_addr.ip() {\n            IpAddr::V4(addr) => addr.octets().to_vec(),\n            IpAddr::V6(addr) => addr.octets().to_vec(),\n        };\n\n        let token_len = HMAC_TAG_LEN + ip_bytes.len() + original_dcid.len();\n        let mut token = io::Cursor::new(vec![0u8; token_len]);\n\n        token.set_position(HMAC_TAG_LEN as u64);\n        token.write_all(&ip_bytes).unwrap();\n        token.write_all(original_dcid).unwrap();\n\n        let tag = boring::hash::hmac_sha256(\n            &self.sign_key,\n            &token.get_ref()[HMAC_TAG_LEN..],\n        )\n        .unwrap();\n\n        token.set_position(0);\n        token.write_all(tag.as_ref()).unwrap();\n\n        token.into_inner()\n    }\n\n    pub(super) fn validate_and_extract_original_dcid<'t>(\n        &self, token: &'t [u8], client_addr: SocketAddr,\n    ) -> io::Result<ConnectionId<'t>> {\n        let ip_bytes = match client_addr.ip() {\n            IpAddr::V4(addr) => addr.octets().to_vec(),\n            IpAddr::V6(addr) => addr.octets().to_vec(),\n        };\n\n        let hmac_and_ip_len = HMAC_TAG_LEN + ip_bytes.len();\n\n        if token.len() < hmac_and_ip_len {\n            return Err(\"token is too short\").into_io();\n        }\n\n        let (tag, payload) = token.split_at(HMAC_TAG_LEN);\n\n        let expected_tag =\n            boring::hash::hmac_sha256(&self.sign_key, payload).unwrap();\n\n        if !boring::memcmp::eq(&expected_tag, tag) {\n            return Err(\"signature verification failed\").into_io();\n        }\n\n        if payload[..ip_bytes.len()] != *ip_bytes {\n            return Err(\"IPs don't match\").into_io();\n        }\n\n        Ok(ConnectionId::from_ref(&token[hmac_and_ip_len..]))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn generate() {\n        let manager = AddrValidationTokenManager::default();\n\n        let assert_tag_generated = |token: &[u8]| {\n            let tag = &token[..HMAC_TAG_LEN];\n            let all_nulls = tag.iter().all(|b| *b == 0u8);\n\n            assert!(!all_nulls);\n        };\n\n        let token = manager.gen(b\"foo\", \"127.0.0.1:1337\".parse().unwrap());\n\n        assert_tag_generated(&token);\n        assert_eq!(token[HMAC_TAG_LEN..HMAC_TAG_LEN + 4], [127, 0, 0, 1]);\n        assert_eq!(&token[HMAC_TAG_LEN + 4..], b\"foo\");\n\n        let token = manager.gen(b\"bar\", \"[::1]:1338\".parse().unwrap());\n\n        assert_tag_generated(&token);\n\n        assert_eq!(token[HMAC_TAG_LEN..HMAC_TAG_LEN + 16], [\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1\n        ]);\n\n        assert_eq!(&token[HMAC_TAG_LEN + 16..], b\"bar\");\n    }\n\n    #[test]\n    fn validate() {\n        let manager = AddrValidationTokenManager::default();\n\n        let addr = \"127.0.0.1:1337\".parse().unwrap();\n        let token = manager.gen(b\"foo\", addr);\n\n        assert_eq!(\n            manager\n                .validate_and_extract_original_dcid(&token, addr)\n                .unwrap(),\n            ConnectionId::from_ref(b\"foo\")\n        );\n\n        let addr = \"[::1]:1338\".parse().unwrap();\n        let token = manager.gen(b\"barbaz\", addr);\n\n        assert_eq!(\n            manager\n                .validate_and_extract_original_dcid(&token, addr)\n                .unwrap(),\n            ConnectionId::from_ref(b\"barbaz\")\n        );\n    }\n\n    #[test]\n    fn validate_err_short_token() {\n        let manager = AddrValidationTokenManager::default();\n        let v4_addr = \"127.0.0.1:1337\".parse().unwrap();\n        let v6_addr = \"[::1]:1338\".parse().unwrap();\n\n        for addr in &[v4_addr, v6_addr] {\n            assert!(manager\n                .validate_and_extract_original_dcid(b\"\", *addr)\n                .is_err());\n\n            assert!(manager\n                .validate_and_extract_original_dcid(&[1u8; HMAC_TAG_LEN], *addr)\n                .is_err());\n\n            assert!(manager\n                .validate_and_extract_original_dcid(\n                    &[1u8; HMAC_TAG_LEN + 1],\n                    *addr\n                )\n                .is_err());\n        }\n    }\n\n    #[test]\n    fn validate_err_ips_mismatch() {\n        let manager = AddrValidationTokenManager::default();\n\n        let token = manager.gen(b\"foo\", \"127.0.0.1:1337\".parse().unwrap());\n\n        assert!(manager\n            .validate_and_extract_original_dcid(\n                &token,\n                \"127.0.0.2:1337\".parse().unwrap()\n            )\n            .is_err());\n\n        let token = manager.gen(b\"barbaz\", \"[::1]:1338\".parse().unwrap());\n\n        assert!(manager\n            .validate_and_extract_original_dcid(\n                &token,\n                \"[::2]:1338\".parse().unwrap()\n            )\n            .is_err());\n    }\n\n    #[test]\n    fn validate_err_invalid_signature() {\n        let manager = AddrValidationTokenManager::default();\n\n        let addr = \"127.0.0.1:1337\".parse().unwrap();\n        let mut token = manager.gen(b\"foo\", addr);\n\n        token[..HMAC_TAG_LEN].copy_from_slice(&[1u8; HMAC_TAG_LEN]);\n\n        assert!(manager\n            .validate_and_extract_original_dcid(&token, addr)\n            .is_err());\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/connection/error.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::result::QuicResult;\nuse std::io;\n\n/// Additional error types that can occur during a QUIC handshake.\n///\n/// Protocol errors are returned directly as [`quiche::Error`] values.\n#[non_exhaustive]\n#[derive(Debug, Clone, thiserror::Error)]\npub enum HandshakeError {\n    /// The configured handshake timeout has expired.\n    #[error(\"handshake timeout expired\")]\n    Timeout,\n    /// The connection was closed while handshaking, for example by the peer.\n    #[error(\"connection closed during Handshake stage\")]\n    ConnectionClosed,\n}\n\n// We use io::Result for `IQC::handshake` to provide a uniform interface with\n// handshakes of other connection types, for example TLS. This is a best-effort\n// mapping to match the existing io::ErrorKind values.\nimpl From<HandshakeError> for io::Error {\n    fn from(err: HandshakeError) -> Self {\n        match err {\n            HandshakeError::Timeout => Self::new(io::ErrorKind::TimedOut, err),\n            HandshakeError::ConnectionClosed =>\n                Self::new(io::ErrorKind::NotConnected, err),\n        }\n    }\n}\n\n/// Derives a [`std::io::Result`] from `IoWorker::handshake`'s result without\n/// taking ownership of the original [`Result`].\npub(crate) fn make_handshake_result<T>(res: &QuicResult<()>) -> io::Result<T> {\n    let Err(err) = res else {\n        return Err(io::Error::other(\n            \"Handshake transitioned to Closing without error\",\n        ));\n    };\n\n    // BoxError does not force its content to be Clone, so we need to check for\n    // the types we expect manually & clone/copy them.\n    if let Some(hs_err) = err.downcast_ref::<HandshakeError>() {\n        Err(hs_err.clone().into())\n    } else if let Some(quiche_err) = err.downcast_ref::<quiche::Error>() {\n        Err(io::Error::other(*quiche_err))\n    } else {\n        let data_fmt = format!(\"unexpected handshake error: {err}\");\n        Err(io::Error::other(data_fmt))\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/connection/id.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse quiche::ConnectionId;\nuse std::sync::Arc;\n\nuse crate::QuicResult;\n\n/// A customizable generator to derive and verify QUIC connection IDs.\n///\n/// For QUIC servers, it can be useful to encode additional information in the\n/// source connection ID. This trait allows users to implement their own logic\n/// for that purpose. The crate also provides [`SimpleConnectionIdGenerator`]\n/// if no such customization is needed.\n///\n/// Clients currently can't configure a [`ConnectionIdGenerator`] and always use\n/// the [`SimpleConnectionIdGenerator`].\npub trait ConnectionIdGenerator<'a>: Send + Sync + 'static {\n    /// Creates a new [`ConnectionId`] according to the generator's logic.\n    fn new_connection_id(&self) -> ConnectionId<'a>;\n\n    /// Verifies whether `cid` was generated by this [`ConnectionIdGenerator`].\n    fn verify_connection_id(&self, cid: &ConnectionId) -> QuicResult<()>;\n}\n\npub type SharedConnectionIdGenerator = Arc<dyn ConnectionIdGenerator<'static>>;\n\n/// A [`ConnectionIdGenerator`] which creates random 20-byte connection IDs.\n///\n/// Random bytes are pulled directly from the operating system to create an ID.\n/// Any `socket_cookie` value is ignored.\n#[derive(Debug, Clone, Default)]\npub struct SimpleConnectionIdGenerator;\n\nimpl ConnectionIdGenerator<'static> for SimpleConnectionIdGenerator {\n    fn new_connection_id(&self) -> ConnectionId<'static> {\n        let mut buf = vec![0; 20];\n        boring::rand::rand_bytes(&mut buf).unwrap();\n\n        ConnectionId::from_vec(buf)\n    }\n\n    /// Performs no verification, because this generator can create\n    /// any valid connection ID.\n    fn verify_connection_id(&self, _cid: &ConnectionId<'_>) -> QuicResult<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/connection/map.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse super::Incoming;\nuse super::InitialQuicConnection;\nuse crate::metrics::Metrics;\n\nuse datagram_socket::DatagramSocketSend;\nuse quiche::ConnectionId;\nuse quiche::MAX_CONN_ID_LEN;\nuse std::collections::BTreeMap;\nuse tokio::sync::mpsc;\n\nconst U64_SZ: usize = std::mem::size_of::<u64>();\nconst MAX_CONN_ID_QUADS: usize = MAX_CONN_ID_LEN.div_ceil(U64_SZ);\nconst CONN_ID_USABLE_LEN: usize = min_usize(\n    // Last byte in CidOwned::Optimized stores CID length\n    MAX_CONN_ID_QUADS * U64_SZ - 1,\n    // CID length must fit in 1 byte\n    min_usize(MAX_CONN_ID_LEN, u8::MAX as _),\n);\n\nconst fn min_usize(v1: usize, v2: usize) -> usize {\n    if v1 < v2 {\n        v1\n    } else {\n        v2\n    }\n}\n\n/// A non unique connection identifier, multiple Cids can map to the same\n/// conenction.\n#[derive(PartialEq, Eq, PartialOrd, Ord)]\nenum CidOwned {\n    /// The QUIC connections IDs theoretically have unbounded length, so for the\n    /// generic case a boxed slice is used to store the ID.\n    Generic(Box<[u8]>),\n    /// For QUIC version 1 (the one that actually exists) the maximal ID size is\n    /// `20`, which should correspond to the `MAX_CONN_ID_LEN` value. For\n    /// this common case, we store the ID in a u64 array for faster\n    /// comparison (and therefore BTreeMap lookups).\n    Optimized([u64; MAX_CONN_ID_QUADS]),\n}\n\nimpl From<&ConnectionId<'_>> for CidOwned {\n    #[inline(always)]\n    fn from(value: &ConnectionId<'_>) -> Self {\n        if value.len() > CONN_ID_USABLE_LEN {\n            return CidOwned::Generic(value.as_ref().into());\n        }\n\n        let mut cid = [0; MAX_CONN_ID_QUADS];\n\n        value\n            .chunks(U64_SZ)\n            .map(|c| match c.try_into() {\n                Ok(v) => u64::from_le_bytes(v),\n                Err(_) => {\n                    let mut remainder = [0u8; U64_SZ];\n                    remainder[..c.len()].copy_from_slice(c);\n                    u64::from_le_bytes(remainder)\n                },\n            })\n            .enumerate()\n            .for_each(|(i, v)| cid[i] = v);\n\n        // In order to differentiate cids with zeroes as opposed to shorter cids,\n        // append the cid length.\n        *cid.last_mut().unwrap() |= (value.len() as u64) << 56;\n\n        CidOwned::Optimized(cid)\n    }\n}\n\n/// A map for QUIC connections.\n///\n/// Due to the fact that QUIC connections can be identified by multiple QUIC\n/// connection IDs, we have to be able to map multiple IDs to the same\n/// connection.\n#[derive(Default)]\npub(crate) struct ConnectionMap {\n    quic_id_map: BTreeMap<CidOwned, mpsc::Sender<Incoming>>,\n}\n\nimpl ConnectionMap {\n    pub(crate) fn insert<Tx, M>(\n        &mut self, cid: &ConnectionId<'_>, conn: &InitialQuicConnection<Tx, M>,\n    ) where\n        Tx: DatagramSocketSend + Send + 'static,\n        M: Metrics,\n    {\n        let ev_sender = conn.incoming_ev_sender.clone();\n        self.quic_id_map.insert(cid.into(), ev_sender);\n    }\n\n    pub(crate) fn map_cid(\n        &mut self, existing_cid: &ConnectionId<'_>, new_cid: &ConnectionId<'_>,\n    ) {\n        if let Some(ev_sender) = self.quic_id_map.get(&existing_cid.into()) {\n            self.quic_id_map.insert(new_cid.into(), ev_sender.clone());\n        }\n    }\n\n    pub(crate) fn unmap_cid(&mut self, cid: &ConnectionId<'_>) {\n        self.quic_id_map.remove(&cid.into());\n    }\n\n    pub(crate) fn get(\n        &self, id: &ConnectionId,\n    ) -> Option<&mpsc::Sender<Incoming>> {\n        if id.len() == MAX_CONN_ID_LEN {\n            // Although both branches run the same code, the one here will\n            // generate an optimized version for the length we are\n            // using, as opposed to temporary cids sent by clients.\n            self.quic_id_map.get(&id.into())\n        } else {\n            self.quic_id_map.get(&id.into())\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use quiche::ConnectionId;\n\n    #[test]\n    fn cid_storage() {\n        let max_v1_cid = ConnectionId::from_ref(&[0xfa; MAX_CONN_ID_LEN]);\n        let optimized = CidOwned::from(&max_v1_cid);\n        assert!(\n            matches!(optimized, CidOwned::Optimized(_)),\n            \"QUIC v1 CID is not stored inline\"\n        );\n\n        let oversize_cid = ConnectionId::from_ref(&[0x1b; MAX_CONN_ID_LEN + 20]);\n        let boxed = CidOwned::from(&oversize_cid);\n        assert!(\n            matches!(boxed, CidOwned::Generic(_)),\n            \"Oversized CID is not boxed\"\n        );\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/connection/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod error;\nmod id;\nmod map;\n\npub use self::error::HandshakeError;\npub use self::id::ConnectionIdGenerator;\npub use self::id::SharedConnectionIdGenerator;\npub use self::id::SimpleConnectionIdGenerator;\npub(crate) use self::map::ConnectionMap;\n\nuse boring::ssl::SslRef;\nuse datagram_socket::AsSocketStats;\nuse datagram_socket::DatagramSocketSend;\nuse datagram_socket::MaybeConnectedSocket;\nuse datagram_socket::QuicAuditStats;\nuse datagram_socket::ShutdownConnection;\nuse datagram_socket::SocketStats;\nuse foundations::telemetry::log;\nuse futures::future::BoxFuture;\nuse futures::Future;\nuse quiche::ConnectionId;\nuse std::fmt;\nuse std::io;\nuse std::net::SocketAddr;\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse std::task::Poll;\nuse std::time::Duration;\nuse std::time::Instant;\nuse std::time::SystemTime;\nuse tokio::sync::mpsc;\nuse tokio_util::task::AbortOnDropHandle;\n\nuse self::error::make_handshake_result;\nuse super::io::connection_stage::Close;\nuse super::io::connection_stage::ConnectionStageContext;\nuse super::io::connection_stage::Handshake;\nuse super::io::connection_stage::RunningApplication;\nuse super::io::worker::Closing;\nuse super::io::worker::IoWorkerParams;\nuse super::io::worker::Running;\nuse super::io::worker::RunningOrClosing;\nuse super::io::worker::WriteState;\nuse super::QuicheConnection;\nuse crate::buf_factory::PooledBuf;\nuse crate::metrics::Metrics;\nuse crate::quic::io::worker::IoWorker;\nuse crate::quic::io::worker::WriterConfig;\nuse crate::quic::io::worker::INCOMING_QUEUE_SIZE;\nuse crate::quic::router::ConnectionMapCommand;\nuse crate::QuicResult;\n\n/// Wrapper for connection statistics recorded by [quiche].\n#[derive(Debug)]\npub struct QuicConnectionStats {\n    /// Aggregate connection statistics across all paths.\n    pub stats: quiche::Stats,\n    /// Specific statistics about the connection's active path.\n    pub path_stats: Option<quiche::PathStats>,\n}\npub(crate) type QuicConnectionStatsShared = Arc<Mutex<QuicConnectionStats>>;\n\nimpl QuicConnectionStats {\n    pub(crate) fn from_conn(qconn: &QuicheConnection) -> Self {\n        Self {\n            stats: qconn.stats(),\n            path_stats: qconn.path_stats().next(),\n        }\n    }\n\n    fn startup_exit_to_socket_stats(\n        value: quiche::StartupExit,\n    ) -> datagram_socket::StartupExit {\n        let reason = match value.reason {\n            quiche::StartupExitReason::Loss =>\n                datagram_socket::StartupExitReason::Loss,\n            quiche::StartupExitReason::BandwidthPlateau =>\n                datagram_socket::StartupExitReason::BandwidthPlateau,\n            quiche::StartupExitReason::PersistentQueue =>\n                datagram_socket::StartupExitReason::PersistentQueue,\n            quiche::StartupExitReason::ConservativeSlowStartRounds =>\n                datagram_socket::StartupExitReason::ConservativeSlowStartRounds,\n        };\n\n        datagram_socket::StartupExit {\n            cwnd: value.cwnd,\n            bandwidth: value.bandwidth,\n            reason,\n        }\n    }\n}\n\nimpl AsSocketStats for QuicConnectionStats {\n    fn as_socket_stats(&self) -> SocketStats {\n        SocketStats {\n            pmtu: self\n                .path_stats\n                .as_ref()\n                .map(|p| p.pmtu as u16)\n                .unwrap_or_default(),\n            rtt_us: self\n                .path_stats\n                .as_ref()\n                .map(|p| p.rtt.as_micros() as i64)\n                .unwrap_or_default(),\n            min_rtt_us: self\n                .path_stats\n                .as_ref()\n                .and_then(|p| p.min_rtt.map(|x| x.as_micros() as i64))\n                .unwrap_or_default(),\n            max_rtt_us: self\n                .path_stats\n                .as_ref()\n                .and_then(|p| p.max_rtt.map(|x| x.as_micros() as i64))\n                .unwrap_or_default(),\n            rtt_var_us: self\n                .path_stats\n                .as_ref()\n                .map(|p| p.rttvar.as_micros() as i64)\n                .unwrap_or_default(),\n            cwnd: self\n                .path_stats\n                .as_ref()\n                .map(|p| p.cwnd as u64)\n                .unwrap_or_default(),\n            total_pto_count: self\n                .path_stats\n                .as_ref()\n                .map(|p| p.total_pto_count as u64)\n                .unwrap_or_default(),\n            packets_sent: self.stats.sent as u64,\n            packets_recvd: self.stats.recv as u64,\n            packets_lost: self.stats.lost as u64,\n            packets_lost_spurious: self.stats.spurious_lost as u64,\n            packets_retrans: self.stats.retrans as u64,\n            bytes_sent: self.stats.sent_bytes,\n            bytes_recvd: self.stats.recv_bytes,\n            bytes_lost: self.stats.lost_bytes,\n            bytes_retrans: self.stats.stream_retrans_bytes,\n            bytes_unsent: 0, /* not implemented yet, kept for compatibility\n                              * with TCP */\n            delivery_rate: self\n                .path_stats\n                .as_ref()\n                .map(|p| p.delivery_rate)\n                .unwrap_or_default(),\n            max_bandwidth: self.path_stats.as_ref().and_then(|p| p.max_bandwidth),\n            startup_exit: self\n                .path_stats\n                .as_ref()\n                .and_then(|p| p.startup_exit)\n                .map(QuicConnectionStats::startup_exit_to_socket_stats),\n            bytes_in_flight_duration_us: self\n                .stats\n                .bytes_in_flight_duration\n                .as_micros() as u64,\n        }\n    }\n}\n\n/// A received network packet with additional metadata.\n#[derive(Debug)]\npub struct Incoming {\n    /// The address that sent the inbound packet.\n    pub peer_addr: SocketAddr,\n    /// The address on which we received the inbound packet.\n    pub local_addr: SocketAddr,\n    /// The receive timestamp of the packet.\n    ///\n    /// Used for the `perf-quic-listener-metrics` feature.\n    pub rx_time: Option<SystemTime>,\n    /// The packet's contents.\n    pub buf: PooledBuf,\n    /// If set, then `buf` is a GRO buffer containing multiple packets.\n    /// Each individual packet has a size of `gso` (except for the last one).\n    pub gro: Option<i32>,\n    /// [SO_MARK] control message value received from the socket.\n    ///\n    /// This will always be `None` after the connection has been spawned as\n    /// the message is `take()`d before spawning.\n    ///\n    /// [SO_MARK]: https://man7.org/linux/man-pages/man7/socket.7.html\n    #[cfg(target_os = \"linux\")]\n    pub so_mark_data: Option<[u8; 4]>,\n}\n\n/// A QUIC connection that has not performed a handshake yet.\n///\n/// This type is currently only used for server-side connections. It is created\n/// and added to the listener's connection stream after an initial packet from\n/// a client has been received and (optionally) the client's IP address has been\n/// validated.\n///\n/// To turn the initial connection into a fully established one, a QUIC\n/// handshake must be performed. Users have multiple options to facilitate this:\n/// - `start` is a simple entrypoint which spawns a task to handle the entire\n///   lifetime of the QUIC connection. The caller can then only communicate with\n///   the connection via their [`ApplicationOverQuic`].\n/// - `handshake` spawns a task for the handshake and awaits its completion.\n///   Afterwards, it pauses the connection and allows the caller to resume it\n///   later via an opaque struct. We spawn a separate task to allow the tokio\n///   scheduler free choice in where to run the handshake.\n/// - `handshake_fut` returns a future to drive the handshake for maximum\n///   flexibility.\n#[must_use = \"call InitialQuicConnection::start to establish the connection\"]\npub struct InitialQuicConnection<Tx, M>\nwhere\n    Tx: DatagramSocketSend + Send + 'static + ?Sized,\n    M: Metrics,\n{\n    params: QuicConnectionParams<Tx, M>,\n    pub(crate) audit_log_stats: Arc<QuicAuditStats>,\n    stats: QuicConnectionStatsShared,\n    pub(crate) incoming_ev_sender: mpsc::Sender<Incoming>,\n    incoming_ev_receiver: mpsc::Receiver<Incoming>,\n}\n\nimpl<Tx, M> InitialQuicConnection<Tx, M>\nwhere\n    Tx: DatagramSocketSend + Send + 'static + ?Sized,\n    M: Metrics,\n{\n    #[inline]\n    pub(crate) fn new(params: QuicConnectionParams<Tx, M>) -> Self {\n        let (incoming_ev_sender, incoming_ev_receiver) =\n            mpsc::channel(INCOMING_QUEUE_SIZE);\n        let audit_log_stats = Arc::new(QuicAuditStats::new(params.scid.to_vec()));\n\n        let stats = Arc::new(Mutex::new(QuicConnectionStats::from_conn(\n            &params.quiche_conn,\n        )));\n\n        Self {\n            params,\n            audit_log_stats,\n            stats,\n            incoming_ev_sender,\n            incoming_ev_receiver,\n        }\n    }\n\n    /// The local address this connection listens on.\n    pub fn local_addr(&self) -> SocketAddr {\n        self.params.local_addr\n    }\n\n    /// The remote address for this connection.\n    pub fn peer_addr(&self) -> SocketAddr {\n        self.params.peer_addr\n    }\n\n    /// [boring]'s SSL object for this connection.\n    #[doc(hidden)]\n    pub fn ssl_mut(&mut self) -> &mut SslRef {\n        self.params.quiche_conn.as_mut()\n    }\n\n    /// A handle to the [`QuicAuditStats`] for this connection.\n    ///\n    /// # Note\n    /// These stats are updated during the lifetime of the connection.\n    /// The getter exists to grab a handle early on, which can then\n    /// be stowed away and read out after the connection has closed.\n    #[inline]\n    pub fn audit_log_stats(&self) -> Arc<QuicAuditStats> {\n        Arc::clone(&self.audit_log_stats)\n    }\n\n    /// A handle to the [`QuicConnectionStats`] for this connection.\n    ///\n    /// # Note\n    /// Initially, these stats represent the state when the [quiche::Connection]\n    /// was created. They are updated when the connection is closed, so this\n    /// getter exists primarily to grab a handle early on.\n    #[inline]\n    pub fn stats(&self) -> &QuicConnectionStatsShared {\n        &self.stats\n    }\n\n    /// Creates a future to drive the connection's handshake.\n    ///\n    /// This is a lower-level alternative to the `handshake` function which\n    /// gives the caller more control over execution of the future. See\n    /// `handshake` for details on the return values.\n    #[allow(clippy::type_complexity)]\n    pub fn handshake_fut<A: ApplicationOverQuic>(\n        self, app: A,\n    ) -> (\n        QuicConnection,\n        BoxFuture<'static, io::Result<Running<Arc<Tx>, M, A>>>,\n    ) {\n        self.params.metrics.connections_in_memory().inc();\n\n        let conn = QuicConnection {\n            local_addr: self.params.local_addr,\n            peer_addr: self.params.peer_addr,\n            audit_log_stats: Arc::clone(&self.audit_log_stats),\n            stats: Arc::clone(&self.stats),\n            scid: self.params.scid,\n        };\n        let context = ConnectionStageContext {\n            in_pkt: self.params.initial_pkt,\n            incoming_pkt_receiver: self.incoming_ev_receiver,\n            application: app,\n            stats: Arc::clone(&self.stats),\n        };\n        let conn_stage = Handshake {\n            handshake_info: self.params.handshake_info,\n        };\n        let params = IoWorkerParams {\n            socket: MaybeConnectedSocket::new(self.params.socket),\n            shutdown_tx: self.params.shutdown_tx,\n            cfg: self.params.writer_cfg,\n            audit_log_stats: self.audit_log_stats,\n            write_state: WriteState::default(),\n            conn_map_cmd_tx: self.params.conn_map_cmd_tx,\n            cid_generator: self.params.cid_generator,\n            #[cfg(feature = \"perf-quic-listener-metrics\")]\n            init_rx_time: self.params.init_rx_time,\n            metrics: self.params.metrics.clone(),\n        };\n\n        let handshake_fut = async move {\n            let qconn = self.params.quiche_conn;\n            let handshake_done =\n                IoWorker::new(params, conn_stage).run(qconn, context).await;\n\n            match handshake_done {\n                RunningOrClosing::Running(r) => Ok(r),\n                RunningOrClosing::Closing(Closing {\n                    params,\n                    work_loop_result,\n                    mut context,\n                    mut qconn,\n                }) => {\n                    let hs_result = make_handshake_result(&work_loop_result);\n                    IoWorker::new(params, Close { work_loop_result })\n                        .close(&mut qconn, &mut context)\n                        .await;\n                    hs_result\n                },\n            }\n        };\n\n        (conn, Box::pin(handshake_fut))\n    }\n\n    /// Performs the QUIC handshake in a separate tokio task and awaits its\n    /// completion.\n    ///\n    /// The returned [`QuicConnection`] holds metadata about the established\n    /// connection. The connection itself is paused after `handshake`\n    /// returns and must be resumed by passing the opaque `Running` value to\n    /// [`InitialQuicConnection::resume`]. This two-step process\n    /// allows callers to collect telemetry and run code before serving their\n    /// [`ApplicationOverQuic`].\n    pub async fn handshake<A: ApplicationOverQuic>(\n        self, app: A,\n    ) -> io::Result<(QuicConnection, Running<Arc<Tx>, M, A>)> {\n        let task_metrics = self.params.metrics.clone();\n        let (conn, handshake_fut) = Self::handshake_fut(self, app);\n\n        let handshake_handle = crate::metrics::tokio_task::spawn(\n            \"quic_handshake_worker\",\n            task_metrics,\n            handshake_fut,\n        );\n\n        // `AbortOnDropHandle` simulates task-killswitch behavior without needing\n        // to give up ownership of the `JoinHandle`.\n        let handshake_abort_handle = AbortOnDropHandle::new(handshake_handle);\n\n        let worker = handshake_abort_handle.await??;\n\n        Ok((conn, worker))\n    }\n\n    /// Resumes a QUIC connection which was paused after a successful handshake.\n    pub fn resume<A: ApplicationOverQuic>(pre_running: Running<Arc<Tx>, M, A>) {\n        let task_metrics = pre_running.params.metrics.clone();\n        let fut = async move {\n            let Running {\n                params,\n                context,\n                qconn,\n            } = pre_running;\n            let running_worker = IoWorker::new(params, RunningApplication);\n\n            let Closing {\n                params,\n                mut context,\n                work_loop_result,\n                mut qconn,\n            } = running_worker.run(qconn, context).await;\n\n            IoWorker::new(params, Close { work_loop_result })\n                .close(&mut qconn, &mut context)\n                .await;\n        };\n\n        crate::metrics::tokio_task::spawn_with_killswitch(\n            \"quic_io_worker\",\n            task_metrics,\n            fut,\n        );\n    }\n\n    /// Drives a QUIC connection from handshake to close in separate tokio\n    /// tasks.\n    ///\n    /// It combines [`InitialQuicConnection::handshake`] and\n    /// [`InitialQuicConnection::resume`] into a single call.\n    pub fn start<A: ApplicationOverQuic>(self, app: A) -> QuicConnection {\n        let task_metrics = self.params.metrics.clone();\n        let (conn, handshake_fut) = Self::handshake_fut(self, app);\n\n        let fut = async move {\n            match handshake_fut.await {\n                Ok(running) => Self::resume(running),\n                Err(e) => {\n                    log::error!(\"QUIC handshake failed in IQC::start\"; \"error\" => e)\n                },\n            }\n        };\n\n        crate::metrics::tokio_task::spawn_with_killswitch(\n            \"quic_handshake_worker\",\n            task_metrics,\n            fut,\n        );\n\n        conn\n    }\n}\n\npub(crate) struct QuicConnectionParams<Tx, M>\nwhere\n    Tx: DatagramSocketSend + Send + 'static + ?Sized,\n    M: Metrics,\n{\n    pub writer_cfg: WriterConfig,\n    pub initial_pkt: Option<Incoming>,\n    pub shutdown_tx: mpsc::Sender<()>,\n    pub conn_map_cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>, /* channel that signals connection map changes */\n    pub scid: ConnectionId<'static>,\n    pub cid_generator: Option<SharedConnectionIdGenerator>,\n    pub metrics: M,\n    #[cfg(feature = \"perf-quic-listener-metrics\")]\n    pub init_rx_time: Option<SystemTime>,\n    pub handshake_info: HandshakeInfo,\n    pub quiche_conn: QuicheConnection,\n    pub socket: Arc<Tx>,\n    pub local_addr: SocketAddr,\n    pub peer_addr: SocketAddr,\n}\n\n/// Metadata about an established QUIC connection.\n///\n/// While this struct allows access to some facets of a QUIC connection, it\n/// notably does not represent the [quiche::Connection] itself. The crate\n/// handles most interactions with [quiche] internally in a worker task. Users\n/// can only access the connection directly via their [`ApplicationOverQuic`]\n/// implementation.\n///\n/// See the [module-level docs](crate::quic) for an overview of how a QUIC\n/// connection is handled internally.\npub struct QuicConnection {\n    local_addr: SocketAddr,\n    peer_addr: SocketAddr,\n    audit_log_stats: Arc<QuicAuditStats>,\n    stats: QuicConnectionStatsShared,\n    scid: ConnectionId<'static>,\n}\n\nimpl QuicConnection {\n    /// The local address this connection listens on.\n    #[inline]\n    pub fn local_addr(&self) -> SocketAddr {\n        self.local_addr\n    }\n\n    /// The remote address for this connection.\n    #[inline]\n    pub fn peer_addr(&self) -> SocketAddr {\n        self.peer_addr\n    }\n\n    /// A handle to the [`QuicAuditStats`] for this connection.\n    ///\n    /// # Note\n    /// These stats are updated during the lifetime of the connection.\n    /// The getter exists to grab a handle early on, which can then\n    /// be stowed away and read out after the connection has closed.\n    #[inline]\n    pub fn audit_log_stats(&self) -> &Arc<QuicAuditStats> {\n        &self.audit_log_stats\n    }\n\n    /// A handle to the [`QuicConnectionStats`] for this connection.\n    ///\n    /// # Note\n    /// Initially, these stats represent the state when the [quiche::Connection]\n    /// was created. They are updated when the connection is closed, so this\n    /// getter exists primarily to grab a handle early on.\n    #[inline]\n    pub fn stats(&self) -> &QuicConnectionStatsShared {\n        &self.stats\n    }\n\n    /// The QUIC source connection ID used by this connection.\n    #[inline]\n    pub fn scid(&self) -> &ConnectionId<'static> {\n        &self.scid\n    }\n}\n\nimpl AsSocketStats for QuicConnection {\n    #[inline]\n    fn as_socket_stats(&self) -> SocketStats {\n        // It is important to note that those stats are only updated when\n        // the connection stops, which is fine, since this is only used to\n        // log after the connection is finished.\n        self.stats.lock().unwrap().as_socket_stats()\n    }\n\n    #[inline]\n    fn as_quic_stats(&self) -> Option<&Arc<QuicAuditStats>> {\n        Some(&self.audit_log_stats)\n    }\n}\n\nimpl<Tx, M> AsSocketStats for InitialQuicConnection<Tx, M>\nwhere\n    Tx: DatagramSocketSend + Send + 'static + ?Sized,\n    M: Metrics,\n{\n    #[inline]\n    fn as_socket_stats(&self) -> SocketStats {\n        // It is important to note that those stats are only updated when\n        // the connection stops, which is fine, since this is only used to\n        // log after the connection is finished.\n        self.stats.lock().unwrap().as_socket_stats()\n    }\n\n    #[inline]\n    fn as_quic_stats(&self) -> Option<&Arc<QuicAuditStats>> {\n        Some(&self.audit_log_stats)\n    }\n}\n\nimpl<Tx, M> ShutdownConnection for InitialQuicConnection<Tx, M>\nwhere\n    Tx: DatagramSocketSend + Send + 'static + ?Sized,\n    M: Metrics,\n{\n    #[inline]\n    fn poll_shutdown(\n        &mut self, _cx: &mut std::task::Context,\n    ) -> std::task::Poll<io::Result<()>> {\n        // TODO: Does nothing at the moment. We always call Self::start\n        // anyway so it's not really important at this moment.\n        Poll::Ready(Ok(()))\n    }\n}\n\nimpl ShutdownConnection for QuicConnection {\n    #[inline]\n    fn poll_shutdown(\n        &mut self, _cx: &mut std::task::Context,\n    ) -> std::task::Poll<io::Result<()>> {\n        // TODO: does nothing at the moment\n        Poll::Ready(Ok(()))\n    }\n}\n\n/// Details about a connection's QUIC handshake.\n#[derive(Debug, Clone)]\npub struct HandshakeInfo {\n    /// The time at which the connection was created.\n    start_time: Instant,\n    /// The timeout before which the handshake must complete.\n    timeout: Option<Duration>,\n    /// The real duration that the handshake took to complete.\n    time_handshake: Option<Duration>,\n}\n\nimpl HandshakeInfo {\n    pub(crate) fn new(start_time: Instant, timeout: Option<Duration>) -> Self {\n        Self {\n            start_time,\n            timeout,\n            time_handshake: None,\n        }\n    }\n\n    /// The time at which the connection was created.\n    #[inline]\n    pub fn start_time(&self) -> Instant {\n        self.start_time\n    }\n\n    /// How long the handshake took to complete.\n    #[inline]\n    pub fn elapsed(&self) -> Duration {\n        self.time_handshake.unwrap_or_default()\n    }\n\n    pub(crate) fn set_elapsed(&mut self) {\n        let elapsed = self.start_time.elapsed();\n        self.time_handshake = Some(elapsed)\n    }\n\n    pub(crate) fn deadline(&self) -> Option<Instant> {\n        self.timeout.map(|timeout| self.start_time + timeout)\n    }\n\n    pub(crate) fn is_expired(&self) -> bool {\n        self.timeout\n            .is_some_and(|timeout| self.start_time.elapsed() >= timeout)\n    }\n}\n\n/// A trait to implement an application served over QUIC.\n///\n/// The application is driven by an internal worker task, which also handles I/O\n/// for the connection. The worker feeds inbound packets into the\n/// [quiche::Connection], calls [`ApplicationOverQuic::process_reads`] followed\n/// by [`ApplicationOverQuic::process_writes`], and then flushes any pending\n/// outbound packets to the network. This repeats in a loop until either the\n/// connection is closed or the [`ApplicationOverQuic`] returns an error.\n///\n/// In between loop iterations, the worker yields until a new packet arrives, a\n/// timer expires, or [`ApplicationOverQuic::wait_for_data`] resolves.\n/// Implementors can interact with the underlying connection via the mutable\n/// reference passed to trait methods.\n#[allow(unused_variables)] // for default functions\npub trait ApplicationOverQuic: Send + 'static {\n    /// Callback to customize the [`ApplicationOverQuic`] after the QUIC\n    /// handshake completed successfully.\n    ///\n    /// # Errors\n    /// Returning an error from this method immediately stops the worker loop\n    /// and transitions to the connection closing stage.\n    fn on_conn_established(\n        &mut self, qconn: &mut QuicheConnection, handshake_info: &HandshakeInfo,\n    ) -> QuicResult<()>;\n\n    /// Determines whether the application's methods will be called by the\n    /// worker.\n    ///\n    /// The function is checked in each iteration of the worker loop. Only\n    /// `on_conn_established()` and `buffer()` bypass this check.\n    fn should_act(&self) -> bool;\n\n    /// A borrowed buffer for the worker to write outbound packets into.\n    ///\n    /// This method allows sharing a buffer between the worker and the\n    /// application, efficiently using the allocated memory while the\n    /// application is inactive. It can also be used to artificially\n    /// restrict the size of outbound network packets.\n    ///\n    /// Any data in the buffer may be overwritten by the worker. If necessary,\n    /// the application should save the contents when this method is called.\n    fn buffer(&mut self) -> &mut [u8];\n\n    /// Waits for an event to trigger the next iteration of the worker loop.\n    ///\n    /// The returned future is awaited in parallel to inbound packets and the\n    /// connection's timers. Any one of those futures resolving triggers the\n    /// next loop iteration, so implementations should not rely on\n    /// `wait_for_data` for the bulk of their processing. Instead, after\n    /// `wait_for_data` resolves, `process_writes` should be used to pull all\n    /// available data out of the event source (for example, a channel).\n    ///\n    /// As for any future, it is **very important** that this method does not\n    /// block the runtime. If it does, the other concurrent futures will be\n    /// starved.\n    ///\n    /// # Cancel safety\n    /// This method MUST be cancel safe.\n    /// It gets called inside select! and could be (repeatedly) cancelled\n    ///\n    /// # Errors\n    /// Returning an error from this method immediately stops the worker loop\n    /// and transitions to the connection closing stage.\n    fn wait_for_data(\n        &mut self, qconn: &mut QuicheConnection,\n    ) -> impl Future<Output = QuicResult<()>> + Send;\n\n    /// Processes data received on the connection.\n    ///\n    /// This method is only called if `should_act()` returns `true` and any\n    /// packets were received since the last worker loop iteration. It\n    /// should be used to read from the connection's open streams.\n    ///\n    /// # Errors\n    /// Returning an error from this method immediately stops the worker loop\n    /// and transitions to the connection closing stage.\n    fn process_reads(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()>;\n\n    /// Adds data to be sent on the connection.\n    ///\n    /// Unlike `process_reads`, this method is called on every iteration of the\n    /// worker loop (provided `should_act()` returns true). It is called\n    /// after `process_reads` and immediately before packets are pushed to\n    /// the socket. The main use case is providing already-buffered data to\n    /// the [quiche::Connection].\n    ///\n    /// # Errors\n    /// Returning an error from this method immediately stops the worker loop\n    /// and transitions to the connection closing stage.\n    fn process_writes(&mut self, qconn: &mut QuicheConnection) -> QuicResult<()>;\n\n    /// Callback to inspect the result of the worker task, before a final packet\n    /// with a `CONNECTION_CLOSE` frame is flushed to the network.\n    ///\n    /// `connection_result` is [`Ok`] only if the connection was closed without\n    /// any local error. Otherwise, the state of `qconn` depends on the\n    /// error type and application behavior.\n    fn on_conn_close<M: Metrics>(\n        &mut self, qconn: &mut QuicheConnection, metrics: &M,\n        connection_result: &QuicResult<()>,\n    ) {\n    }\n}\n\n/// A command to execute on a [quiche::Connection] in the context of an\n/// [`ApplicationOverQuic`].\n///\n/// We expect most [`ApplicationOverQuic`] implementations (such as\n/// [H3Driver](crate::http3::driver::H3Driver)) will provide some way to submit\n/// actions for them to take, for example via a channel. This enum may be\n/// accepted as part of those actions to inspect or alter the state of the\n/// underlying connection.\npub enum QuicCommand {\n    /// Close the connection with the given parameters.\n    ///\n    /// Some packets may still be sent after this command has been executed, so\n    /// the worker task may continue running for a bit. See\n    /// [`quiche::Connection::close`] for details.\n    ConnectionClose(ConnectionShutdownBehaviour),\n    /// Execute a custom callback on the connection.\n    Custom(Box<dyn FnOnce(&mut QuicheConnection) + Send + 'static>),\n    /// Collect the current [`SocketStats`] from the connection.\n    ///\n    /// Unlike [`QuicConnection::stats()`], these statistics are not cached and\n    /// instead are retrieved right before the command is executed.\n    Stats(Box<dyn FnOnce(datagram_socket::SocketStats) + Send + 'static>),\n    /// Collect the current [`QuicConnectionStats`] from the connection.\n    ///\n    /// These statistics are not cached and instead are retrieved right before\n    /// the command is executed.\n    ConnectionStats(Box<dyn FnOnce(QuicConnectionStats) + Send + 'static>),\n}\n\nimpl QuicCommand {\n    /// Consume the command and perform its operation on `qconn`.\n    ///\n    /// This method should be called by [`ApplicationOverQuic`] implementations\n    /// when they receive a [`QuicCommand`] to execute.\n    pub fn execute(self, qconn: &mut QuicheConnection) {\n        match self {\n            Self::ConnectionClose(behavior) => {\n                let ConnectionShutdownBehaviour {\n                    send_application_close,\n                    error_code,\n                    reason,\n                } = behavior;\n\n                let _ = qconn.close(send_application_close, error_code, &reason);\n            },\n            Self::Custom(f) => {\n                (f)(qconn);\n            },\n            Self::Stats(callback) => {\n                let stats_pair = QuicConnectionStats::from_conn(qconn);\n                (callback)(stats_pair.as_socket_stats());\n            },\n            Self::ConnectionStats(callback) => {\n                let stats_pair = QuicConnectionStats::from_conn(qconn);\n                (callback)(stats_pair);\n            },\n        }\n    }\n}\n\nimpl fmt::Debug for QuicCommand {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        match self {\n            Self::ConnectionClose(b) =>\n                f.debug_tuple(\"ConnectionClose\").field(b).finish(),\n            Self::Custom(_) => f.debug_tuple(\"Custom\").finish_non_exhaustive(),\n            Self::Stats(_) => f.debug_tuple(\"Stats\").finish_non_exhaustive(),\n            Self::ConnectionStats(_) =>\n                f.debug_tuple(\"ConnectionStats\").finish_non_exhaustive(),\n        }\n    }\n}\n\n/// Parameters to close a [quiche::Connection].\n///\n/// The connection will use these parameters for the `CONNECTION_CLOSE` frame\n/// it sends to its peer.\n#[derive(Debug, Clone)]\npub struct ConnectionShutdownBehaviour {\n    /// Whether to send an application close or a regular close to the peer.\n    ///\n    /// If this is true but the connection is not in a state where it is safe to\n    /// send an application error (not established nor in early data), in\n    /// accordance with [RFC 9000](https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.3-3), the\n    /// error code is changed to `APPLICATION_ERROR` and the reason phrase is\n    /// cleared.\n    pub send_application_close: bool,\n    /// The [QUIC][proto-err] or [application-level][app-err] error code to send\n    /// to the peer.\n    ///\n    /// [proto-err]: https://www.rfc-editor.org/rfc/rfc9000.html#section-20.1\n    /// [app-err]: https://www.rfc-editor.org/rfc/rfc9000.html#section-20.2\n    pub error_code: u64,\n    /// The reason phrase to send to the peer.\n    pub reason: Vec<u8>,\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/hooks.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::settings::TlsCertificatePaths;\nuse boring::ssl::SslContextBuilder;\n\n/// A set of hooks executed at the level of a [quiche::Connection].\npub trait ConnectionHook {\n    /// Constructs an optional [`SslContextBuilder`].\n    ///\n    /// This method allows full customization of quiche's SSL context, for\n    /// example to specify async callbacks during the QUIC handshake. It is\n    /// called once per socket during initial setup, and then reused across\n    /// all connections on that socket.\n    ///\n    /// Only called if both the hook and [`TlsCertificatePaths`] are set in\n    /// [`ConnectionParams`](crate::ConnectionParams).\n    fn create_custom_ssl_context_builder(\n        &self, settings: TlsCertificatePaths<'_>,\n    ) -> Option<SslContextBuilder>;\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/io/connection_stage.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::fmt::Debug;\nuse std::ops::ControlFlow;\nuse std::time::Instant;\n\nuse tokio::sync::mpsc;\n\nuse crate::quic::connection::ApplicationOverQuic;\nuse crate::quic::connection::HandshakeError;\nuse crate::quic::connection::HandshakeInfo;\nuse crate::quic::connection::Incoming;\nuse crate::quic::connection::QuicConnectionStatsShared;\nuse crate::quic::QuicheConnection;\nuse crate::QuicResult;\n\n/// Represents the current lifecycle stage of a [quiche::Connection].\n/// Implementors of this trait inform the underlying I/O loop as to how to\n/// behave.\n///\n/// The I/O loop will always handle sending/receiving packets - this trait\n/// simply serves to augment its functionality. For example, an established\n/// HTTP/3 connection may want its `on_read` to include handing packets off to\n/// an [ApplicationOverQuic].\n///\n/// To prevent borrow checker conflicts, we inject a `qconn` into all methods.\n/// This also simplifies state transitions, since the `IoWorker` must maintain\n/// ownership over the connection in order to read, gather, and flush from it.\npub trait ConnectionStage: Send + Debug {\n    fn on_read<A: ApplicationOverQuic>(\n        &mut self, _received_packets: bool, _qconn: &mut QuicheConnection,\n        _ctx: &mut ConnectionStageContext<A>,\n    ) -> QuicResult<()> {\n        Ok(())\n    }\n\n    fn on_flush<A: ApplicationOverQuic>(\n        &mut self, _qconn: &mut QuicheConnection,\n        _ctx: &mut ConnectionStageContext<A>,\n    ) -> ControlFlow<QuicResult<()>> {\n        ControlFlow::Continue(())\n    }\n\n    fn wait_deadline(&mut self) -> Option<Instant> {\n        None\n    }\n\n    fn post_wait(\n        &self, _qconn: &mut QuicheConnection,\n    ) -> ControlFlow<QuicResult<()>> {\n        ControlFlow::Continue(())\n    }\n}\n\n/// Global context shared across all [ConnectionStage]s for a given connection\npub struct ConnectionStageContext<A> {\n    pub in_pkt: Option<Incoming>,\n    pub application: A,\n    pub incoming_pkt_receiver: mpsc::Receiver<Incoming>,\n    pub stats: QuicConnectionStatsShared,\n}\n\nimpl<A> ConnectionStageContext<A>\nwhere\n    A: ApplicationOverQuic,\n{\n    // TODO: remove when AOQ::buffer() situation is sorted - that method shouldn't\n    // exist\n    pub fn buffer(&mut self) -> &mut [u8] {\n        self.application.buffer()\n    }\n}\n\n#[derive(Debug)]\npub struct Handshake {\n    pub handshake_info: HandshakeInfo,\n}\n\nimpl Handshake {\n    fn check_handshake_timeout_expired(\n        &self, conn: &mut QuicheConnection,\n    ) -> QuicResult<()> {\n        if self.handshake_info.is_expired() {\n            let _ = conn.close(\n                false,\n                quiche::WireErrorCode::ApplicationError as u64,\n                &[],\n            );\n            return Err(HandshakeError::Timeout.into());\n        }\n\n        Ok(())\n    }\n}\n\nimpl ConnectionStage for Handshake {\n    fn on_flush<A: ApplicationOverQuic>(\n        &mut self, qconn: &mut QuicheConnection,\n        _ctx: &mut ConnectionStageContext<A>,\n    ) -> ControlFlow<QuicResult<()>> {\n        // Transition to RunningApplication if we have 1-RTT keys (handshake is\n        // complete) or if we have 0-RTT keys (in early data).\n        if qconn.is_established() || qconn.is_in_early_data() {\n            ControlFlow::Break(Ok(()))\n        } else {\n            ControlFlow::Continue(())\n        }\n    }\n\n    fn wait_deadline(&mut self) -> Option<Instant> {\n        self.handshake_info.deadline()\n    }\n\n    fn post_wait(\n        &self, qconn: &mut QuicheConnection,\n    ) -> ControlFlow<QuicResult<()>> {\n        match self.check_handshake_timeout_expired(qconn) {\n            Ok(_) => ControlFlow::Continue(()),\n            Err(e) => ControlFlow::Break(Err(e)),\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct RunningApplication;\n\nimpl ConnectionStage for RunningApplication {\n    fn on_read<A: ApplicationOverQuic>(\n        &mut self, received_packets: bool, qconn: &mut QuicheConnection,\n        ctx: &mut ConnectionStageContext<A>,\n    ) -> QuicResult<()> {\n        if ctx.application.should_act() {\n            if received_packets {\n                ctx.application.process_reads(qconn)?;\n            }\n\n            if qconn.is_established() {\n                ctx.application.process_writes(qconn)?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub struct Close {\n    pub work_loop_result: QuicResult<()>,\n}\n\nimpl ConnectionStage for Close {}\n"
  },
  {
    "path": "tokio-quiche/src/quic/io/gso.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::io;\nuse std::net::SocketAddr;\nuse std::time::Instant;\n\nuse foundations::telemetry::metrics::Counter;\nuse foundations::telemetry::metrics::TimeHistogram;\n\n#[cfg(all(target_os = \"linux\", not(feature = \"fuzzing\")))]\nmod linux_imports {\n    pub(super) use nix::sys::socket::sendmsg;\n    pub(super) use nix::sys::socket::ControlMessage;\n    pub(super) use nix::sys::socket::MsgFlags;\n    pub(super) use nix::sys::socket::SockaddrStorage;\n    pub(super) use smallvec::SmallVec;\n    pub(super) use std::io::ErrorKind;\n    pub(super) use std::os::fd::AsRawFd;\n    pub(super) use tokio::io::Interest;\n}\n\n#[cfg(all(target_os = \"linux\", not(feature = \"fuzzing\")))]\nuse self::linux_imports::*;\n\n// Maximum number of packets can be sent in UDP GSO.\npub(crate) const UDP_MAX_SEGMENT_COUNT: usize = 64;\n\n#[cfg(not(feature = \"gcongestion\"))]\n/// Returns a new max send buffer size to avoid the fragmentation\n/// at the end. Maximum send buffer size is min(MAX_SEND_BUF_SIZE,\n/// connection's send_quantum).\n/// For example,\n///\n/// - max_send_buf = 1000 and mss = 100, return 1000\n/// - max_send_buf = 1000 and mss = 90, return 990\n///\n/// not to have last 10 bytes packet.\npub(crate) fn tune_max_send_size(\n    segment_size: Option<usize>, send_quantum: usize, max_capacity: usize,\n) -> usize {\n    let max_send_buf_size = send_quantum.min(max_capacity);\n\n    if let Some(mss) = segment_size {\n        max_send_buf_size / mss * mss\n    } else {\n        max_send_buf_size\n    }\n}\n\n// https://wiki.cfdata.org/pages/viewpage.action?pageId=436188159\npub(crate) const UDP_MAX_GSO_PACKET_SIZE: usize = 65507;\n\n#[cfg(all(target_os = \"linux\", not(feature = \"fuzzing\")))]\n#[derive(Copy, Clone, Debug)]\npub(crate) enum PktInfo {\n    V4(libc::in_pktinfo),\n    V6(libc::in6_pktinfo),\n}\n\n#[cfg(all(target_os = \"linux\", not(feature = \"fuzzing\")))]\nimpl PktInfo {\n    fn make_cmsg(&'_ self) -> ControlMessage<'_> {\n        match self {\n            Self::V4(pkt) => ControlMessage::Ipv4PacketInfo(pkt),\n            Self::V6(pkt) => ControlMessage::Ipv6PacketInfo(pkt),\n        }\n    }\n\n    fn from_socket_addr(addr: SocketAddr) -> Self {\n        match addr {\n            SocketAddr::V4(ipv4) => {\n                // This is basically a safe wrapper around `mem::transmute()`.\n                // Calling this on the raw octets will ensure they\n                // become a native-endian, kernel-readable u32\n                let s_addr = u32::from_ne_bytes(ipv4.ip().octets());\n\n                Self::V4(libc::in_pktinfo {\n                    ipi_ifindex: 0,\n                    ipi_spec_dst: libc::in_addr { s_addr },\n                    ipi_addr: libc::in_addr { s_addr: 0 },\n                })\n            },\n            SocketAddr::V6(ipv6) => Self::V6(libc::in6_pktinfo {\n                ipi6_ifindex: 0,\n                ipi6_addr: libc::in6_addr {\n                    s6_addr: ipv6.ip().octets(),\n                },\n            }),\n        }\n    }\n}\n\n#[cfg(all(target_os = \"linux\", not(feature = \"fuzzing\")))]\n#[allow(clippy::too_many_arguments)]\npub async fn send_to(\n    socket: &tokio::net::UdpSocket, to: SocketAddr, from: Option<SocketAddr>,\n    send_buf: &[u8], segment_size: usize, tx_time: Option<Instant>,\n    would_block_metric: Counter, send_to_wouldblock_duration_s: TimeHistogram,\n) -> io::Result<usize> {\n    // An instant with the value of zero, since [`Instant`] is backed by a version\n    // of timespec this allows to extract raw values from an [`Instant`]\n    const INSTANT_ZERO: Instant = unsafe { std::mem::transmute(0u128) };\n\n    let mut sendmsg_retry_timer = None;\n    loop {\n        let iov = [std::io::IoSlice::new(send_buf)];\n        let segment_size_u16 = segment_size as u16;\n\n        let raw_time = tx_time\n            .map(|t| t.duration_since(INSTANT_ZERO).as_nanos() as u64)\n            .unwrap_or(0);\n\n        let pkt_info = from.map(PktInfo::from_socket_addr);\n\n        let mut cmsgs: SmallVec<[ControlMessage; 3]> = SmallVec::new();\n\n        // Create cmsg for UDP_SEGMENT.\n        cmsgs.push(ControlMessage::UdpGsoSegments(&segment_size_u16));\n\n        if tx_time.is_some() {\n            // Create cmsg for TXTIME.\n            cmsgs.push(ControlMessage::TxTime(&raw_time));\n        }\n\n        if let Some(pkt) = pkt_info.as_ref() {\n            // Create cmsg for IP(V6)_PKTINFO.\n            cmsgs.push(pkt.make_cmsg());\n        }\n\n        let addr = SockaddrStorage::from(to);\n\n        // Must use [`try_io`] so tokio can properly clear its readyness flag\n        let res = socket.try_io(Interest::WRITABLE, || {\n            let fd = socket.as_raw_fd();\n            sendmsg(fd, &iov, &cmsgs, MsgFlags::empty(), Some(&addr))\n                .map_err(Into::into)\n        });\n\n        match res {\n            // Wait for the socket to become writable and try again\n            Err(e) if e.kind() == ErrorKind::WouldBlock => {\n                if sendmsg_retry_timer.is_none() {\n                    sendmsg_retry_timer =\n                        Some(send_to_wouldblock_duration_s.start_timer());\n                }\n                would_block_metric.inc();\n                socket.writable().await?\n            },\n            res => return res,\n        }\n    }\n}\n\n#[cfg(any(not(target_os = \"linux\"), feature = \"fuzzing\"))]\n#[allow(clippy::too_many_arguments)]\npub(crate) async fn send_to(\n    socket: &tokio::net::UdpSocket, to: SocketAddr, _from: Option<SocketAddr>,\n    send_buf: &[u8], _segment_size: usize, _tx_time: Option<Instant>,\n    _would_block_metric: Counter, _send_to_wouldblock_duration_s: TimeHistogram,\n) -> io::Result<usize> {\n    socket.send_to(send_buf, to).await\n}\n\n#[cfg(all(target_os = \"linux\", test))]\nmod test {\n    #[test]\n    /// If this test begins to fail, it means the implementation of [`Instant`]\n    /// has changed in the std library.\n    fn instant_zero() {\n        use std::time::Instant;\n\n        const INSTANT_ZERO: Instant = unsafe { std::mem::transmute(0u128) };\n        const NANOS_PER_SEC: u128 = 1_000_000_000;\n\n        // Define a [`Timespec`] similar to the one backing [`Instant`]\n        #[derive(Debug)]\n        struct Timespec {\n            tv_sec: i64,\n            tv_nsec: u32,\n        }\n\n        let now = Instant::now();\n        let now_timespec: Timespec = unsafe { std::mem::transmute(now) };\n\n        let ref_elapsed = now.duration_since(INSTANT_ZERO).as_nanos();\n        let raw_elapsed = now_timespec.tv_sec as u128 * NANOS_PER_SEC +\n            now_timespec.tv_nsec as u128;\n\n        assert_eq!(ref_elapsed, raw_elapsed);\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/io/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\npub mod connection_stage;\npub(crate) mod gso;\npub(crate) mod utilization_estimator;\npub(crate) mod worker;\n"
  },
  {
    "path": "tokio-quiche/src/quic/io/utilization_estimator.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse foundations::telemetry::metrics::Gauge;\n\nuse std::collections::VecDeque;\nuse std::ops::Div;\nuse std::ops::Sub;\nuse std::time::Duration;\nuse std::time::Instant;\n\nuse crate::quic::QuicheConnection;\n\nconst EST_WIN: usize = 10;\n\n/// [`BandwidthReporter`] is responsible to track the bandwidth estimate for the\n/// connection\npub(super) struct BandwidthReporter {\n    /// Time of last update\n    last_update: Instant,\n    /// Period between update (set using rtt)\n    update_period: Duration,\n    /// Estimate at last update\n    last_bandwidth: u64,\n    /// Bytes sent at last update\n    last_sent: u64,\n    /// Bytes lost at last update\n    last_lost: u64,\n    /// Bytes acked at last update\n    last_acked: u64,\n    /// Max recorded bandwidth\n    pub(super) max_bandwidth: u64,\n    /// Loss at max recorded bandwidth\n    pub(super) max_loss_pct: f32,\n\n    estimator: MaxUtilizedBandwidthEstimator,\n\n    gauge: Gauge,\n}\n\nimpl BandwidthReporter {\n    pub(super) fn new(gauge: Gauge) -> Self {\n        BandwidthReporter {\n            last_update: Instant::now(),\n            update_period: Duration::from_millis(50),\n\n            last_bandwidth: 0,\n\n            last_sent: 0,\n            last_lost: 0,\n            last_acked: 0,\n\n            max_bandwidth: 0,\n            max_loss_pct: 0.,\n\n            estimator: MaxUtilizedBandwidthEstimator::new(),\n\n            gauge,\n        }\n    }\n\n    #[inline]\n    pub(super) fn update(&mut self, quiche: &QuicheConnection, now: Instant) {\n        if now.duration_since(self.last_update) < self.update_period {\n            return;\n        }\n\n        let stats = quiche.stats();\n\n        let bytes_sent = stats.sent_bytes - self.last_sent;\n        let bytes_lost = stats.lost_bytes - self.last_lost;\n        let bytes_acked = stats.acked_bytes - self.last_acked;\n\n        self.estimator.new_round(\n            self.last_update,\n            bytes_sent,\n            bytes_lost,\n            bytes_acked,\n        );\n\n        self.last_sent = stats.sent_bytes;\n        self.last_lost = stats.lost_bytes;\n        self.last_acked = stats.acked_bytes;\n\n        self.last_update = now;\n\n        let bw_estimate = self.estimator.get();\n\n        if self.last_bandwidth != bw_estimate.bandwidth {\n            self.gauge.dec_by(self.last_bandwidth);\n\n            self.last_bandwidth = bw_estimate.bandwidth;\n\n            self.gauge.inc_by(self.last_bandwidth);\n\n            self.max_bandwidth = self.max_bandwidth.max(self.last_bandwidth);\n            self.max_loss_pct = self.max_loss_pct.max(bw_estimate.loss);\n        }\n\n        if let Some(p) = quiche.path_stats().find(|s| s.active) {\n            self.update_period = p.rtt;\n        }\n    }\n}\n\nimpl Drop for BandwidthReporter {\n    fn drop(&mut self) {\n        self.gauge.dec_by(self.last_bandwidth);\n    }\n}\n\n#[derive(Clone, Copy, Default)]\npub struct Estimate {\n    pub bandwidth: u64,\n    pub loss: f32,\n}\n\nimpl PartialEq for Estimate {\n    fn eq(&self, other: &Self) -> bool {\n        self.bandwidth == other.bandwidth\n    }\n}\n\nimpl Eq for Estimate {}\n\nimpl PartialOrd for Estimate {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for Estimate {\n    fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n        self.bandwidth.cmp(&other.bandwidth)\n    }\n}\n\nstruct Round {\n    bytes_sent: u64,\n    bytes_acked: u64,\n    bytes_lost: u64,\n    start: Instant,\n}\n\npub(super) struct MaxUtilizedBandwidthEstimator {\n    rounds: VecDeque<Round>,\n    estimate: WindowedFilter<Estimate, Instant, Duration>,\n    bytes_sent_prev_round: u64,\n}\n\nimpl MaxUtilizedBandwidthEstimator {\n    fn new() -> Self {\n        let rounds = VecDeque::with_capacity(EST_WIN);\n\n        MaxUtilizedBandwidthEstimator {\n            rounds,\n            estimate: WindowedFilter::new(Duration::from_secs(120)),\n            bytes_sent_prev_round: 0,\n        }\n    }\n\n    fn new_round(\n        &mut self, time: Instant, bytes_sent: u64, bytes_lost: u64,\n        bytes_acked: u64,\n    ) {\n        if self.rounds.len() == EST_WIN {\n            let _ = self.rounds.pop_front();\n        }\n\n        self.rounds.push_back(Round {\n            bytes_sent: self.bytes_sent_prev_round,\n            bytes_acked,\n            bytes_lost,\n            start: time,\n        });\n\n        // Unlike acked and lost count, sent count is computed over a window 1 rtt\n        // in the past.\n        self.bytes_sent_prev_round = bytes_sent;\n\n        let bytes_acked = self.rounds.iter().map(|v| v.bytes_acked).sum::<u64>();\n        let bytes_lost = self.rounds.iter().map(|v| v.bytes_lost).sum::<u64>();\n        let bytes_sent = self.rounds.iter().map(|v| v.bytes_sent).sum::<u64>();\n\n        let loss = if bytes_lost == 0 {\n            0.\n        } else {\n            bytes_lost as f32 / bytes_sent as f32\n        };\n\n        let time_delta = time.duration_since(self.rounds.front().unwrap().start);\n\n        if bytes_acked > 0 {\n            let ack_rate =\n                bandwidth_from_bytes_and_time_delta(bytes_acked, time_delta);\n            let send_rate =\n                bandwidth_from_bytes_and_time_delta(bytes_sent, time_delta);\n            let estimate = Estimate {\n                bandwidth: ack_rate.min(send_rate),\n                loss,\n            };\n\n            if self.rounds.len() < EST_WIN / 2 {\n                self.estimate.reset(estimate, time)\n            } else {\n                self.estimate.update(estimate, time)\n            }\n        }\n    }\n\n    pub(super) fn get(&self) -> Estimate {\n        // Too few rounds\n        if self.rounds.len() < EST_WIN / 2 {\n            return Default::default();\n        }\n\n        self.estimate.get_best().unwrap_or_default()\n    }\n}\n\n/// Bandwidth in bits per second from bytes and time period\nfn bandwidth_from_bytes_and_time_delta(bytes: u64, time_delta: Duration) -> u64 {\n    if bytes == 0 {\n        return 0;\n    }\n\n    let mut nanos = time_delta.as_nanos();\n    if nanos == 0 {\n        nanos = 1;\n    }\n\n    let num_nano_bits = 8 * bytes as u128 * 1_000_000_000;\n    if num_nano_bits < nanos {\n        return 1;\n    }\n\n    (num_nano_bits / nanos) as u64\n}\n\n/// Below is windowed filter implementation from quiche\n#[derive(Clone, Copy)]\nstruct Sample<T, I> {\n    sample: T,\n    time: I,\n}\n\npub struct WindowedFilter<T, I, D> {\n    window_length: D,\n    estimates: [Option<Sample<T, I>>; 3],\n}\n\nimpl<T, I, D> WindowedFilter<T, I, D>\nwhere\n    T: Ord + Copy,\n    I: Sub<I, Output = D> + Copy,\n    D: Ord + Div<u32, Output = D> + Copy,\n{\n    pub fn new(window_length: D) -> Self {\n        WindowedFilter {\n            window_length,\n            estimates: [None, None, None],\n        }\n    }\n\n    pub fn reset(&mut self, new_sample: T, new_time: I) {\n        let sample = Some(Sample {\n            sample: new_sample,\n            time: new_time,\n        });\n\n        self.estimates = [sample, sample, sample];\n    }\n\n    pub fn get_best(&self) -> Option<T> {\n        self.estimates[0].as_ref().map(|e| e.sample)\n    }\n\n    pub fn update(&mut self, new_sample: T, new_time: I) {\n        // Reset all estimates if they have not yet been initialized, if new\n        // sample is a new best, or if the newest recorded estimate is too\n        // old.\n        if match &self.estimates[0] {\n            None => true,\n            Some(best) if new_sample > best.sample => true,\n            _ =>\n                new_time - self.estimates[2].as_ref().unwrap().time >\n                    self.window_length,\n        } {\n            return self.reset(new_sample, new_time);\n        }\n\n        if new_sample > self.estimates[1].unwrap().sample {\n            self.estimates[1] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n            self.estimates[2] = self.estimates[1];\n        } else if new_sample > self.estimates[2].unwrap().sample {\n            self.estimates[2] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n        }\n\n        // Expire and update estimates as necessary.\n        if new_time - self.estimates[0].unwrap().time > self.window_length {\n            // The best estimate hasn't been updated for an entire window, so\n            // promote second and third best estimates.\n            self.estimates[0] = self.estimates[1];\n            self.estimates[1] = self.estimates[2];\n            self.estimates[2] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n            // Need to iterate one more time. Check if the new best estimate is\n            // outside the window as well, since it may also have been recorded a\n            // long time ago. Don't need to iterate once more since we cover that\n            // case at the beginning of the method.\n            if new_time - self.estimates[0].unwrap().time > self.window_length {\n                self.estimates[0] = self.estimates[1];\n                self.estimates[1] = self.estimates[2];\n            }\n            return;\n        }\n\n        if self.estimates[1].unwrap().sample == self.estimates[0].unwrap().sample &&\n            new_time - self.estimates[1].unwrap().time > self.window_length / 4\n        {\n            // A quarter of the window has passed without a better sample, so the\n            // second-best estimate is taken from the second quarter of the\n            // window.\n            self.estimates[1] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n            self.estimates[2] = self.estimates[1];\n            return;\n        }\n\n        if self.estimates[2].unwrap().sample == self.estimates[1].unwrap().sample &&\n            new_time - self.estimates[2].unwrap().time > self.window_length / 2\n        {\n            // We've passed a half of the window without a better estimate, so\n            // take a third-best estimate from the second half of the\n            // window.\n            self.estimates[2] = Some(Sample {\n                sample: new_sample,\n                time: new_time,\n            });\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn estimate() {\n        let mut now = Instant::now();\n\n        let mut estimator = MaxUtilizedBandwidthEstimator::new();\n\n        assert_eq!(estimator.get().bandwidth, 0);\n        assert!(estimator.estimate.get_best().is_none());\n\n        // First round send 30M, nothing gets acked\n        estimator.new_round(now, 30_000_000, 0, 0);\n\n        assert_eq!(estimator.get().bandwidth, 0); // Not enough rounds for estimate yet\n        assert!(estimator.estimate.get_best().is_none());\n\n        now += Duration::from_secs(30);\n\n        // Send 60M, previous 30M gets acked\n        estimator.new_round(now, 60_000_000, 0, 30_000_000);\n\n        // 30M over 30s = 1MBps = 8Mbps\n        assert_eq!(estimator.get().bandwidth, 0); // Not enough rounds for estimate yet\n        assert_eq!(estimator.estimate.get_best().unwrap().bandwidth, 8_000_000);\n\n        now += Duration::from_secs(30);\n\n        // Send 90M, previous 60M gets acked\n        estimator.new_round(now, 90_000_000, 0, 60_000_000);\n\n        // 90M over 60s = 1.5MBps = 12Mbps\n        assert_eq!(estimator.get().bandwidth, 0); // Not enough rounds for estimate yet\n        assert_eq!(estimator.estimate.get_best().unwrap().bandwidth, 12_000_000);\n\n        now += Duration::from_secs(30);\n\n        // Send 10M, previous 90M gets acked\n        estimator.new_round(now, 30_000_000, 0, 90_000_000);\n\n        // 180M over 90s = 2MBps = 16Mbps\n        assert_eq!(estimator.get().bandwidth, 0); // Not enough rounds for estimate yet\n        assert_eq!(estimator.estimate.get_best().unwrap().bandwidth, 16_000_000);\n\n        for _ in 0..4 {\n            now += Duration::from_secs(30);\n            // Send another 10M, previous 10M gets acked\n            estimator.new_round(now, 30_000_000, 0, 30_000_000);\n            // The bandwidth is lower but it doesn't matter, we record highest\n            // bandwidth, so it remains as before for two minutes\n            assert_eq!(estimator.get().bandwidth, 16_000_000);\n        }\n\n        // After two minutes the filter is updated, and the max bandwidth is\n        // reduced\n        now += Duration::from_secs(30);\n        // Send another 10M, previous 10M gets acked\n        estimator.new_round(now, 30_000_000, 0, 30_000_000);\n\n        assert!(estimator.get().bandwidth < 8 * 2_000_000);\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/io/worker.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::net::SocketAddr;\nuse std::ops::ControlFlow;\nuse std::sync::Arc;\nuse std::task::Poll;\nuse std::time::Duration;\nuse std::time::Instant;\n#[cfg(feature = \"perf-quic-listener-metrics\")]\nuse std::time::SystemTime;\n\nuse super::connection_stage::Close;\nuse super::connection_stage::ConnectionStage;\nuse super::connection_stage::ConnectionStageContext;\nuse super::connection_stage::Handshake;\nuse super::connection_stage::RunningApplication;\nuse super::gso::*;\nuse super::utilization_estimator::BandwidthReporter;\n\nuse crate::metrics::labels;\nuse crate::metrics::Metrics;\nuse crate::quic::connection::ApplicationOverQuic;\nuse crate::quic::connection::HandshakeError;\nuse crate::quic::connection::Incoming;\nuse crate::quic::connection::QuicConnectionStats;\nuse crate::quic::connection::SharedConnectionIdGenerator;\nuse crate::quic::router::ConnectionMapCommand;\nuse crate::quic::QuicheConnection;\nuse crate::QuicResult;\n\nuse boring::ssl::SslRef;\nuse datagram_socket::DatagramSocketSend;\nuse datagram_socket::DatagramSocketSendExt;\nuse datagram_socket::MaybeConnectedSocket;\nuse datagram_socket::QuicAuditStats;\nuse foundations::telemetry::log;\nuse quiche::ConnectionId;\nuse quiche::Error as QuicheError;\nuse quiche::SendInfo;\nuse tokio::select;\nuse tokio::sync::mpsc;\nuse tokio::time;\n\n// Number of incoming packets to be buffered in the incoming channel.\npub(crate) const INCOMING_QUEUE_SIZE: usize = 2048;\n\n// Check if there are any incoming packets while sending data every this number\n// of sent packets\npub(crate) const CHECK_INCOMING_QUEUE_RATIO: usize = INCOMING_QUEUE_SIZE / 16;\n\nconst RELEASE_TIMER_THRESHOLD: Duration = Duration::from_micros(250);\n\n/// Stop queuing GSO packets, if packet size is below this threshold.\nconst GSO_THRESHOLD: usize = 1_000;\n\npub struct WriterConfig {\n    pub pending_cid: Option<ConnectionId<'static>>,\n    pub peer_addr: SocketAddr,\n    pub local_addr: SocketAddr,\n    pub with_gso: bool,\n    pub pacing_offload: bool,\n    pub with_pktinfo: bool,\n}\n\n#[derive(Default)]\npub(crate) struct WriteState {\n    conn_established: bool,\n    bytes_written: usize,\n    segment_size: usize,\n    num_pkts: usize,\n    tx_time: Option<Instant>,\n    has_pending_data: bool,\n    // If pacer schedules packets too far into the future, we want to pause\n    // sending, until the future arrives\n    next_release_time: Option<Instant>,\n    // The selected source and destination addresses for the current write\n    // cycle.\n    selected_path: Option<(SocketAddr, SocketAddr)>,\n    // Iterator over the network paths that haven't been flushed yet.\n    pending_paths: quiche::SocketAddrIter,\n}\n\npub(crate) struct IoWorkerParams<Tx, M> {\n    pub(crate) socket: MaybeConnectedSocket<Tx>,\n    pub(crate) shutdown_tx: mpsc::Sender<()>,\n    pub(crate) cfg: WriterConfig,\n    pub(crate) audit_log_stats: Arc<QuicAuditStats>,\n    pub(crate) write_state: WriteState,\n    pub(crate) conn_map_cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>,\n    pub(crate) cid_generator: Option<SharedConnectionIdGenerator>,\n    #[cfg(feature = \"perf-quic-listener-metrics\")]\n    pub(crate) init_rx_time: Option<SystemTime>,\n    pub(crate) metrics: M,\n}\n\npub(crate) struct IoWorker<Tx, M, S> {\n    socket: MaybeConnectedSocket<Tx>,\n    /// A field that signals to the listener task that the connection has gone\n    /// away (nothing is sent here, listener task just detects the sender\n    /// has dropped)\n    shutdown_tx: mpsc::Sender<()>,\n    cfg: WriterConfig,\n    audit_log_stats: Arc<QuicAuditStats>,\n    write_state: WriteState,\n    conn_map_cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>,\n    cid_generator: Option<SharedConnectionIdGenerator>,\n    #[cfg(feature = \"perf-quic-listener-metrics\")]\n    init_rx_time: Option<SystemTime>,\n    metrics: M,\n    conn_stage: S,\n    bw_estimator: BandwidthReporter,\n}\n\nimpl<Tx, M, S> IoWorker<Tx, M, S>\nwhere\n    Tx: DatagramSocketSend + Send,\n    M: Metrics,\n    S: ConnectionStage,\n{\n    pub(crate) fn new(params: IoWorkerParams<Tx, M>, conn_stage: S) -> Self {\n        let bw_estimator =\n            BandwidthReporter::new(params.metrics.utilized_bandwidth());\n\n        log::trace!(\"Creating IoWorker with stage: {conn_stage:?}\");\n\n        Self {\n            socket: params.socket,\n            shutdown_tx: params.shutdown_tx,\n            cfg: params.cfg,\n            audit_log_stats: params.audit_log_stats,\n            write_state: params.write_state,\n            conn_map_cmd_tx: params.conn_map_cmd_tx,\n            cid_generator: params.cid_generator,\n            #[cfg(feature = \"perf-quic-listener-metrics\")]\n            init_rx_time: params.init_rx_time,\n            metrics: params.metrics,\n            conn_stage,\n            bw_estimator,\n        }\n    }\n\n    fn fill_available_scids(&self, qconn: &mut QuicheConnection) {\n        if qconn.scids_left() == 0 {\n            return;\n        }\n        let Some(cid_generator) = self.cid_generator.as_deref() else {\n            return;\n        };\n\n        let current_cid = qconn.source_id().into_owned();\n        for _ in 0..qconn.scids_left() {\n            // We don't emit stateless resets, so any unguessable value is fine\n            let reset_token = random_u128();\n            let new_cid = cid_generator.new_connection_id();\n\n            if self\n                .conn_map_cmd_tx\n                .send(ConnectionMapCommand::MapCid {\n                    existing_cid: current_cid.clone(),\n                    new_cid: new_cid.clone(),\n                })\n                .is_err()\n            {\n                // Can't do anything if the connection map is gone\n                return;\n            }\n\n            if qconn.new_scid(&new_cid, reset_token, false).is_err() {\n                // This only fails if we have reached the CID limit already\n                return;\n            }\n        }\n    }\n\n    fn unmap_cid(&self, cid: ConnectionId<'static>) {\n        // If the connection map is gone, the ID is already \"unmapped\"\n        let _ = self\n            .conn_map_cmd_tx\n            .send(ConnectionMapCommand::UnmapCid(cid));\n    }\n\n    fn refresh_connection_ids(&self, qconn: &mut QuicheConnection) {\n        // Top up the connection's active CIDs\n        self.fill_available_scids(qconn);\n\n        // Remove retired CIDs from the ingress router\n        while let Some(retired_cid) = qconn.retired_scid_next() {\n            self.unmap_cid(retired_cid);\n        }\n    }\n\n    async fn work_loop<A: ApplicationOverQuic>(\n        &mut self, qconn: &mut QuicheConnection,\n        ctx: &mut ConnectionStageContext<A>,\n    ) -> QuicResult<()> {\n        const DEFAULT_SLEEP: Duration = Duration::from_secs(60);\n        let mut current_deadline: Option<Instant> = None;\n        let sleep = time::sleep(DEFAULT_SLEEP);\n        tokio::pin!(sleep);\n\n        loop {\n            let now = Instant::now();\n\n            self.write_state.has_pending_data = true;\n\n            while self.write_state.has_pending_data {\n                let mut packets_sent = 0;\n\n                // Try to clear all received packets every so often, because\n                // incoming packets contain acks, and because the\n                // receive queue has a very limited size, once it is full incoming\n                // packets get stalled indefinitely\n                let mut did_recv = false;\n                while let Some(pkt) = ctx\n                    .in_pkt\n                    .take()\n                    .or_else(|| ctx.incoming_pkt_receiver.try_recv().ok())\n                {\n                    self.process_incoming(qconn, pkt)?;\n                    did_recv = true;\n                }\n\n                self.conn_stage.on_read(did_recv, qconn, ctx)?;\n                self.refresh_connection_ids(qconn);\n\n                let can_release = match self.write_state.next_release_time {\n                    None => true,\n                    Some(next_release) =>\n                        next_release\n                            .checked_duration_since(now)\n                            .unwrap_or_default() <\n                            RELEASE_TIMER_THRESHOLD,\n                };\n\n                self.write_state.has_pending_data &= can_release;\n\n                while self.write_state.has_pending_data &&\n                    packets_sent < CHECK_INCOMING_QUEUE_RATIO\n                {\n                    self.gather_data_from_quiche_conn(qconn, ctx.buffer())?;\n\n                    // Break if the connection is closed\n                    if qconn.is_closed() {\n                        return Ok(());\n                    }\n\n                    let mut flush_operation_token =\n                        TrackMidHandshakeFlush::new(self.metrics.clone());\n\n                    self.flush_buffer_to_socket(ctx.buffer()).await;\n\n                    flush_operation_token.mark_complete();\n\n                    packets_sent += self.write_state.num_pkts;\n\n                    if let ControlFlow::Break(reason) =\n                        self.conn_stage.on_flush(qconn, ctx)\n                    {\n                        return reason;\n                    }\n                }\n            }\n\n            self.bw_estimator.update(qconn, now);\n\n            self.audit_log_stats\n                .set_max_bandwidth(self.bw_estimator.max_bandwidth);\n            self.audit_log_stats.set_max_loss_pct(\n                (self.bw_estimator.max_loss_pct * 100_f32).round() as u8,\n            );\n\n            let new_deadline = min_of_some(\n                qconn.timeout_instant(),\n                self.write_state.next_release_time,\n            );\n            let new_deadline =\n                min_of_some(new_deadline, self.conn_stage.wait_deadline());\n\n            if new_deadline != current_deadline {\n                current_deadline = new_deadline;\n\n                sleep\n                    .as_mut()\n                    .reset(new_deadline.unwrap_or(now + DEFAULT_SLEEP).into());\n            }\n\n            let incoming_recv = &mut ctx.incoming_pkt_receiver;\n            let application = &mut ctx.application;\n\n            select! {\n                biased;\n                () = &mut sleep => {\n                    // It's very important that we keep the timeout arm at the top of this loop so\n                    // that we poll it every time we need to. Since this is a biased `select!`, if\n                    // we put this behind another arm, we could theoretically starve the sleep arm\n                    // and hang connections.\n                    //\n                    // See https://docs.rs/tokio/latest/tokio/macro.select.html#fairness for more\n                    qconn.on_timeout();\n\n                    self.write_state.next_release_time = None;\n                    current_deadline = None;\n                    sleep.as_mut().reset((now + DEFAULT_SLEEP).into());\n                }\n                Some(pkt) = incoming_recv.recv() => ctx.in_pkt = Some(pkt),\n                directive = self.wait_for_data_or_handshake(qconn, application) => {\n                    match directive? {\n                        WaitForDataOrHandshakeDirective::Flush => {\n                            self.flush_buffer_to_socket(application.buffer()).await;\n                        }\n                        WaitForDataOrHandshakeDirective::Noop => {}\n                    }\n                },\n            };\n\n            if let ControlFlow::Break(reason) = self.conn_stage.post_wait(qconn) {\n                return reason;\n            }\n        }\n    }\n\n    #[cfg(feature = \"perf-quic-listener-metrics\")]\n    fn measure_complete_handshake_time(&mut self) {\n        if let Some(init_rx_time) = self.init_rx_time.take() {\n            if let Ok(delta) = init_rx_time.elapsed() {\n                self.metrics\n                    .handshake_time_seconds(\n                        labels::QuicHandshakeStage::HandshakeResponse,\n                    )\n                    .observe(delta.as_nanos() as u64);\n            }\n        }\n    }\n\n    fn gather_data_from_quiche_conn(\n        &mut self, qconn: &mut QuicheConnection, send_buf: &mut [u8],\n    ) -> QuicResult<usize> {\n        let mut segment_size = None;\n        let mut send_info = None;\n\n        self.write_state.num_pkts = 0;\n        self.write_state.bytes_written = 0;\n\n        self.write_state.selected_path = None;\n\n        let now = Instant::now();\n\n        let send_buf = {\n            let trunc = UDP_MAX_GSO_PACKET_SIZE.min(send_buf.len());\n            &mut send_buf[..trunc]\n        };\n\n        #[cfg(feature = \"gcongestion\")]\n        let gcongestion_enabled = true;\n\n        #[cfg(not(feature = \"gcongestion\"))]\n        let gcongestion_enabled = qconn.gcongestion_enabled().unwrap_or(false);\n\n        let initial_release_decision = if gcongestion_enabled {\n            let initial_release_decision = qconn\n                .get_next_release_time()\n                .filter(|_| self.pacing_enabled(qconn));\n\n            if let Some(future_release_time) =\n                initial_release_decision.as_ref().and_then(|v| v.time(now))\n            {\n                let max_into_fut = qconn.max_release_into_future();\n\n                if future_release_time.duration_since(now) >= max_into_fut {\n                    self.write_state.next_release_time =\n                        Some(now + max_into_fut.mul_f32(0.8));\n                    self.write_state.has_pending_data = false;\n                    return Ok(0);\n                }\n            }\n\n            initial_release_decision\n        } else {\n            None\n        };\n\n        let buffer_write_outcome = loop {\n            let outcome = self.write_packet_to_buffer(\n                qconn,\n                send_buf,\n                &mut send_info,\n                segment_size,\n            );\n\n            let packet_size = match outcome {\n                Ok(0) => break Ok(0),\n\n                Ok(bytes_written) => bytes_written,\n\n                Err(e) => break Err(e),\n            };\n\n            // Flush to network after generating a single packet when GSO\n            // is disabled.\n            if !self.cfg.with_gso {\n                break outcome;\n            }\n\n            #[cfg(not(feature = \"gcongestion\"))]\n            let max_send_size = if !gcongestion_enabled {\n                // Only call qconn.send_quantum when !gcongestion_enabled.\n                tune_max_send_size(\n                    segment_size,\n                    qconn.send_quantum(),\n                    send_buf.len(),\n                )\n            } else {\n                usize::MAX\n            };\n\n            #[cfg(feature = \"gcongestion\")]\n            let max_send_size = usize::MAX;\n\n            // If segment_size is known, update the maximum of\n            // GSO sender buffer size to the multiple of\n            // segment_size.\n            let buffer_is_full = self.write_state.num_pkts ==\n                UDP_MAX_SEGMENT_COUNT ||\n                self.write_state.bytes_written >= max_send_size;\n\n            if buffer_is_full {\n                break outcome;\n            }\n\n            // Flush to network when the newly generated packet size is\n            // different from previously written packet, as GSO needs packets\n            // to have the same size, except for the last one in the buffer.\n            // The last packet may be smaller than the previous size.\n            match segment_size {\n                Some(size)\n                    if packet_size != size || packet_size < GSO_THRESHOLD =>\n                    break outcome,\n                None => segment_size = Some(packet_size),\n                _ => (),\n            }\n\n            if gcongestion_enabled {\n                // If the release time of next packet is different, or it can't be\n                // part of a burst, start the next batch\n                if let Some(initial_release_decision) = initial_release_decision {\n                    match qconn.get_next_release_time() {\n                        Some(release)\n                            if release.can_burst() ||\n                                release.time_eq(\n                                    &initial_release_decision,\n                                    now,\n                                ) => {},\n                        _ => break outcome,\n                    }\n                }\n            }\n        };\n\n        let tx_time = if gcongestion_enabled {\n            initial_release_decision\n                .filter(|_| self.pacing_enabled(qconn))\n                // Return the time from the release decision if release_decision.time > now, else None.\n                .and_then(|v| v.time(now))\n        } else {\n            send_info\n                .filter(|_| self.pacing_enabled(qconn))\n                .map(|v| v.at)\n        };\n\n        self.write_state.conn_established = qconn.is_established();\n        self.write_state.tx_time = tx_time;\n        self.write_state.segment_size =\n            segment_size.unwrap_or(self.write_state.bytes_written);\n\n        if !gcongestion_enabled {\n            if let Some(time) = tx_time {\n                const DEFAULT_MAX_INTO_FUTURE: Duration =\n                    Duration::from_millis(1);\n                if time\n                    .checked_duration_since(now)\n                    .map(|d| d > DEFAULT_MAX_INTO_FUTURE)\n                    .unwrap_or(false)\n                {\n                    self.write_state.next_release_time =\n                        Some(now + DEFAULT_MAX_INTO_FUTURE.mul_f32(0.8));\n                    self.write_state.has_pending_data = false;\n                    return Ok(0);\n                }\n            }\n        }\n\n        buffer_write_outcome\n    }\n\n    /// Selects a network path, if none already selected.\n    ///\n    /// This will return the first path available in the write state's\n    /// `pending_paths` iterator. If that is empty a new iterator will be\n    /// created by querying quiche itself.\n    ///\n    /// Note that the connection's statically configured local address will be\n    /// used to query quiche for available paths, so this can't handle multiple\n    /// local addresses currently.\n    fn select_path(\n        &mut self, qconn: &QuicheConnection,\n    ) -> Option<(SocketAddr, SocketAddr)> {\n        if self.write_state.selected_path.is_some() {\n            return self.write_state.selected_path;\n        }\n\n        let from = self.cfg.local_addr;\n\n        // Initialize paths iterator.\n        if self.write_state.pending_paths.len() == 0 {\n            self.write_state.pending_paths = qconn.paths_iter(from);\n        }\n\n        let to = self.write_state.pending_paths.next()?;\n\n        Some((from, to))\n    }\n\n    #[cfg(not(feature = \"gcongestion\"))]\n    fn pacing_enabled(&self, qconn: &QuicheConnection) -> bool {\n        self.cfg.pacing_offload && qconn.pacing_enabled()\n    }\n\n    #[cfg(feature = \"gcongestion\")]\n    fn pacing_enabled(&self, _qconn: &QuicheConnection) -> bool {\n        self.cfg.pacing_offload\n    }\n\n    fn write_packet_to_buffer(\n        &mut self, qconn: &mut QuicheConnection, send_buf: &mut [u8],\n        send_info: &mut Option<SendInfo>, segment_size: Option<usize>,\n    ) -> QuicResult<usize> {\n        let mut send_buf = &mut send_buf[self.write_state.bytes_written..];\n        if send_buf.len() > segment_size.unwrap_or(usize::MAX) {\n            // Never let the buffer be longer than segment size, for GSO to\n            // function properly.\n            send_buf = &mut send_buf[..segment_size.unwrap_or(usize::MAX)];\n        }\n\n        // On the first call to `select_path()` a path will be chosen based on\n        // the local address the connection initially landed on. Once a path is\n        // selected following calls to `select_path()` will return it, until it\n        // is reset at the start of the next write cycle.\n        //\n        // The path is then passed to `send_on_path()` which will only generate\n        // packets meant for that path, this way a single GSO buffer will only\n        // contain packets that belong to the same network path, which is\n        // required because the from/to addresses for each `sendmsg()` call\n        // apply to the whole GSO buffer.\n        let (from, to) = self.select_path(qconn).unzip();\n\n        match qconn.send_on_path(send_buf, from, to) {\n            Ok((packet_size, info)) => {\n                let _ = send_info.get_or_insert(info);\n\n                self.write_state.bytes_written += packet_size;\n                self.write_state.num_pkts += 1;\n\n                let from = send_info.as_ref().map(|info| info.from);\n                let to = send_info.as_ref().map(|info| info.to);\n\n                self.write_state.selected_path = from.zip(to);\n\n                self.write_state.has_pending_data = true;\n\n                Ok(packet_size)\n            },\n\n            Err(QuicheError::Done) => {\n                // Flush the current buffer to network. If no other path needs\n                // to be flushed to the network also yield the work loop task.\n                //\n                // Otherwise the write loop will start again and the next path\n                // will be selected.\n                let has_pending_paths = self.write_state.pending_paths.len() > 0;\n\n                // Keep writing if there are paths left to try.\n                self.write_state.has_pending_data = has_pending_paths;\n\n                Ok(0)\n            },\n\n            Err(e) => {\n                let error_code = if let Some(local_error) = qconn.local_error() {\n                    local_error.error_code\n                } else {\n                    let internal_error_code =\n                        quiche::WireErrorCode::InternalError as u64;\n                    let _ = qconn.close(false, internal_error_code, &[]);\n\n                    internal_error_code\n                };\n\n                self.audit_log_stats\n                    .set_sent_conn_close_transport_error_code(error_code as i64);\n\n                Err(Box::new(e))\n            },\n        }\n    }\n\n    async fn flush_buffer_to_socket(&mut self, send_buf: &[u8]) {\n        if self.write_state.bytes_written > 0 {\n            let current_send_buf = &send_buf[..self.write_state.bytes_written];\n\n            let (from, to) = self.write_state.selected_path.unzip();\n\n            let to = to.unwrap_or(self.cfg.peer_addr);\n            let from = from.filter(|_| self.cfg.with_pktinfo);\n\n            let send_res = if let (Some(udp_socket), true) =\n                (self.socket.as_udp_socket(), self.cfg.with_gso)\n            {\n                // Only UDP supports GSO.\n                send_to(\n                    udp_socket,\n                    to,\n                    from,\n                    current_send_buf,\n                    self.write_state.segment_size,\n                    self.write_state.tx_time,\n                    self.metrics\n                        .write_errors(labels::QuicWriteError::WouldBlock),\n                    self.metrics.send_to_wouldblock_duration_s(),\n                )\n                .await\n            } else {\n                self.socket.send_to(current_send_buf, to).await\n            };\n\n            #[cfg(feature = \"perf-quic-listener-metrics\")]\n            self.measure_complete_handshake_time();\n\n            match send_res {\n                Ok(n) =>\n                    if n < self.write_state.bytes_written {\n                        self.metrics\n                            .write_errors(labels::QuicWriteError::Partial)\n                            .inc();\n                    },\n\n                Err(_) => {\n                    self.metrics.write_errors(labels::QuicWriteError::Err).inc();\n                },\n            }\n        }\n    }\n\n    /// Process the incoming packet\n    fn process_incoming(\n        &mut self, qconn: &mut QuicheConnection, mut pkt: Incoming,\n    ) -> QuicResult<()> {\n        let recv_info = quiche::RecvInfo {\n            from: pkt.peer_addr,\n            to: pkt.local_addr,\n        };\n\n        if let Some(gro) = pkt.gro {\n            for dgram in pkt.buf.chunks_mut(gro as usize) {\n                qconn.recv(dgram, recv_info)?;\n            }\n        } else {\n            qconn.recv(&mut pkt.buf, recv_info)?;\n        }\n\n        Ok(())\n    }\n\n    // When a connection is established, process application data, if not the task\n    // is probably polled following a wakeup from boring, so we check if quiche\n    // has any handshake packets to send.\n    //\n    // TODO(erittenhouse): would be nice to decouple wait_for_data from the\n    // application, but wait_for_quiche relies on IOW methods, so we can't write a\n    // default implementation for ConnectionStage\n    async fn wait_for_data_or_handshake<A: ApplicationOverQuic>(\n        &mut self, qconn: &mut QuicheConnection, quic_application: &mut A,\n    ) -> QuicResult<WaitForDataOrHandshakeDirective> {\n        if quic_application.should_act() {\n            // Poll the application to make progress.\n            //\n            // Once the connection has been established (i.e. the handshake is\n            // complete), we only poll the application.\n            //\n            // The exception is 0-RTT in TLS 1.3, where the full handshake is\n            // still in progress but we have 0-RTT keys to process early data.\n            // This means TLS callbacks might only be polled on the next timeout\n            // or when a packet is received from the peer.\n            quic_application.wait_for_data(qconn).await?;\n            Ok(WaitForDataOrHandshakeDirective::Noop)\n        } else {\n            // Poll quiche to make progress on handshake callbacks.\n            self.wait_for_quiche(qconn, quic_application.buffer())\n                .await?;\n            Ok(WaitForDataOrHandshakeDirective::Flush)\n        }\n    }\n\n    /// Check if Quiche has any packets to send\n    ///\n    /// If yes: fills buffer and updates self.write_state.bytes_written\n    /// If no: Poll::Pending\n    ///\n    /// # Example\n    ///\n    /// This function can be used, for example, to drive an asynchronous TLS\n    /// handshake. Each call to `gather_data_from_quiche_conn` attempts to\n    /// progress the handshake via a call to `quiche::Connection.send()` -\n    /// once one of the `gather_data_from_quiche_conn()` calls writes to the\n    /// send buffer, we signal to the caller which has to take care of flushing\n    async fn wait_for_quiche(\n        &mut self, qconn: &mut QuicheConnection, buffer: &mut [u8],\n    ) -> QuicResult<()> {\n        std::future::poll_fn(|_| {\n            match self.gather_data_from_quiche_conn(qconn, buffer) {\n                Ok(bytes_written) => {\n                    // We need to avoid consecutive calls to gather(), which write\n                    // data to the buffer, without a flush().\n                    // If we don't avoid those consecutive calls, we end\n                    // up overwriting data in the buffer or unnecessarily waiting\n                    // for more calls to drive_handshake()\n                    // before calling the handshake complete.\n                    if bytes_written == 0 && self.write_state.bytes_written == 0 {\n                        Poll::Pending\n                    } else {\n                        Poll::Ready(Ok(()))\n                    }\n                },\n                _ => Poll::Ready(Err(quiche::Error::TlsFail)),\n            }\n        })\n        .await?;\n        Ok(())\n    }\n}\n\n/// Whether caller of [`wait_for_data_or_handshake`] is required to\n/// call [`flush_buffer_to_socket`]\n#[must_use]\nenum WaitForDataOrHandshakeDirective {\n    Noop,\n    Flush,\n}\n\npub struct Running<Tx, M, A> {\n    pub(crate) params: IoWorkerParams<Tx, M>,\n    pub(crate) context: ConnectionStageContext<A>,\n    pub(crate) qconn: QuicheConnection,\n}\n\nimpl<Tx, M, A> Running<Tx, M, A> {\n    pub fn ssl(&mut self) -> &mut SslRef {\n        self.qconn.as_mut()\n    }\n}\n\npub(crate) struct Closing<Tx, M, A> {\n    pub(crate) params: IoWorkerParams<Tx, M>,\n    pub(crate) context: ConnectionStageContext<A>,\n    pub(crate) work_loop_result: QuicResult<()>,\n    pub(crate) qconn: QuicheConnection,\n}\n\npub enum RunningOrClosing<Tx, M, A> {\n    Running(Running<Tx, M, A>),\n    Closing(Closing<Tx, M, A>),\n}\n\nimpl<Tx, M> IoWorker<Tx, M, Handshake>\nwhere\n    Tx: DatagramSocketSend + Send,\n    M: Metrics,\n{\n    pub(crate) async fn run<A>(\n        mut self, mut qconn: QuicheConnection, mut ctx: ConnectionStageContext<A>,\n    ) -> RunningOrClosing<Tx, M, A>\n    where\n        A: ApplicationOverQuic,\n    {\n        // This makes an assumption that the waker being set in ex_data is stable\n        // across the active task's lifetime. Moving a future that encompasses an\n        // async callback from this task across a channel, for example, will\n        // cause issues as this waker will then be stale and attempt to\n        // wake the wrong task.\n        std::future::poll_fn(|cx| {\n            let ssl = qconn.as_mut();\n            ssl.set_task_waker(Some(cx.waker().clone()));\n\n            Poll::Ready(())\n        })\n        .await;\n\n        #[cfg(target_os = \"linux\")]\n        if let Some(incoming) = ctx.in_pkt.as_mut() {\n            self.audit_log_stats\n                .set_initial_so_mark_data(incoming.so_mark_data.take());\n        }\n\n        let mut work_loop_result = self.work_loop(&mut qconn, &mut ctx).await;\n        if work_loop_result.is_ok() && qconn.is_closed() {\n            work_loop_result = Err(HandshakeError::ConnectionClosed.into());\n        }\n\n        if let Err(err) = &work_loop_result {\n            self.metrics.failed_handshakes(err.into()).inc();\n\n            return RunningOrClosing::Closing(Closing {\n                params: self.into(),\n                context: ctx,\n                work_loop_result,\n                qconn,\n            });\n        };\n\n        match self.on_conn_established(&mut qconn, &mut ctx.application) {\n            Ok(()) => RunningOrClosing::Running(Running {\n                params: self.into(),\n                context: ctx,\n                qconn,\n            }),\n            Err(e) => {\n                foundations::telemetry::log::warn!(\n                    \"Handshake stage on_connection_established failed\"; \"error\"=>%e\n                );\n\n                RunningOrClosing::Closing(Closing {\n                    params: self.into(),\n                    context: ctx,\n                    work_loop_result,\n                    qconn,\n                })\n            },\n        }\n    }\n\n    fn on_conn_established<App: ApplicationOverQuic>(\n        &mut self, qconn: &mut QuicheConnection, driver: &mut App,\n    ) -> QuicResult<()> {\n        // Only calculate the QUIC handshake duration and call the driver's\n        // on_conn_established hook if this is the first time\n        // is_established == true.\n        if self.audit_log_stats.transport_handshake_duration_us() == -1 {\n            self.conn_stage.handshake_info.set_elapsed();\n            let handshake_info = &self.conn_stage.handshake_info;\n\n            self.audit_log_stats\n                .set_transport_handshake_duration(handshake_info.elapsed());\n\n            driver.on_conn_established(qconn, handshake_info)?;\n        }\n\n        if let Some(cid) = self.cfg.pending_cid.take() {\n            self.unmap_cid(cid);\n        }\n\n        Ok(())\n    }\n}\n\nimpl<Tx, M, S> From<IoWorker<Tx, M, S>> for IoWorkerParams<Tx, M> {\n    fn from(value: IoWorker<Tx, M, S>) -> Self {\n        Self {\n            socket: value.socket,\n            shutdown_tx: value.shutdown_tx,\n            cfg: value.cfg,\n            audit_log_stats: value.audit_log_stats,\n            write_state: value.write_state,\n            conn_map_cmd_tx: value.conn_map_cmd_tx,\n            cid_generator: value.cid_generator,\n            #[cfg(feature = \"perf-quic-listener-metrics\")]\n            init_rx_time: value.init_rx_time,\n            metrics: value.metrics,\n        }\n    }\n}\n\nimpl<Tx, M> IoWorker<Tx, M, RunningApplication>\nwhere\n    Tx: DatagramSocketSend + Send,\n    M: Metrics,\n{\n    pub(crate) async fn run<A: ApplicationOverQuic>(\n        mut self, mut qconn: QuicheConnection, mut ctx: ConnectionStageContext<A>,\n    ) -> Closing<Tx, M, A> {\n        // Perform a single call to process_reads()/process_writes(),\n        // unconditionally, to ensure that any application data (e.g.\n        // STREAM frames or datagrams) processed by the Handshake\n        // stage are properly passed to the application.\n        if let Err(e) = self.conn_stage.on_read(true, &mut qconn, &mut ctx) {\n            return Closing {\n                params: self.into(),\n                context: ctx,\n                work_loop_result: Err(e),\n                qconn,\n            };\n        };\n\n        let work_loop_result = self.work_loop(&mut qconn, &mut ctx).await;\n\n        Closing {\n            params: self.into(),\n            context: ctx,\n            work_loop_result,\n            qconn,\n        }\n    }\n}\n\nimpl<Tx, M> IoWorker<Tx, M, Close>\nwhere\n    Tx: DatagramSocketSend + Send,\n    M: Metrics,\n{\n    pub(crate) async fn close<A: ApplicationOverQuic>(\n        mut self, qconn: &mut QuicheConnection,\n        ctx: &mut ConnectionStageContext<A>,\n    ) {\n        if self.conn_stage.work_loop_result.is_ok() &&\n            self.bw_estimator.max_bandwidth > 0\n        {\n            let metrics = &self.metrics;\n\n            metrics\n                .max_bandwidth_mbps()\n                .observe(self.bw_estimator.max_bandwidth as f64 * 1e-6);\n\n            metrics\n                .max_loss_pct()\n                .observe(self.bw_estimator.max_loss_pct as f64 * 100.);\n        }\n\n        if ctx.application.should_act() {\n            ctx.application.on_conn_close(\n                qconn,\n                &self.metrics,\n                &self.conn_stage.work_loop_result,\n            );\n        }\n\n        // TODO: this assumes that the tidy_up operation can be completed in one\n        // send (ignoring flow/congestion control constraints). We should\n        // guarantee that it gets sent by doublechecking the\n        // gathered/flushed byte totals and retry if they don't match.\n        let _ = self.gather_data_from_quiche_conn(qconn, ctx.buffer());\n        self.flush_buffer_to_socket(ctx.buffer()).await;\n\n        *ctx.stats.lock().unwrap() = QuicConnectionStats::from_conn(qconn);\n\n        if let Some(err) = qconn.peer_error() {\n            if err.is_app {\n                self.audit_log_stats\n                    .set_recvd_conn_close_application_error_code(\n                        err.error_code as _,\n                    );\n            } else {\n                self.audit_log_stats\n                    .set_recvd_conn_close_transport_error_code(\n                        err.error_code as _,\n                    );\n            }\n        }\n\n        if let Some(err) = qconn.local_error() {\n            if err.is_app {\n                self.audit_log_stats\n                    .set_sent_conn_close_application_error_code(\n                        err.error_code as _,\n                    );\n            } else {\n                self.audit_log_stats\n                    .set_sent_conn_close_transport_error_code(\n                        err.error_code as _,\n                    );\n            }\n        }\n\n        self.close_connection(qconn);\n\n        if let Err(work_loop_error) = self.conn_stage.work_loop_result {\n            self.audit_log_stats\n                .set_connection_close_reason(work_loop_error);\n        }\n    }\n\n    fn close_connection(&mut self, qconn: &mut QuicheConnection) {\n        if let Some(cid) = self.cfg.pending_cid.take() {\n            self.unmap_cid(cid);\n        }\n        while let Some(retired_cid) = qconn.retired_scid_next() {\n            self.unmap_cid(retired_cid);\n        }\n        for cid in qconn.source_ids().cloned() {\n            self.unmap_cid(cid.into_owned());\n        }\n\n        self.metrics.connections_in_memory().dec();\n    }\n}\n\n/// Returns the minimum of `v1` and `v2`, ignoring `None`s.\nfn min_of_some<T: Ord>(v1: Option<T>, v2: Option<T>) -> Option<T> {\n    match (v1, v2) {\n        (Some(a), Some(b)) => Some(a.min(b)),\n        (Some(v), _) | (_, Some(v)) => Some(v),\n        (None, None) => None,\n    }\n}\n\n/// A Token which increment the skipped_mid_handshake_flush_count metric on\n/// `Drop` unless it is marked complete.\nstruct TrackMidHandshakeFlush<M: Metrics> {\n    complete: bool,\n    metrics: M,\n}\n\nimpl<M: Metrics> TrackMidHandshakeFlush<M> {\n    fn new(metrics: M) -> Self {\n        Self {\n            complete: false,\n            metrics,\n        }\n    }\n\n    fn mark_complete(&mut self) {\n        self.complete = true;\n    }\n}\n\nimpl<M: Metrics> Drop for TrackMidHandshakeFlush<M> {\n    fn drop(&mut self) {\n        if !self.complete {\n            self.metrics.skipped_mid_handshake_flush_count().inc();\n        }\n    }\n}\n\nfn random_u128() -> u128 {\n    let mut buf = [0; 16];\n    boring::rand::rand_bytes(&mut buf).expect(\"boring's RAND_bytes never fails\");\n    u128::from_ne_bytes(buf)\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! `async`-ified QUIC connections powered by [quiche].\n//!\n//! Hooking up a [quiche::Connection] to [tokio]'s executor and IO primitives\n//! requires an [`ApplicationOverQuic`] to control the connection. The\n//! application exposes a small number of callbacks which are executed whenever\n//! there is work to do with the connection.\n//!\n//! The primary entrypoints to set up a connection are [`listen`][listen] for\n//! servers and [`connect`] for clients.\n//! [`listen_with_capabilities`](crate::listen_with_capabilities)\n//! and [`connect_with_config`] exist for scenarios that require more in-depth\n//! configuration. Lastly, the [`raw`] submodule allows users to take full\n//! control over connection creation and its ingress path.\n//!\n//! # QUIC Connection Internals\n//!\n//! ![QUIC Worker Setup](https://github.com/cloudflare/quiche/blob/master/tokio-quiche/docs/worker.png?raw=true)\n//!\n//! *Note: Internal details are subject to change between minor versions.*\n//!\n//! tokio-quiche conceptually separates a network socket into a `recv` half and\n//! a `send` half. The `recv` half can only sensibly be used by one async task\n//! at a time, while many tasks can `send` packets on the socket concurrently.\n//! Thus, we spawn a dedicated `InboundPacketRouter` task for each socket which\n//! becomes the sole owner of the socket's `recv` half. It decodes the QUIC\n//! header in each packet, looks up the destination connection ID (DCID), and\n//! forwards the packet to the connection's `IoWorker` task.\n//!\n//! If the packet initiates a new connection, it is passed to an\n//! `InitialPacketHandler` with logic for either the client- or server-side\n//! connection setup. The purple `ConnectionAcceptor` depicted above is the\n//! server-side implementation. It optionally validates the client's IP\n//! address with a `RETRY` packet before packaging the nascent connection into\n//! an [`InitialQuicConnection`][iqc] and sending it to the\n//! [`QuicConnectionStream`] returned by [`listen`][listen].\n//!\n//! At this point the caller of [`listen`][listen] has control of the\n//! [`InitialQuicConnection`][iqc] (`IQC`). Now an `IoWorker` task needs to be\n//! spawned to continue driving the connection. This is possible with\n//! `IQC::handshake` or `IQC::start` (see the [`InitialQuicConnection`][iqc]\n//! docs). Client-side connections use the same infrastructure (except for the\n//! `InitialPacketHandler`), but [`connect`] immediately consumes the\n//! [`QuicConnectionStream`] and calls `IQC::start`.\n//!\n//! `IoWorker` is responsible for feeding inbound packets into the underlying\n//! [`quiche::Connection`], executing the [`ApplicationOverQuic`] callbacks, and\n//! flushing outbound packets to the network via the socket's shared `send`\n//! half. It loops through these operations in the order shown above, yielding\n//! only when sending packets and on `wait_for_data` calls. New inbound packets\n//! or a timeout can also restart the loop while `wait_for_data` is pending.\n//! This continues until the connection is closed or the [`ApplicationOverQuic`]\n//! returns an error.\n//!\n//! [listen]: crate::listen\n//! [iqc]: crate::InitialQuicConnection\n\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse datagram_socket::DatagramSocketRecv;\nuse datagram_socket::DatagramSocketSend;\nuse foundations::telemetry::log;\n\nuse crate::http3::settings::Http3Settings;\nuse crate::metrics::DefaultMetrics;\nuse crate::metrics::Metrics;\nuse crate::settings::Config;\nuse crate::socket::QuicListener;\nuse crate::socket::Socket;\nuse crate::ClientH3Controller;\nuse crate::ClientH3Driver;\nuse crate::ConnectionParams;\nuse crate::QuicConnectionStream;\nuse crate::QuicResult;\nuse crate::QuicResultExt;\n\nmod addr_validation_token;\npub(crate) mod connection;\nmod hooks;\nmod io;\npub mod raw;\nmod router;\n\nuse self::connection::ApplicationOverQuic;\nuse self::connection::ConnectionIdGenerator as _;\nuse self::connection::QuicConnection;\nuse self::router::acceptor::ConnectionAcceptor;\nuse self::router::acceptor::ConnectionAcceptorConfig;\nuse self::router::connector::ClientConnector;\nuse self::router::InboundPacketRouter;\n\npub use self::connection::ConnectionShutdownBehaviour;\npub use self::connection::HandshakeError;\npub use self::connection::HandshakeInfo;\npub use self::connection::Incoming;\npub use self::connection::QuicCommand;\npub use self::connection::QuicConnectionStats;\npub use self::connection::SimpleConnectionIdGenerator;\npub use self::hooks::ConnectionHook;\n\n/// Alias of [quiche::Connection] used internally by the crate.\n#[cfg(feature = \"zero-copy\")]\npub type QuicheConnection = quiche::Connection<crate::buf_factory::BufFactory>;\n/// Alias of [quiche::Connection] used internally by the crate.\n#[cfg(not(feature = \"zero-copy\"))]\npub type QuicheConnection = quiche::Connection;\n\nfn make_qlog_writer(\n    dir: &str, id: &str,\n) -> std::io::Result<std::io::BufWriter<std::fs::File>> {\n    let mut path = std::path::PathBuf::from(dir);\n    let filename = format!(\"{id}.sqlog\");\n    path.push(filename);\n\n    let f = std::fs::File::create(&path)?;\n    Ok(std::io::BufWriter::new(f))\n}\n\n/// Connects to an HTTP/3 server using `socket` and the default client\n/// configuration.\n///\n/// This function always uses the [`ApplicationOverQuic`] provided in\n/// [`http3::driver`](crate::http3::driver) and returns a corresponding\n/// [ClientH3Controller]. To specify a different implementation or customize the\n/// configuration, use [`connect_with_config`].\n///\n/// # Note\n/// tokio-quiche currently only supports one client connection per socket.\n/// Sharing a socket among multiple connections will lead to lost packets as\n/// both connections try to read from the shared socket.\npub async fn connect<Tx, Rx, S>(\n    socket: S, host: Option<&str>,\n) -> QuicResult<(QuicConnection, ClientH3Controller)>\nwhere\n    Tx: DatagramSocketSend + Send + 'static,\n    Rx: DatagramSocketRecv + Unpin + 'static,\n    S: TryInto<Socket<Tx, Rx>>,\n    S::Error: std::error::Error + Send + Sync + 'static,\n{\n    // Don't apply_max_capabilities(): some NICs don't support GSO\n    let socket: Socket<Tx, Rx> = socket.try_into()?;\n\n    let (h3_driver, h3_controller) =\n        ClientH3Driver::new(Http3Settings::default());\n    let mut params = ConnectionParams::default();\n    params.settings.max_idle_timeout = Some(Duration::from_secs(30));\n\n    Ok((\n        connect_with_config(socket, host, &params, h3_driver).await?,\n        h3_controller,\n    ))\n}\n\n/// Connects to a QUIC server using `socket` and the provided\n/// [`ApplicationOverQuic`].\n///\n/// When the future resolves, the connection has completed its handshake and\n/// `app` is running in the worker task. In case the handshake failed, we close\n/// the connection automatically and the future will resolve with an error.\n///\n/// # Note\n/// tokio-quiche currently only supports one client connection per socket.\n/// Sharing a socket among multiple connections will lead to lost packets as\n/// both connections try to read from the shared socket.\npub async fn connect_with_config<Tx, Rx, App>(\n    socket: Socket<Tx, Rx>, host: Option<&str>, params: &ConnectionParams<'_>,\n    app: App,\n) -> QuicResult<QuicConnection>\nwhere\n    Tx: DatagramSocketSend + Send + 'static,\n    Rx: DatagramSocketRecv + Unpin + 'static,\n    App: ApplicationOverQuic,\n{\n    let mut client_config = Config::new(params, socket.capabilities)?;\n    let scid = SimpleConnectionIdGenerator.new_connection_id();\n\n    #[cfg(feature = \"zero-copy\")]\n    let mut quiche_conn = quiche::connect_with_buffer_factory(\n        host,\n        &scid,\n        socket.local_addr,\n        socket.peer_addr,\n        client_config.as_mut(),\n    )?;\n\n    #[cfg(not(feature = \"zero-copy\"))]\n    let mut quiche_conn = quiche::connect(\n        host,\n        &scid,\n        socket.local_addr,\n        socket.peer_addr,\n        client_config.as_mut(),\n    )?;\n\n    log::info!(\"created unestablished quiche::Connection\"; \"scid\" => ?scid);\n\n    if let Some(session) = &params.session {\n        quiche_conn.set_session(session).map_err(|error| {\n            log::error!(\"application provided an invalid session\"; \"error\"=>?error);\n            quiche::Error::CryptoFail\n        })?;\n    }\n\n    // Set the qlog writer here instead of in the `ClientConnector` to avoid\n    // missing logs from early in the connection\n    if let Some(qlog_dir) = &client_config.qlog_dir {\n        log::info!(\"setting up qlogs\"; \"qlog_dir\"=>qlog_dir);\n        let id = format!(\"{:?}\", &scid);\n        if let Ok(writer) = make_qlog_writer(qlog_dir, &id) {\n            quiche_conn.set_qlog(\n                std::boxed::Box::new(writer),\n                \"tokio-quiche qlog\".to_string(),\n                format!(\"tokio-quiche qlog id={id}\"),\n            );\n        }\n    }\n\n    // Set the keylog file here for the same reason\n    if let Some(keylog_file) = &client_config.keylog_file {\n        log::info!(\"setting up keylog file\");\n        if let Ok(keylog_clone) = keylog_file.try_clone() {\n            quiche_conn.set_keylog(Box::new(keylog_clone));\n        }\n    }\n\n    let socket_tx = Arc::new(socket.send);\n    let socket_rx = socket.recv;\n\n    let (router, mut quic_connection_stream) = InboundPacketRouter::new(\n        client_config,\n        Arc::clone(&socket_tx),\n        socket_rx,\n        socket.local_addr,\n        ClientConnector::new(socket_tx, quiche_conn),\n        DefaultMetrics,\n    );\n\n    // drive the packet router:\n    tokio::spawn(async move {\n        match router.await {\n            Ok(()) => log::debug!(\"incoming packet router finished\"),\n            Err(error) => {\n                log::error!(\"incoming packet router failed\"; \"error\"=>error)\n            },\n        }\n    });\n\n    Ok(quic_connection_stream\n        .recv()\n        .await\n        .ok_or(\"unable to establish connection\")??\n        .start(app))\n}\n\npub(crate) fn start_listener<M>(\n    socket: QuicListener, params: &ConnectionParams, metrics: M,\n) -> std::io::Result<QuicConnectionStream<M>>\nwhere\n    M: Metrics,\n{\n    #[cfg(unix)]\n    assert!(\n        datagram_socket::is_nonblocking(&socket).unwrap_or_default(),\n        \"O_NONBLOCK should be set for the listening socket\"\n    );\n\n    let config = Config::new(params, socket.capabilities).into_io()?;\n\n    let local_addr = socket.socket.local_addr()?;\n    let socket_tx = Arc::new(socket.socket);\n    let socket_rx = Arc::clone(&socket_tx);\n\n    let acceptor = ConnectionAcceptor::new(\n        ConnectionAcceptorConfig {\n            disable_client_ip_validation: config.disable_client_ip_validation,\n            qlog_dir: config.qlog_dir.clone(),\n            keylog_file: config\n                .keylog_file\n                .as_ref()\n                .and_then(|f| f.try_clone().ok()),\n            #[cfg(target_os = \"linux\")]\n            with_pktinfo: if local_addr.is_ipv4() {\n                config.has_ippktinfo\n            } else {\n                config.has_ipv6pktinfo\n            },\n        },\n        Arc::clone(&socket_tx),\n        Default::default(),\n        socket.cid_generator,\n        metrics.clone(),\n    );\n\n    let (socket_driver, accept_stream) = InboundPacketRouter::new(\n        config,\n        socket_tx,\n        socket_rx,\n        local_addr,\n        acceptor,\n        metrics.clone(),\n    );\n\n    crate::metrics::tokio_task::spawn(\"quic_udp_listener\", metrics, async move {\n        match socket_driver.await {\n            Ok(()) => log::trace!(\"incoming packet router finished\"),\n            Err(error) => {\n                log::error!(\"incoming packet router failed\"; \"error\"=>error)\n            },\n        }\n    });\n    Ok(QuicConnectionStream::new(accept_stream))\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/raw.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Helper to wrap existing [quiche::Connection]s.\n//!\n//! This is a low-level interface for users who either need to heavily customize\n//! the [`quiche::Connection`] beyond what is possible via the crate's\n//! [`settings`](crate::settings), or need more control over how to pass data\n//! into the connection.\n//!\n//! Most use cases are much better served by our\n//! [`connect`](crate::quic::connect) (for clients) or [`listen`](crate::listen)\n//! (for servers) API.\n\nuse datagram_socket::DatagramSocketSend;\nuse std::sync::Arc;\nuse std::task::ready;\nuse std::task::Context;\nuse std::task::Poll;\nuse std::time::Instant;\nuse tokio::sync::mpsc;\n\nuse super::connection::InitialQuicConnection;\nuse super::connection::QuicConnectionParams;\nuse super::io::worker::WriterConfig;\nuse super::router::ConnectionMapCommand;\nuse crate::metrics::Metrics;\nuse crate::quic::HandshakeInfo;\nuse crate::quic::Incoming;\nuse crate::quic::QuicheConnection;\nuse crate::socket::Socket;\n\n/// Result of manually wrapping a [`quiche::Connection`] in an\n/// [`InitialQuicConnection`].\n///\n/// This struct bundles the interfaces which interact with the connection.\npub struct ConnWrapperResult<Tx, M>\nwhere\n    Tx: DatagramSocketSend + Send + 'static + ?Sized,\n    M: Metrics,\n{\n    /// The connection wrapper.\n    pub conn: InitialQuicConnection<Tx, M>,\n    /// Sender for inbound packets on the connection.\n    pub incoming_tx: mpsc::Sender<Incoming>,\n    /// Receiver for `connection closed` notifications. This fires\n    /// after a `CONNECTION_CLOSE` frame has been sent on the connection,\n    /// but before `worker_shutdown_rx`.\n    pub conn_close_rx: ConnCloseReceiver,\n    /// Receiver which fires only when its associated sender is dropped.\n    /// This happens when the connection's IO task exits.\n    pub worker_shutdown_rx: mpsc::Receiver<()>,\n}\n\n/// Wraps an existing [`quiche::Connection`] in an [`InitialQuicConnection`],\n/// bypassing the regular packet router workflow.\n///\n/// Connections wrapped in this way require the user to manually pass inbound\n/// packets via the channel returned in [`ConnWrapperResult`]. The passed\n/// `tx_socket` is only used to _send_ outbound packets and to extract the\n/// endpoint's addresses.\n///\n/// # Note\n/// This function does not attempt any I/O when wrapping the\n/// [`quiche::Connection`]. To start handshaking and consuming packets from the\n/// returned channel, use the methods on [`InitialQuicConnection`].\npub fn wrap_quiche_conn<Tx, R, M>(\n    quiche_conn: QuicheConnection, tx_socket: Socket<Arc<Tx>, R>, metrics: M,\n) -> ConnWrapperResult<Tx, M>\nwhere\n    Tx: DatagramSocketSend + Send + 'static + ?Sized,\n    M: Metrics,\n{\n    let Socket {\n        send: socket,\n        local_addr,\n        peer_addr,\n        ..\n    } = tx_socket;\n    let (shutdown_tx, worker_shutdown_rx) = mpsc::channel(1);\n    let (conn_map_cmd_tx, conn_map_rx) = mpsc::unbounded_channel();\n\n    let scid = quiche_conn.source_id().into_owned();\n\n    let writer_cfg = WriterConfig {\n        pending_cid: None, // only used for unmapping in IPR\n        peer_addr,\n        local_addr,\n        // TODO: try to read Tx' SocketCaps. false is always a safe default.\n        with_gso: false,\n        pacing_offload: false,\n        with_pktinfo: false,\n    };\n\n    let conn_params = QuicConnectionParams {\n        writer_cfg,\n        initial_pkt: None,\n        shutdown_tx,\n        conn_map_cmd_tx,\n        scid,\n        cid_generator: None,\n        metrics,\n        #[cfg(feature = \"perf-quic-listener-metrics\")]\n        init_rx_time: None,\n        handshake_info: HandshakeInfo::new(Instant::now(), None),\n        quiche_conn,\n        socket,\n        local_addr,\n        peer_addr,\n    };\n\n    let conn = InitialQuicConnection::new(conn_params);\n    let incoming_tx = conn.incoming_ev_sender.clone();\n\n    ConnWrapperResult {\n        conn,\n        incoming_tx,\n        conn_close_rx: ConnCloseReceiver(conn_map_rx),\n        worker_shutdown_rx,\n    }\n}\n\n/// Pollable receiver for `connection closed` notifications from a QUIC\n/// connection.\n///\n/// This receiver also fires if the corresponding sender has been dropped\n/// without a `CONNECTION_CLOSE` frame on the connection.\npub struct ConnCloseReceiver(mpsc::UnboundedReceiver<ConnectionMapCommand>);\n\nimpl ConnCloseReceiver {\n    /// Polls to receive a `connection closed` notification.\n    pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<()> {\n        loop {\n            let cmd = ready!(self.0.poll_recv(cx));\n            if matches!(cmd, None | Some(ConnectionMapCommand::UnmapCid(_))) {\n                // Raw connections have neither a `pending_cid` nor a\n                // `cid_generator`. The only time they unmap a CID is when the\n                // connection is closed.\n                return Poll::Ready(());\n            }\n        }\n    }\n\n    /// Waits for a `connection closed` notification.\n    pub async fn recv(&mut self) {\n        std::future::poll_fn(|cx| self.poll_recv(cx)).await\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/router/acceptor.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::fs::File;\nuse std::io;\nuse std::sync::Arc;\nuse std::time::Instant;\n\nuse datagram_socket::DatagramSocketSend;\nuse datagram_socket::DatagramSocketSendExt;\nuse datagram_socket::MAX_DATAGRAM_SIZE;\nuse quiche::ConnectionId;\nuse quiche::Header;\nuse quiche::RetryConnectionIds;\nuse quiche::Type as PacketType;\nuse task_killswitch::spawn_with_killswitch;\n\nuse crate::metrics::labels;\nuse crate::metrics::Metrics;\nuse crate::quic::addr_validation_token::AddrValidationTokenManager;\nuse crate::quic::connection::SharedConnectionIdGenerator;\nuse crate::quic::make_qlog_writer;\nuse crate::quic::router::NewConnection;\nuse crate::quic::Incoming;\nuse crate::QuicResultExt;\n\nuse super::InitialPacketHandler;\n\n/// A [`ConnectionAcceptor`] is an [`InitialPacketHandler`] that acts as a\n/// server and accepts quic connections.\npub(crate) struct ConnectionAcceptor<S, M> {\n    config: ConnectionAcceptorConfig,\n    socket: Arc<S>,\n    token_manager: AddrValidationTokenManager,\n    cid_generator: SharedConnectionIdGenerator,\n    metrics: M,\n}\n\npub(crate) struct ConnectionAcceptorConfig {\n    pub(crate) disable_client_ip_validation: bool,\n    pub(crate) qlog_dir: Option<String>,\n    pub(crate) keylog_file: Option<File>,\n    #[cfg(target_os = \"linux\")]\n    pub(crate) with_pktinfo: bool,\n}\n\nimpl<S, M> ConnectionAcceptor<S, M>\nwhere\n    S: DatagramSocketSend + Send + 'static,\n    M: Metrics,\n{\n    pub(crate) fn new(\n        config: ConnectionAcceptorConfig, socket: Arc<S>,\n        token_manager: AddrValidationTokenManager,\n        cid_generator: SharedConnectionIdGenerator, metrics: M,\n    ) -> Self {\n        Self {\n            config,\n            socket,\n            token_manager,\n            cid_generator,\n            metrics,\n        }\n    }\n\n    fn accept_conn(\n        &mut self, incoming: Incoming, retry_cids: Option<RetryConnectionIds>,\n        pending_cid: ConnectionId<'static>, quiche_config: &mut quiche::Config,\n    ) -> io::Result<Option<NewConnection>> {\n        let handshake_start_time = Instant::now();\n        let scid = self.cid_generator.new_connection_id();\n\n        let mut conn = if let Some(retry_cids) = retry_cids {\n            quiche::accept_with_retry(\n                &scid,\n                retry_cids,\n                incoming.local_addr,\n                incoming.peer_addr,\n                quiche_config,\n            )\n        } else {\n            quiche::accept_with_buf_factory(\n                &scid,\n                None,\n                incoming.local_addr,\n                incoming.peer_addr,\n                quiche_config,\n            )\n        }\n        .into_io()?;\n\n        if let Some(qlog_dir) = &self.config.qlog_dir {\n            let id = format!(\"{:?}\", &scid);\n            if let Ok(writer) = make_qlog_writer(qlog_dir, &id) {\n                conn.set_qlog(\n                    std::boxed::Box::new(writer),\n                    \"tokio-quiche qlog\".to_string(),\n                    format!(\"tokio-quiche qlog id={id}\"),\n                );\n            }\n        }\n\n        if let Some(keylog_file) = &self.config.keylog_file {\n            if let Ok(keylog_clone) = keylog_file.try_clone() {\n                conn.set_keylog(Box::new(keylog_clone));\n            }\n        }\n\n        Ok(Some(NewConnection {\n            conn,\n            handshake_start_time,\n            pending_cid: Some(pending_cid),\n            cid_generator: Some(Arc::clone(&self.cid_generator)),\n            initial_pkt: Some(incoming),\n        }))\n    }\n\n    fn handshake_reply(\n        &self, incoming: Incoming,\n        writer: impl FnOnce(&mut [u8]) -> io::Result<usize>,\n    ) -> io::Result<Option<NewConnection>> {\n        let mut send_buf = [0u8; MAX_DATAGRAM_SIZE];\n        let written = writer(&mut send_buf)?;\n        let socket = Arc::clone(&self.socket);\n        #[cfg(target_os = \"linux\")]\n        let with_pktinfo = self.config.with_pktinfo;\n        #[cfg(target_os = \"linux\")]\n        let would_block_metric = self\n            .metrics\n            .write_errors(labels::QuicWriteError::WouldBlock);\n        #[cfg(target_os = \"linux\")]\n        let send_to_wouldblock_duration_s =\n            self.metrics.send_to_wouldblock_duration_s();\n\n        spawn_with_killswitch(async move {\n            let send_buf = &send_buf[..written];\n            let to = incoming.peer_addr;\n\n            #[allow(unused_variables)]\n            let Some(udp) = socket.as_udp_socket() else {\n                let _ = socket.send_to(send_buf, to).await;\n                return;\n            };\n\n            #[cfg(target_os = \"linux\")]\n            {\n                let from = Some(incoming.local_addr).filter(|_| with_pktinfo);\n                let _ = crate::quic::io::gso::send_to(\n                    udp,\n                    to,\n                    from,\n                    send_buf,\n                    send_buf.len(),\n                    None,\n                    would_block_metric,\n                    send_to_wouldblock_duration_s,\n                )\n                .await;\n            }\n\n            #[cfg(not(target_os = \"linux\"))]\n            let _ = socket.send_to(send_buf, to).await;\n        });\n\n        Ok(None)\n    }\n\n    fn stateless_retry(\n        &mut self, incoming: Incoming, hdr: Header,\n    ) -> io::Result<Option<NewConnection>> {\n        let scid = self.cid_generator.new_connection_id();\n\n        let token = self.token_manager.gen(&hdr.dcid, incoming.peer_addr);\n\n        self.handshake_reply(incoming, move |buf| {\n            quiche::retry(&hdr.scid, &hdr.dcid, &scid, &token, hdr.version, buf)\n                .into_io()\n        })\n    }\n}\n\nimpl<S, M> InitialPacketHandler for ConnectionAcceptor<S, M>\nwhere\n    S: DatagramSocketSend + Send + 'static,\n    M: Metrics,\n{\n    fn handle_initials(\n        &mut self, incoming: Incoming, hdr: quiche::Header<'static>,\n        quiche_config: &mut quiche::Config,\n    ) -> io::Result<Option<NewConnection>> {\n        if hdr.ty != PacketType::Initial {\n            // Non-initial packets should have a valid CID, but we want to have\n            // some telemetry if this isn't the case.\n            if let Err(e) = self.cid_generator.verify_connection_id(&hdr.dcid) {\n                self.metrics.invalid_cid_packet_count(e).inc();\n            }\n\n            Err(labels::QuicInvalidInitialPacketError::WrongType(hdr.ty))?;\n        }\n\n        if !quiche::version_is_supported(hdr.version) {\n            return self.handshake_reply(incoming, |buf| {\n                quiche::negotiate_version(&hdr.scid, &hdr.dcid, buf).into_io()\n            });\n        }\n\n        if self.config.disable_client_ip_validation {\n            return self.accept_conn(incoming, None, hdr.dcid, quiche_config);\n        }\n\n        // NOTE: token is always present in Initial packets\n        let token = hdr.token.as_ref().unwrap();\n        if token.is_empty() {\n            return self.stateless_retry(incoming, hdr);\n        }\n\n        let original_dcid = self\n            .token_manager\n            .validate_and_extract_original_dcid(token, incoming.peer_addr)\n            .or(Err(\n                labels::QuicInvalidInitialPacketError::TokenValidationFail,\n            ))?;\n\n        let retry_cids = Some(RetryConnectionIds {\n            original_destination_cid: &original_dcid,\n            retry_source_cid: &hdr.dcid,\n        });\n\n        self.accept_conn(incoming, retry_cids, hdr.dcid.clone(), quiche_config)\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/router/connector.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::io;\nuse std::mem;\nuse std::sync::Arc;\nuse std::task::Context;\nuse std::task::Poll;\nuse std::time::Instant;\n\nuse datagram_socket::DatagramSocketSend;\nuse datagram_socket::DatagramSocketSendExt;\nuse datagram_socket::MaybeConnectedSocket;\nuse datagram_socket::MAX_DATAGRAM_SIZE;\nuse foundations::telemetry::log;\nuse quiche::ConnectionId;\nuse quiche::Header;\nuse tokio_util::time::delay_queue::Key;\nuse tokio_util::time::DelayQueue;\n\nuse crate::quic::router::InitialPacketHandler;\nuse crate::quic::router::NewConnection;\nuse crate::quic::Incoming;\nuse crate::quic::QuicheConnection;\n\n/// A [`ClientConnector`] manages client-initiated [`quiche::Connection`]s. When\n/// a connection is established, this struct returns the connection to the\n/// [`InboundPacketRouter`](super::InboundPacketRouter) for further processing.\npub(crate) struct ClientConnector<Tx> {\n    socket_tx: MaybeConnectedSocket<Arc<Tx>>,\n    connection: ConnectionState,\n    timeout_queue: DelayQueue<ConnectionId<'static>>,\n}\n\n/// State the connecting connection is in.\nenum ConnectionState {\n    /// Connection hasn't had any initials sent for it\n    Queued(QuicheConnection),\n    /// It's currently in a QUIC handshake\n    Pending(PendingConnection),\n    /// It's been returned to the\n    /// [`InboundPacketRouter`](super::InboundPacketRouter).\n    Returned,\n}\n\nimpl ConnectionState {\n    fn take_if_queued(&mut self) -> Option<QuicheConnection> {\n        match mem::replace(self, Self::Returned) {\n            Self::Queued(conn) => Some(conn),\n            state => {\n                *self = state;\n                None\n            },\n        }\n    }\n\n    fn take_if_pending_and_id_matches(\n        &mut self, scid: &ConnectionId<'static>,\n    ) -> Option<PendingConnection> {\n        match mem::replace(self, Self::Returned) {\n            Self::Pending(pending) if *scid == pending.conn.source_id() =>\n                Some(pending),\n            state => {\n                *self = state;\n                None\n            },\n        }\n    }\n}\n\n/// A [`PendingConnection`] holds an internal [`quiche::Connection`] and an\n/// optional timeout [`Key`].\nstruct PendingConnection {\n    conn: QuicheConnection,\n    timeout_key: Option<Key>,\n    handshake_start_time: Instant,\n}\n\nimpl<Tx> ClientConnector<Tx>\nwhere\n    Tx: DatagramSocketSend + Send + 'static,\n{\n    pub(crate) fn new(socket_tx: Arc<Tx>, connection: QuicheConnection) -> Self {\n        Self {\n            socket_tx: MaybeConnectedSocket::new(socket_tx),\n            connection: ConnectionState::Queued(connection),\n            timeout_queue: Default::default(),\n        }\n    }\n\n    /// Sets the connection to it's pending state. Await [`Incoming`] packets.\n    ///\n    /// This sends any pending packets and arms the connection's timeout timer.\n    fn set_connection_to_pending(\n        &mut self, mut conn: QuicheConnection,\n    ) -> io::Result<()> {\n        simple_conn_send(&self.socket_tx, &mut conn)?;\n\n        let timeout_key = conn.timeout_instant().map(|instant| {\n            self.timeout_queue\n                .insert_at(conn.source_id().into_owned(), instant.into())\n        });\n\n        self.connection = ConnectionState::Pending(PendingConnection {\n            conn,\n            timeout_key,\n            handshake_start_time: Instant::now(),\n        });\n\n        Ok(())\n    }\n\n    /// Handles an incoming packet (or packets) designated for this pending\n    /// connection.\n    ///\n    /// If the connection is pending, we return it\n    fn on_incoming(\n        &mut self, mut incoming: Incoming, hdr: Header<'static>,\n    ) -> io::Result<Option<NewConnection>> {\n        let Some(PendingConnection {\n            mut conn,\n            timeout_key,\n            handshake_start_time,\n        }) = self.connection.take_if_pending_and_id_matches(&hdr.dcid)\n        else {\n            log::debug!(\"Received Initial packet for unknown connection ID\"; \"scid\" => ?hdr.dcid);\n            return Ok(None);\n        };\n\n        let recv_info = quiche::RecvInfo {\n            from: incoming.peer_addr,\n            to: incoming.local_addr,\n        };\n\n        if let Some(gro) = incoming.gro {\n            for dgram in incoming.buf.chunks_mut(gro as usize) {\n                // Log error here if recv fails\n                let _ = conn.recv(dgram, recv_info);\n            }\n        } else {\n            // Log error here if recv fails\n            let _ = conn.recv(&mut incoming.buf, recv_info);\n        }\n\n        // disarm the timer since we're either going to immediately rearm it or\n        // return an established connection.\n        if let Some(key) = timeout_key {\n            self.timeout_queue.remove(&key);\n        }\n\n        let scid = conn.source_id();\n        if conn.is_established() {\n            log::debug!(\"QUIC connection established\"; \"scid\" => ?scid);\n\n            Ok(Some(NewConnection {\n                conn,\n                pending_cid: None,\n                initial_pkt: None,\n                cid_generator: None,\n                handshake_start_time,\n            }))\n        } else if conn.is_closed() {\n            let scid = conn.source_id();\n            log::error!(\"QUIC connection closed on_incoming\"; \"scid\" => ?scid);\n\n            Err(io::Error::new(\n                io::ErrorKind::TimedOut,\n                format!(\"connection {scid:?} timed out\"),\n            ))\n        } else {\n            self.set_connection_to_pending(conn).map(|()| None)\n        }\n    }\n\n    /// [`ClientConnector::on_timeout`] runs [`quiche::Connection::on_timeout`]\n    /// for a pending connection. If the connection is closed, this sends an\n    /// error upstream.\n    fn on_timeout(&mut self, scid: ConnectionId<'static>) -> io::Result<()> {\n        log::debug!(\"connection timedout\"; \"scid\" => ?scid);\n\n        let Some(mut pending) =\n            self.connection.take_if_pending_and_id_matches(&scid)\n        else {\n            log::debug!(\"timedout connection missing from pending map\"; \"scid\" => ?scid);\n            return Ok(());\n        };\n\n        pending.conn.on_timeout();\n\n        if pending.conn.is_closed() {\n            log::error!(\"pending connection closed on_timeout\"; \"scid\" => ?scid);\n\n            return Err(io::Error::new(\n                io::ErrorKind::TimedOut,\n                format!(\"connection {scid:?} timed out\"),\n            ));\n        }\n\n        self.set_connection_to_pending(pending.conn)\n    }\n\n    /// [`ClientConnector::update`] handles expired pending connections and\n    /// checks starts the inner connection if not started yet.\n    fn update(&mut self, cx: &mut Context) -> io::Result<()> {\n        while let Poll::Ready(Some(expired)) = self.timeout_queue.poll_expired(cx)\n        {\n            let scid = expired.into_inner();\n            self.on_timeout(scid)?;\n        }\n\n        if let Some(conn) = self.connection.take_if_queued() {\n            self.set_connection_to_pending(conn)?;\n        }\n\n        Ok(())\n    }\n}\n\nimpl<Tx> InitialPacketHandler for ClientConnector<Tx>\nwhere\n    Tx: DatagramSocketSend + Send + 'static,\n{\n    fn update(&mut self, ctx: &mut Context<'_>) -> io::Result<()> {\n        ClientConnector::update(self, ctx)\n    }\n\n    fn handle_initials(\n        &mut self, incoming: Incoming, hdr: Header<'static>,\n        _: &mut quiche::Config,\n    ) -> io::Result<Option<NewConnection>> {\n        self.on_incoming(incoming, hdr)\n    }\n}\n\n/// Repeatedly send packets until quiche reports that it's done.\n///\n/// This does not have to be efficent, since once a connection is established\n/// the [`crate::quic::io::worker::IoWorker`] will take over sending and\n/// receiving.\nfn simple_conn_send<Tx: DatagramSocketSend + Send + Sync + 'static>(\n    socket_tx: &MaybeConnectedSocket<Arc<Tx>>, conn: &mut QuicheConnection,\n) -> io::Result<()> {\n    let scid = conn.source_id().into_owned();\n    log::debug!(\"sending client Initials to peer\"; \"scid\" => ?scid);\n\n    loop {\n        let scid = scid.clone();\n        let mut buf = [0; MAX_DATAGRAM_SIZE];\n        let send_res = conn.send(&mut buf);\n\n        let socket_clone = socket_tx.clone();\n        match send_res {\n            Ok((n, send_info)) => {\n                tokio::spawn({\n                    let buf = buf[0..n].to_vec();\n                    async move {\n                        socket_clone.send_to(&buf, send_info.to).await.inspect_err(|error| {\n                        log::error!(\"error sending client Initial packets to peer\"; \"scid\" => ?scid, \"peer_addr\" => send_info.to, \"error\" => error.to_string());\n                    })\n                    }\n                });\n            },\n            Err(quiche::Error::Done) => break Ok(()),\n            Err(error) => {\n                log::error!(\"error writing packets to quiche's internal buffer\"; \"scid\" => ?scid, \"error\" => error.to_string());\n                break Err(std::io::Error::other(error));\n            },\n        }\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/quic/router/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\npub(crate) mod acceptor;\npub(crate) mod connector;\n\nuse super::connection::ConnectionMap;\nuse super::connection::HandshakeInfo;\nuse super::connection::Incoming;\nuse super::connection::InitialQuicConnection;\nuse super::connection::QuicConnectionParams;\nuse super::io::worker::WriterConfig;\nuse super::QuicheConnection;\nuse crate::buf_factory::BufFactory;\nuse crate::buf_factory::PooledBuf;\nuse crate::metrics::labels;\nuse crate::metrics::quic_expensive_metrics_ip_reduce;\nuse crate::metrics::Metrics;\nuse crate::quic::connection::SharedConnectionIdGenerator;\nuse crate::settings::Config;\nuse datagram_socket::DatagramSocketRecv;\nuse datagram_socket::DatagramSocketSend;\nuse foundations::telemetry::log;\nuse quiche::ConnectionId;\nuse quiche::Header;\nuse quiche::MAX_CONN_ID_LEN;\nuse std::default::Default;\nuse std::future::Future;\nuse std::io;\nuse std::net::SocketAddr;\nuse std::pin::Pin;\nuse std::sync::Arc;\nuse std::task::ready;\nuse std::task::Context;\nuse std::task::Poll;\nuse std::time::Instant;\nuse std::time::SystemTime;\nuse task_killswitch::spawn_with_killswitch;\nuse tokio::sync::mpsc;\n\n#[cfg(target_os = \"linux\")]\nuse foundations::telemetry::metrics::Counter;\n#[cfg(target_os = \"linux\")]\nuse foundations::telemetry::metrics::TimeHistogram;\n#[cfg(target_os = \"linux\")]\nuse libc::sockaddr_in;\n#[cfg(target_os = \"linux\")]\nuse libc::sockaddr_in6;\n\ntype ConnStream<Tx, M> = mpsc::Receiver<io::Result<InitialQuicConnection<Tx, M>>>;\n\n#[cfg(feature = \"perf-quic-listener-metrics\")]\nmod listener_stage_timer {\n    use foundations::telemetry::metrics::TimeHistogram;\n    use std::time::Instant;\n\n    pub(super) struct ListenerStageTimer {\n        start: Instant,\n        time_hist: TimeHistogram,\n    }\n\n    impl ListenerStageTimer {\n        pub(super) fn new(\n            start: Instant, time_hist: TimeHistogram,\n        ) -> ListenerStageTimer {\n            ListenerStageTimer { start, time_hist }\n        }\n    }\n\n    impl Drop for ListenerStageTimer {\n        fn drop(&mut self) {\n            self.time_hist\n                .observe((Instant::now() - self.start).as_nanos() as u64);\n        }\n    }\n}\n\n#[derive(Debug)]\nstruct PollRecvData {\n    bytes: usize,\n    // The packet's source, e.g., the peer's address\n    src_addr: SocketAddr,\n    // The packet's original destination. If the original destination is\n    // different from the local listening address, this will be `None`.\n    dst_addr_override: Option<SocketAddr>,\n    rx_time: Option<SystemTime>,\n    gro: Option<i32>,\n    #[cfg(target_os = \"linux\")]\n    so_mark_data: Option<[u8; 4]>,\n}\n\n/// A message to the listener notifiying a mapping for a connection should be\n/// removed.\npub enum ConnectionMapCommand {\n    MapCid {\n        existing_cid: ConnectionId<'static>,\n        new_cid: ConnectionId<'static>,\n    },\n    UnmapCid(ConnectionId<'static>),\n}\n\n/// An `InboundPacketRouter` maintains a map of quic connections and routes\n/// [`Incoming`] packets from the [recv half][rh] of a datagram socket to those\n/// connections or some quic initials handler.\n///\n/// [rh]: datagram_socket::DatagramSocketRecv\n///\n/// When a packet (or batch of packets) is received, the router will either\n/// route those packets to an established\n/// [`QuicConnection`](super::QuicConnection) or have a them handled by a\n/// `InitialPacketHandler` which either acts as a quic listener or\n/// quic connector, a server or client respectively.\n///\n/// If you only have a single connection, or if you need more control over the\n/// socket, use `QuicConnection` directly instead.\npub struct InboundPacketRouter<Tx, Rx, M, I>\nwhere\n    Tx: DatagramSocketSend + Send + 'static,\n    M: Metrics,\n{\n    socket_tx: Arc<Tx>,\n    socket_rx: Rx,\n    local_addr: SocketAddr,\n    config: Config,\n    conns: ConnectionMap,\n    incoming_packet_handler: I,\n    shutdown_tx: Option<mpsc::Sender<()>>,\n    shutdown_rx: mpsc::Receiver<()>,\n    conn_map_cmd_tx: mpsc::UnboundedSender<ConnectionMapCommand>,\n    conn_map_cmd_rx: mpsc::UnboundedReceiver<ConnectionMapCommand>,\n    accept_sink: mpsc::Sender<io::Result<InitialQuicConnection<Tx, M>>>,\n    metrics: M,\n    #[cfg(target_os = \"linux\")]\n    udp_drop_count: u32,\n\n    #[cfg(target_os = \"linux\")]\n    reusable_cmsg_space: Vec<u8>,\n\n    current_buf: PooledBuf,\n\n    // We keep the metrics in here, to avoid cloning them each packet\n    #[cfg(target_os = \"linux\")]\n    metrics_handshake_time_seconds: TimeHistogram,\n    #[cfg(target_os = \"linux\")]\n    metrics_udp_drop_count: Counter,\n}\n\nimpl<Tx, Rx, M, I> InboundPacketRouter<Tx, Rx, M, I>\nwhere\n    Tx: DatagramSocketSend + Send + 'static,\n    Rx: DatagramSocketRecv,\n    M: Metrics,\n    I: InitialPacketHandler,\n{\n    pub(crate) fn new(\n        config: Config, socket_tx: Arc<Tx>, socket_rx: Rx,\n        local_addr: SocketAddr, incoming_packet_handler: I, metrics: M,\n    ) -> (Self, ConnStream<Tx, M>) {\n        let (shutdown_tx, shutdown_rx) = mpsc::channel(1);\n        let (accept_sink, accept_stream) = mpsc::channel(config.listen_backlog);\n        let (conn_map_cmd_tx, conn_map_cmd_rx) = mpsc::unbounded_channel();\n\n        (\n            InboundPacketRouter {\n                local_addr,\n                socket_tx,\n                socket_rx,\n                conns: ConnectionMap::default(),\n                incoming_packet_handler,\n                shutdown_tx: Some(shutdown_tx),\n                shutdown_rx,\n                conn_map_cmd_tx,\n                conn_map_cmd_rx,\n                accept_sink,\n                #[cfg(target_os = \"linux\")]\n                udp_drop_count: 0,\n                #[cfg(target_os = \"linux\")]\n                // Specify CMSG space. Even if they're not all currently used, the cmsg buffer may\n                // have been configured by a previous version of Tokio-Quiche with the socket\n                // re-used on graceful restart. As such, this vector should _only grow_, and care\n                // should be taken when adding new cmsgs.\n                reusable_cmsg_space: nix::cmsg_space!(\n                    u32, // GRO\n                    nix::sys::time::TimeSpec, // timestamp\n                    u16, // drop count\n                    sockaddr_in, // IP_RECVORIGDSTADDR\n                    sockaddr_in6, // IPV6_RECVORIGDSTADDR\n                    u32 // SO_MARK\n                ),\n                config,\n\n                current_buf: BufFactory::get_max_buf(),\n\n                #[cfg(target_os = \"linux\")]\n                metrics_handshake_time_seconds: metrics.handshake_time_seconds(labels::QuicHandshakeStage::QueueWaiting),\n                #[cfg(target_os = \"linux\")]\n                metrics_udp_drop_count: metrics.udp_drop_count(),\n\n                metrics,\n\n            },\n            accept_stream,\n        )\n    }\n\n    fn on_incoming(&mut self, mut incoming: Incoming) -> io::Result<()> {\n        #[cfg(feature = \"perf-quic-listener-metrics\")]\n        let start = std::time::Instant::now();\n\n        if let Some(dcid) = short_dcid(&incoming.buf) {\n            if let Some(ev_sender) = self.conns.get(&dcid) {\n                let _ = ev_sender.try_send(incoming);\n                return Ok(());\n            }\n        }\n\n        let hdr = Header::from_slice(&mut incoming.buf, MAX_CONN_ID_LEN)\n            .map_err(|e| match e {\n                quiche::Error::BufferTooShort | quiche::Error::InvalidPacket =>\n                    labels::QuicInvalidInitialPacketError::FailedToParse.into(),\n                e => io::Error::other(e),\n            })?;\n\n        if let Some(ev_sender) = self.conns.get(&hdr.dcid) {\n            let _ = ev_sender.try_send(incoming);\n            return Ok(());\n        }\n\n        #[cfg(feature = \"perf-quic-listener-metrics\")]\n        let _timer = listener_stage_timer::ListenerStageTimer::new(\n            start,\n            self.metrics.handshake_time_seconds(\n                labels::QuicHandshakeStage::HandshakeProtocol,\n            ),\n        );\n\n        if self.shutdown_tx.is_none() {\n            return Ok(());\n        }\n\n        let local_addr = incoming.local_addr;\n        let peer_addr = incoming.peer_addr;\n\n        #[cfg(feature = \"perf-quic-listener-metrics\")]\n        let init_rx_time = incoming.rx_time;\n\n        let new_connection = self.incoming_packet_handler.handle_initials(\n            incoming,\n            hdr,\n            self.config.as_mut(),\n        )?;\n\n        match new_connection {\n            Some(new_connection) => self.spawn_new_connection(\n                new_connection,\n                local_addr,\n                peer_addr,\n                #[cfg(feature = \"perf-quic-listener-metrics\")]\n                init_rx_time,\n            ),\n            None => Ok(()),\n        }\n    }\n\n    /// Creates a new [`QuicConnection`](super::QuicConnection) and spawns an\n    /// associated io worker.\n    fn spawn_new_connection(\n        &mut self, new_connection: NewConnection, local_addr: SocketAddr,\n        peer_addr: SocketAddr,\n        #[cfg(feature = \"perf-quic-listener-metrics\")] init_rx_time: Option<\n            SystemTime,\n        >,\n    ) -> io::Result<()> {\n        let NewConnection {\n            conn,\n            pending_cid,\n            cid_generator,\n            handshake_start_time,\n            initial_pkt,\n        } = new_connection;\n\n        let Some(ref shutdown_tx) = self.shutdown_tx else {\n            // don't create new connections if we're shutting down.\n            return Ok(());\n        };\n        let Ok(send_permit) = self.accept_sink.try_reserve() else {\n            // drop the connection if the backlog is full. the client will retry.\n            return Err(\n                labels::QuicInvalidInitialPacketError::AcceptQueueOverflow.into(),\n            );\n        };\n\n        let scid = conn.source_id().into_owned();\n        let writer_cfg = WriterConfig {\n            peer_addr,\n            local_addr,\n            pending_cid: pending_cid.clone(),\n            with_gso: self.config.has_gso,\n            pacing_offload: self.config.pacing_offload,\n            with_pktinfo: if self.local_addr.is_ipv4() {\n                self.config.has_ippktinfo\n            } else {\n                self.config.has_ipv6pktinfo\n            },\n        };\n\n        let handshake_info = HandshakeInfo::new(\n            handshake_start_time,\n            self.config.handshake_timeout,\n        );\n\n        let conn = InitialQuicConnection::new(QuicConnectionParams {\n            writer_cfg,\n            initial_pkt,\n            shutdown_tx: shutdown_tx.clone(),\n            conn_map_cmd_tx: self.conn_map_cmd_tx.clone(),\n            scid: scid.clone(),\n            cid_generator,\n            metrics: self.metrics.clone(),\n            #[cfg(feature = \"perf-quic-listener-metrics\")]\n            init_rx_time,\n            handshake_info,\n            quiche_conn: conn,\n            socket: Arc::clone(&self.socket_tx),\n            local_addr,\n            peer_addr,\n        });\n\n        conn.audit_log_stats\n            .set_transport_handshake_start(instant_to_system(\n                handshake_start_time,\n            ));\n\n        self.conns.insert(&scid, &conn);\n\n        // Add the client-generated \"pending\" connection ID to the map as well.\n        // This is only required for QUIC servers, because clients can send\n        // Initial packets with arbitrary DCIDs to servers.\n        if let Some(pending_cid) = pending_cid {\n            self.conns.map_cid(&scid, &pending_cid);\n        }\n\n        self.metrics.accepted_initial_packet_count().inc();\n        if self.config.enable_expensive_packet_count_metrics {\n            if let Some(peer_ip) =\n                quic_expensive_metrics_ip_reduce(conn.peer_addr().ip())\n            {\n                self.metrics\n                    .expensive_accepted_initial_packet_count(peer_ip)\n                    .inc();\n            }\n        }\n\n        send_permit.send(Ok(conn));\n        Ok(())\n    }\n}\n\nimpl<Tx, Rx, M, I> InboundPacketRouter<Tx, Rx, M, I>\nwhere\n    Tx: DatagramSocketSend + Send + Sync + 'static,\n    Rx: DatagramSocketRecv,\n    M: Metrics,\n    I: InitialPacketHandler,\n{\n    /// [`InboundPacketRouter::poll_recv_from`] should be used if the underlying\n    /// system or socket does not support rx_time nor GRO.\n    fn poll_recv_from(\n        &mut self, cx: &mut Context<'_>,\n    ) -> Poll<io::Result<PollRecvData>> {\n        let mut buf = tokio::io::ReadBuf::new(&mut self.current_buf);\n        let addr = ready!(self.socket_rx.poll_recv_from(cx, &mut buf))?;\n        Poll::Ready(Ok(PollRecvData {\n            bytes: buf.filled().len(),\n            src_addr: addr,\n            rx_time: None,\n            gro: None,\n            dst_addr_override: None,\n            #[cfg(target_os = \"linux\")]\n            so_mark_data: None,\n        }))\n    }\n\n    fn poll_recv_and_rx_time(\n        &mut self, cx: &mut Context<'_>,\n    ) -> Poll<io::Result<PollRecvData>> {\n        #[cfg(not(target_os = \"linux\"))]\n        {\n            self.poll_recv_from(cx)\n        }\n\n        #[cfg(target_os = \"linux\")]\n        {\n            use libc::SOL_SOCKET;\n            use libc::SO_MARK;\n            use nix::errno::Errno;\n            use nix::sys::socket::*;\n            use std::net::SocketAddrV4;\n            use std::net::SocketAddrV6;\n            use std::os::fd::AsRawFd;\n            use tokio::io::Interest;\n\n            let Some(udp_socket) = self.socket_rx.as_udp_socket() else {\n                // the given socket is not a UDP socket, fall back to the\n                // simple poll_recv_from.\n                return self.poll_recv_from(cx);\n            };\n\n            loop {\n                let iov_s = &mut [io::IoSliceMut::new(&mut self.current_buf)];\n                match udp_socket.try_io(Interest::READABLE, || {\n                    recvmsg::<SockaddrStorage>(\n                        udp_socket.as_raw_fd(),\n                        iov_s,\n                        Some(&mut self.reusable_cmsg_space),\n                        MsgFlags::empty(),\n                    )\n                    .map_err(|x| x.into())\n                }) {\n                    Ok(r) => {\n                        let bytes = r.bytes;\n\n                        let address = match r.address {\n                            Some(inner) => inner,\n                            _ => return Poll::Ready(Err(Errno::EINVAL.into())),\n                        };\n\n                        let peer_addr = match address.family() {\n                            Some(AddressFamily::Inet) => SocketAddrV4::from(\n                                *address.as_sockaddr_in().unwrap(),\n                            )\n                            .into(),\n                            Some(AddressFamily::Inet6) => SocketAddrV6::from(\n                                *address.as_sockaddr_in6().unwrap(),\n                            )\n                            .into(),\n                            _ => {\n                                return Poll::Ready(Err(Errno::EINVAL.into()));\n                            },\n                        };\n\n                        let mut rx_time = None;\n                        let mut gro = None;\n                        let mut dst_addr_override = None;\n                        let mut mark_bytes: Option<[u8; 4]> = None;\n\n                        let Ok(cmsgs) = r.cmsgs() else {\n                            // Best-effort if we can't read cmsgs.\n                            return Poll::Ready(Ok(PollRecvData {\n                                bytes,\n                                src_addr: peer_addr,\n                                dst_addr_override,\n                                rx_time,\n                                gro,\n                                so_mark_data: mark_bytes,\n                            }));\n                        };\n\n                        for cmsg in cmsgs {\n                            match cmsg {\n                                ControlMessageOwned::RxqOvfl(c) => {\n                                    if c != self.udp_drop_count {\n                                        self.metrics_udp_drop_count.inc_by(\n                                            (c - self.udp_drop_count) as u64,\n                                        );\n                                        self.udp_drop_count = c;\n                                    }\n                                },\n                                ControlMessageOwned::ScmTimestampns(val) => {\n                                    rx_time = SystemTime::UNIX_EPOCH\n                                        .checked_add(val.into());\n                                    if let Some(delta) =\n                                        rx_time.and_then(|rx_time| {\n                                            rx_time.elapsed().ok()\n                                        })\n                                    {\n                                        self.metrics_handshake_time_seconds\n                                            .observe(delta.as_nanos() as u64);\n                                    }\n                                },\n                                ControlMessageOwned::UdpGroSegments(val) =>\n                                    gro = Some(val),\n                                ControlMessageOwned::Ipv4OrigDstAddr(val) => {\n                                    let source_addr = std::net::Ipv4Addr::from(\n                                        u32::to_be(val.sin_addr.s_addr),\n                                    );\n                                    let source_port = u16::to_be(val.sin_port);\n\n                                    let parsed_addr =\n                                        SocketAddr::V4(SocketAddrV4::new(\n                                            source_addr,\n                                            source_port,\n                                        ));\n\n                                    dst_addr_override = resolve_dst_addr(\n                                        &self.local_addr,\n                                        &parsed_addr,\n                                    );\n                                },\n                                ControlMessageOwned::Ipv6OrigDstAddr(val) => {\n                                    // Don't have to flip IPv6 bytes since it's a\n                                    // byte array, not a\n                                    // series of bytes parsed as a u32 as in the\n                                    // IPv4 case\n                                    let source_addr = std::net::Ipv6Addr::from(\n                                        val.sin6_addr.s6_addr,\n                                    );\n                                    let source_port = u16::to_be(val.sin6_port);\n                                    let source_flowinfo =\n                                        u32::to_be(val.sin6_flowinfo);\n                                    let source_scope =\n                                        u32::to_be(val.sin6_scope_id);\n\n                                    let parsed_addr =\n                                        SocketAddr::V6(SocketAddrV6::new(\n                                            source_addr,\n                                            source_port,\n                                            source_flowinfo,\n                                            source_scope,\n                                        ));\n\n                                    dst_addr_override = resolve_dst_addr(\n                                        &self.local_addr,\n                                        &parsed_addr,\n                                    );\n                                },\n                                ControlMessageOwned::Ipv4PacketInfo(_) |\n                                ControlMessageOwned::Ipv6PacketInfo(_) => {\n                                    // We only want the destination address from\n                                    // IP_RECVORIGDSTADDR, but we'll get these\n                                    // messages because we set IP_PKTINFO on the\n                                    // socket.\n                                },\n                                ControlMessageOwned::Unknown(raw_cmsg) => {\n                                    let UnknownCmsg {\n                                        cmsg_header,\n                                        data_bytes,\n                                    } = raw_cmsg;\n\n                                    if cmsg_header.cmsg_level == SOL_SOCKET &&\n                                        cmsg_header.cmsg_type == SO_MARK\n                                    {\n                                        let Ok(arr) =\n                                            <[u8; 4]>::try_from(data_bytes)\n                                        else {\n                                            // Should be unreachable as SO_MARK is\n                                            // a u32: https://elixir.bootlin.com/linux/v6.17/source/include/net/sock.h#L487\n                                            continue;\n                                        };\n\n                                        let _ = mark_bytes.insert(arr);\n                                    }\n                                },\n                                _ => {\n                                    // Unrecognized cmsg received, just ignore\n                                    // it.\n                                },\n                            };\n                        }\n\n                        return Poll::Ready(Ok(PollRecvData {\n                            bytes,\n                            src_addr: peer_addr,\n                            dst_addr_override,\n                            rx_time,\n                            gro,\n                            so_mark_data: mark_bytes,\n                        }));\n                    },\n                    Err(e) if e.kind() == io::ErrorKind::WouldBlock => {\n                        // NOTE: we manually poll the socket here to register\n                        // interest in the socket to become\n                        // writable for the given `cx`. Under the hood, tokio's\n                        // implementation just checks for\n                        // EWOULDBLOCK and if socket is busy registers provided\n                        // waker to be invoked when the\n                        // socket is free and consequently drive the event loop.\n                        ready!(udp_socket.poll_recv_ready(cx))?\n                    },\n                    Err(e) => return Poll::Ready(Err(e)),\n                }\n            }\n        }\n    }\n\n    fn handle_conn_map_commands(&mut self) {\n        while let Ok(req) = self.conn_map_cmd_rx.try_recv() {\n            match req {\n                ConnectionMapCommand::MapCid {\n                    existing_cid,\n                    new_cid,\n                } => self.conns.map_cid(&existing_cid, &new_cid),\n                ConnectionMapCommand::UnmapCid(cid) => self.conns.unmap_cid(&cid),\n            }\n        }\n    }\n}\n\n// Quickly extract the connection id of a short quic packet without allocating\nfn short_dcid(buf: &[u8]) -> Option<ConnectionId<'_>> {\n    let is_short_dcid = buf.first()? >> 7 == 0;\n\n    if is_short_dcid {\n        buf.get(1..1 + MAX_CONN_ID_LEN).map(ConnectionId::from_ref)\n    } else {\n        None\n    }\n}\n\n/// Converts an [`Instant`] to a [`SystemTime`], based on the current delta\n/// between both clocks.\nfn instant_to_system(ts: Instant) -> SystemTime {\n    let now = Instant::now();\n    let system_now = SystemTime::now();\n    if let Some(delta) = now.checked_duration_since(ts) {\n        return system_now - delta;\n    }\n\n    let delta = ts.checked_duration_since(now).expect(\"now < ts\");\n    system_now + delta\n}\n\n/// Determine if we should store the destination address for a packet, based on\n/// an address parsed from a\n/// [`ControlMessageOwned`](nix::sys::socket::ControlMessageOwned).\n///\n/// This is to prevent overriding the destination address if the packet was\n/// originally addressed to `local`, as that would cause us to incorrectly\n/// address packets when sending.\n///\n/// Returns the parsed address if it should be stored.\n#[cfg(target_os = \"linux\")]\nfn resolve_dst_addr(\n    local: &SocketAddr, parsed: &SocketAddr,\n) -> Option<SocketAddr> {\n    if local != parsed {\n        return Some(*parsed);\n    }\n\n    None\n}\n\nimpl<Tx, Rx, M, I> Future for InboundPacketRouter<Tx, Rx, M, I>\nwhere\n    Tx: DatagramSocketSend + Send + Sync + 'static,\n    Rx: DatagramSocketRecv + Unpin,\n    M: Metrics,\n    I: InitialPacketHandler + Unpin,\n{\n    type Output = io::Result<()>;\n\n    fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {\n        let server_addr = self.local_addr;\n\n        loop {\n            if let Err(error) = self.incoming_packet_handler.update(cx) {\n                // This is so rare that it's easier to spawn a separate task\n                let sender = self.accept_sink.clone();\n                spawn_with_killswitch(async move {\n                    let _ = sender.send(Err(error)).await;\n                });\n            }\n\n            match self.poll_recv_and_rx_time(cx) {\n                Poll::Ready(Ok(PollRecvData {\n                    bytes,\n                    src_addr: peer_addr,\n                    dst_addr_override,\n                    rx_time,\n                    gro,\n                    #[cfg(target_os = \"linux\")]\n                    so_mark_data,\n                })) => {\n                    let mut buf = std::mem::replace(\n                        &mut self.current_buf,\n                        BufFactory::get_max_buf(),\n                    );\n                    buf.truncate(bytes);\n\n                    let send_from = if let Some(dst_addr) = dst_addr_override {\n                        log::trace!(\"overriding local address\"; \"actual_local\" => dst_addr, \"configured_local\" => server_addr);\n                        dst_addr\n                    } else {\n                        server_addr\n                    };\n\n                    let res = self.on_incoming(Incoming {\n                        peer_addr,\n                        local_addr: send_from,\n                        buf,\n                        rx_time,\n                        gro,\n                        #[cfg(target_os = \"linux\")]\n                        so_mark_data,\n                    });\n\n                    if let Err(e) = res {\n                        let err_type = initial_packet_error_type(&e);\n                        self.metrics\n                            .rejected_initial_packet_count(err_type.clone())\n                            .inc();\n\n                        if self.config.enable_expensive_packet_count_metrics {\n                            if let Some(peer_ip) =\n                                quic_expensive_metrics_ip_reduce(peer_addr.ip())\n                            {\n                                self.metrics\n                                    .expensive_rejected_initial_packet_count(\n                                        err_type.clone(),\n                                        peer_ip,\n                                    )\n                                    .inc();\n                            }\n                        }\n\n                        if matches!(\n                            err_type,\n                            labels::QuicInvalidInitialPacketError::Unexpected\n                        ) {\n                            // don't block packet routing on errors\n                            let _ = self.accept_sink.try_send(Err(e));\n                        }\n                    }\n                },\n\n                Poll::Ready(Err(e)) => {\n                    log::error!(\"Incoming packet router encountered recvmsg error\"; \"error\" => e);\n                    continue;\n                },\n\n                Poll::Pending => {\n                    // Check whether any connections are still active\n                    if self.shutdown_tx.is_some() && self.accept_sink.is_closed()\n                    {\n                        self.shutdown_tx = None;\n                    }\n\n                    if self.shutdown_rx.poll_recv(cx).is_ready() {\n                        return Poll::Ready(Ok(()));\n                    }\n\n                    // Process any incoming connection map signals and handle them\n                    self.handle_conn_map_commands();\n\n                    return Poll::Pending;\n                },\n            }\n        }\n    }\n}\n\n/// Categorizes errors that are returned when handling packets which are not\n/// associated with an established connection. The purpose is to suppress\n/// logging of 'expected' errors (e.g. junk data sent to the UDP socket) to\n/// prevent DoS.\nfn initial_packet_error_type(\n    e: &io::Error,\n) -> labels::QuicInvalidInitialPacketError {\n    Some(e)\n        .filter(|e| e.kind() == io::ErrorKind::Other)\n        .and_then(io::Error::get_ref)\n        .and_then(|e| e.downcast_ref())\n        .map_or(\n            labels::QuicInvalidInitialPacketError::Unexpected,\n            Clone::clone,\n        )\n}\n\n/// An [`InitialPacketHandler`] handles unknown quic initials and processes\n/// them; generally accepting new connections (acting as a server), or\n/// establishing a connection to a server (acting as a client). An\n/// [`InboundPacketRouter`] holds an instance of this trait and routes\n/// [`Incoming`] packets to it when it receives initials.\n///\n/// The handler produces [`quiche::Connection`]s which are then turned into\n/// [`QuicConnection`](super::QuicConnection), IoWorker pair.\npub trait InitialPacketHandler {\n    fn update(&mut self, _ctx: &mut Context<'_>) -> io::Result<()> {\n        Ok(())\n    }\n\n    fn handle_initials(\n        &mut self, incoming: Incoming, hdr: Header<'static>,\n        quiche_config: &mut quiche::Config,\n    ) -> io::Result<Option<NewConnection>>;\n}\n\n/// A [`NewConnection`] describes a new [`quiche::Connection`] that can be\n/// driven by an io worker.\npub struct NewConnection {\n    conn: QuicheConnection,\n    pending_cid: Option<ConnectionId<'static>>,\n    initial_pkt: Option<Incoming>,\n    cid_generator: Option<SharedConnectionIdGenerator>,\n    /// When the handshake started. Should be called before [`quiche::accept`]\n    /// or [`quiche::connect`].\n    handshake_start_time: Instant,\n}\n\n// TODO: the router module is private so we can't move these to /tests\n// TODO: Rewrite tests to be Windows compatible\n#[cfg(all(test, unix))]\nmod tests {\n    use super::acceptor::ConnectionAcceptor;\n    use super::acceptor::ConnectionAcceptorConfig;\n    use super::*;\n\n    use crate::http3::settings::Http3Settings;\n    use crate::metrics::DefaultMetrics;\n    use crate::quic::connection::SimpleConnectionIdGenerator;\n    use crate::settings::Config;\n    use crate::settings::Hooks;\n    use crate::settings::QuicSettings;\n    use crate::settings::TlsCertificatePaths;\n    use crate::socket::SocketCapabilities;\n    use crate::ConnectionParams;\n    use crate::ServerH3Driver;\n\n    use datagram_socket::MAX_DATAGRAM_SIZE;\n    use h3i::actions::h3::Action;\n    use std::sync::Arc;\n    use std::time::Duration;\n    use tokio::net::UdpSocket;\n    use tokio::time;\n\n    const TEST_CERT_FILE: &str = concat!(\n        env!(\"CARGO_MANIFEST_DIR\"),\n        \"/\",\n        \"../quiche/examples/cert.crt\"\n    );\n    const TEST_KEY_FILE: &str = concat!(\n        env!(\"CARGO_MANIFEST_DIR\"),\n        \"/\",\n        \"../quiche/examples/cert.key\"\n    );\n\n    fn test_connect(host_port: String) {\n        let h3i_config = h3i::config::Config::new()\n            .with_host_port(\"test.com\".to_string())\n            .with_idle_timeout(2000)\n            .with_connect_to(host_port)\n            .verify_peer(false)\n            .build()\n            .unwrap();\n\n        let conn_close = h3i::quiche::ConnectionError {\n            is_app: true,\n            error_code: h3i::quiche::WireErrorCode::NoError as _,\n            reason: Vec::new(),\n        };\n        let actions = vec![Action::ConnectionClose { error: conn_close }];\n\n        let _ = h3i::client::sync_client::connect(h3i_config, actions, None);\n    }\n\n    #[tokio::test]\n    async fn test_timeout() {\n        // Configure a short idle timeout to speed up connection reclamation as\n        // quiche doesn't support time mocking\n        let quic_settings = QuicSettings {\n            max_idle_timeout: Some(Duration::from_millis(1)),\n            max_recv_udp_payload_size: MAX_DATAGRAM_SIZE,\n            max_send_udp_payload_size: MAX_DATAGRAM_SIZE,\n            ..Default::default()\n        };\n\n        let tls_cert_settings = TlsCertificatePaths {\n            cert: TEST_CERT_FILE,\n            private_key: TEST_KEY_FILE,\n            kind: crate::settings::CertificateKind::X509,\n        };\n\n        let params = ConnectionParams::new_server(\n            quic_settings,\n            tls_cert_settings,\n            Hooks::default(),\n        );\n        let config = Config::new(&params, SocketCapabilities::default()).unwrap();\n\n        let socket = UdpSocket::bind(\"127.0.0.1:0\").await.unwrap();\n        let local_addr = socket.local_addr().unwrap();\n        let host_port = local_addr.to_string();\n        let socket_tx = Arc::new(socket);\n        let socket_rx = Arc::clone(&socket_tx);\n\n        let acceptor = ConnectionAcceptor::new(\n            ConnectionAcceptorConfig {\n                disable_client_ip_validation: config.disable_client_ip_validation,\n                qlog_dir: config.qlog_dir.clone(),\n                keylog_file: config\n                    .keylog_file\n                    .as_ref()\n                    .and_then(|f| f.try_clone().ok()),\n                #[cfg(target_os = \"linux\")]\n                with_pktinfo: false,\n            },\n            Arc::clone(&socket_tx),\n            Default::default(),\n            Arc::new(SimpleConnectionIdGenerator),\n            DefaultMetrics,\n        );\n\n        let (socket_driver, mut incoming) = InboundPacketRouter::new(\n            config,\n            socket_tx,\n            socket_rx,\n            local_addr,\n            acceptor,\n            DefaultMetrics,\n        );\n        tokio::spawn(socket_driver);\n\n        // Start a request and drop it after connection establishment\n        std::thread::spawn(move || test_connect(host_port));\n\n        // Wait for a new connection\n        time::pause();\n\n        let (h3_driver, _) = ServerH3Driver::new(Http3Settings::default());\n        let conn = incoming.recv().await.unwrap().unwrap();\n        let drop_check = conn.incoming_ev_sender.clone();\n        let _conn = conn.start(h3_driver);\n\n        // Poll the incoming until the connection is dropped\n        time::advance(Duration::new(30, 0)).await;\n        time::resume();\n\n        // NOTE: this is a smoke test - in case of issues `notified()` future will\n        // never resolve hanging the test.\n        drop_check.closed().await;\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/result.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::error::Error;\nuse std::io;\n\n/// Generic thread-safe boxed error.\n///\n/// From all our prior experience we've learned that there is very little\n/// practical use in concrete error types. On the surface it seems appealing to\n/// use such errors, because they have, ahem, concrete type. But the flip side\n/// is that code in big projects quickly ends up being polluted with endless\n/// adapter error types to combine different APIs together, or, even worse, an\n/// Error god-object gets introduced to accommodate all possible error types.\n///\n/// On rare occasions concrete error types can be used, where handling of the\n/// error depends on the error kind. But, in practice, such cases are quite\n/// rare.\npub type BoxError = Box<dyn Error + Send + Sync + 'static>;\n/// [Result] alias based on [`BoxError`] for this crate.\npub type QuicResult<T> = Result<T, BoxError>;\n\n/// Extension trait to add methods to [Result].\npub trait QuicResultExt<T, E> {\n    /// Turns the [Result] into an [`io::Result`] with\n    /// [`ErrorKind::Other`](io::ErrorKind::Other).\n    fn into_io(self) -> io::Result<T>\n    where\n        E: Into<BoxError>;\n}\n\nimpl<T, E> QuicResultExt<T, E> for Result<T, E> {\n    #[inline]\n    fn into_io(self) -> io::Result<T>\n    where\n        E: Into<BoxError>,\n    {\n        self.map_err(io::Error::other)\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/settings/config.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse foundations::telemetry::log;\nuse std::borrow::Cow;\nuse std::fs::File;\nuse std::time::Duration;\n\nuse crate::result::QuicResult;\nuse crate::settings::CertificateKind;\nuse crate::settings::ConnectionParams;\nuse crate::settings::TlsCertificatePaths;\nuse crate::socket::SocketCapabilities;\n\n/// Whether `--cfg capture_keylogs` was set at build time. We keep supporting\n/// the `capture_keylogs` feature for backward compatibility.\nconst KEYLOGFILE_ENABLED: bool =\n    cfg!(capture_keylogs) || cfg!(feature = \"capture_keylogs\");\n\n/// Internal representation of the combined configuration for a QUIC connection.\npub(crate) struct Config {\n    pub quiche_config: quiche::Config,\n    pub disable_client_ip_validation: bool,\n    pub qlog_dir: Option<String>,\n    pub has_gso: bool,\n    pub pacing_offload: bool,\n    pub enable_expensive_packet_count_metrics: bool,\n    pub keylog_file: Option<File>,\n    pub listen_backlog: usize,\n    pub handshake_timeout: Option<Duration>,\n    pub has_ippktinfo: bool,\n    pub has_ipv6pktinfo: bool,\n}\n\nimpl AsMut<quiche::Config> for Config {\n    fn as_mut(&mut self) -> &mut quiche::Config {\n        &mut self.quiche_config\n    }\n}\n\nimpl Config {\n    pub(crate) fn new(\n        params: &ConnectionParams, socket_capabilities: SocketCapabilities,\n    ) -> QuicResult<Self> {\n        let quic_settings = &params.settings;\n        let keylog_path = match &quic_settings.keylog_file {\n            Some(f) => Some(Cow::Borrowed(f.as_ref())),\n            None => std::env::var_os(\"SSLKEYLOGFILE\").map(Cow::from),\n        };\n        let keylog_file = keylog_path.and_then(|path| if KEYLOGFILE_ENABLED {\n                File::options().create(true).append(true).open(path)\n                    .inspect_err(|e| log::warn!(\"failed to open SSLKEYLOGFILE\"; \"error\" => e))\n                    .ok()\n            } else {\n                log::warn!(\"SSLKEYLOGFILE is set, but `--cfg capture_keylogs` was not enabled. No keys will be logged.\");\n                None\n            });\n\n        let SocketCapabilities {\n            has_gso,\n            has_txtime: pacing_offload,\n            has_ippktinfo,\n            has_ipv6pktinfo,\n            ..\n        } = socket_capabilities;\n\n        #[cfg(feature = \"gcongestion\")]\n        let pacing_offload = quic_settings.enable_pacing && pacing_offload;\n\n        Ok(Config {\n            quiche_config: make_quiche_config(params, keylog_file.is_some())?,\n            disable_client_ip_validation: quic_settings\n                .disable_client_ip_validation,\n            qlog_dir: quic_settings.qlog_dir.clone(),\n            has_gso,\n            pacing_offload,\n            enable_expensive_packet_count_metrics: quic_settings\n                .enable_expensive_packet_count_metrics,\n            keylog_file,\n            listen_backlog: quic_settings.listen_backlog,\n            handshake_timeout: quic_settings.handshake_timeout,\n            has_ippktinfo,\n            has_ipv6pktinfo,\n        })\n    }\n}\n\nfn make_quiche_config(\n    params: &ConnectionParams, should_log_keys: bool,\n) -> QuicResult<quiche::Config> {\n    let ssl_ctx_builder = params\n        .hooks\n        .connection_hook\n        .as_ref()\n        .zip(params.tls_cert)\n        .and_then(|(hook, tls)| hook.create_custom_ssl_context_builder(tls));\n\n    let mut config = if let Some(builder) = ssl_ctx_builder {\n        quiche::Config::with_boring_ssl_ctx_builder(\n            quiche::PROTOCOL_VERSION,\n            builder,\n        )?\n    } else {\n        quiche_config_with_tls(params.tls_cert)?\n    };\n\n    let quic_settings = &params.settings;\n\n    let alpns: Vec<&[u8]> =\n        quic_settings.alpn.iter().map(Vec::as_slice).collect();\n    config.set_application_protos(&alpns).unwrap();\n\n    if let Some(timeout) = quic_settings.max_idle_timeout {\n        let ms = timeout\n            .as_millis()\n            .try_into()\n            .map_err(|_| \"QuicSettings::max_idle_timeout exceeds u64\")?;\n        config.set_max_idle_timeout(ms);\n    }\n\n    config.enable_dgram(\n        quic_settings.enable_dgram,\n        quic_settings.dgram_recv_max_queue_len,\n        quic_settings.dgram_send_max_queue_len,\n    );\n\n    config.set_max_recv_udp_payload_size(quic_settings.max_recv_udp_payload_size);\n    config.set_max_send_udp_payload_size(quic_settings.max_send_udp_payload_size);\n    config.set_initial_max_data(quic_settings.initial_max_data);\n    config.set_initial_max_stream_data_bidi_local(\n        quic_settings.initial_max_stream_data_bidi_local,\n    );\n    config.set_initial_max_stream_data_bidi_remote(\n        quic_settings.initial_max_stream_data_bidi_remote,\n    );\n    config.set_initial_max_stream_data_uni(\n        quic_settings.initial_max_stream_data_uni,\n    );\n    config.set_initial_max_streams_bidi(quic_settings.initial_max_streams_bidi);\n    config.set_initial_max_streams_uni(quic_settings.initial_max_streams_uni);\n    config.set_disable_active_migration(quic_settings.disable_active_migration);\n    config\n        .set_active_connection_id_limit(quic_settings.active_connection_id_limit);\n    config.set_cc_algorithm_name(quic_settings.cc_algorithm.as_str())?;\n    config.set_initial_congestion_window_packets(\n        quic_settings.initial_congestion_window_packets,\n    );\n    config.set_enable_relaxed_loss_threshold(\n        quic_settings.enable_relaxed_loss_threshold,\n    );\n    config.discover_pmtu(quic_settings.discover_path_mtu);\n    config.set_pmtud_max_probes(quic_settings.pmtud_max_probes);\n    config.enable_hystart(quic_settings.enable_hystart);\n\n    config.enable_pacing(quic_settings.enable_pacing);\n    if let Some(max_pacing_rate) = quic_settings.max_pacing_rate {\n        config.set_max_pacing_rate(max_pacing_rate);\n    }\n\n    if quic_settings.verify_peer {\n        config.verify_peer(quic_settings.verify_peer);\n    }\n\n    config.set_max_connection_window(quic_settings.max_connection_window);\n    config.set_max_stream_window(quic_settings.max_stream_window);\n    config.set_enable_send_streams_blocked(\n        quic_settings.enable_send_streams_blocked,\n    );\n    config.grease(quic_settings.grease);\n    config.set_max_amplification_factor(quic_settings.max_amplification_factor);\n    config.set_send_capacity_factor(quic_settings.send_capacity_factor);\n    config.set_ack_delay_exponent(quic_settings.ack_delay_exponent);\n    config.set_max_ack_delay(quic_settings.max_ack_delay);\n    config.set_path_challenge_recv_max_queue_len(\n        quic_settings.max_path_challenge_recv_queue_len,\n    );\n    config.set_stateless_reset_token(quic_settings.stateless_reset_token);\n    config.set_disable_dcid_reuse(quic_settings.disable_dcid_reuse);\n\n    if let Some(track_unknown_transport_params) =\n        quic_settings.track_unknown_transport_parameters\n    {\n        config.enable_track_unknown_transport_parameters(\n            track_unknown_transport_params,\n        );\n    }\n    if params.settings.enable_early_data {\n        config.enable_early_data();\n    }\n\n    if should_log_keys {\n        config.log_keys();\n    }\n\n    Ok(config)\n}\n\nfn quiche_config_with_tls(\n    tls_cert: Option<TlsCertificatePaths>,\n) -> QuicResult<quiche::Config> {\n    let Some(tls) = tls_cert else {\n        return Ok(quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap());\n    };\n\n    match tls.kind {\n        #[cfg(not(feature = \"rpk\"))]\n        CertificateKind::RawPublicKey => {\n            // TODO: don't compile this enum variant unless rpk feature is enabled\n            panic!(\"Can't use RPK when compiled without rpk feature\");\n        },\n        #[cfg(feature = \"rpk\")]\n        CertificateKind::RawPublicKey => {\n            let mut ssl_ctx_builder = boring::ssl::SslContextBuilder::new_rpk()?;\n            let raw_public_key = read_file(tls.cert)?;\n            ssl_ctx_builder.set_rpk_certificate(&raw_public_key)?;\n\n            let raw_private_key = read_file(tls.private_key)?;\n            let pkey =\n                boring::pkey::PKey::private_key_from_pem(&raw_private_key)?;\n            ssl_ctx_builder.set_null_chain_private_key(&pkey)?;\n\n            Ok(quiche::Config::with_boring_ssl_ctx_builder(\n                quiche::PROTOCOL_VERSION,\n                ssl_ctx_builder,\n            )?)\n        },\n        CertificateKind::X509 => {\n            let mut config =\n                quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n            config.load_cert_chain_from_pem_file(tls.cert)?;\n            config.load_priv_key_from_pem_file(tls.private_key)?;\n            Ok(config)\n        },\n    }\n}\n\n#[cfg(feature = \"rpk\")]\nfn read_file(path: &str) -> QuicResult<Vec<u8>> {\n    use anyhow::Context as _;\n    std::fs::read(path)\n        .with_context(|| format!(\"read {path}\"))\n        .map_err(Into::into)\n}\n"
  },
  {
    "path": "tokio-quiche/src/settings/hooks.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::quic::ConnectionHook;\nuse std::sync::Arc;\n\n/// Hook configuration for use in the QUIC connection lifecycle.\n///\n/// Use these to manage the connection outside of what is possible with an\n/// [`ApplicationOverQuic`](crate::ApplicationOverQuic).\n#[derive(Default, Clone)]\npub struct Hooks {\n    pub connection_hook: Option<Arc<dyn ConnectionHook + Send + Sync + 'static>>,\n    // http3_hook: ...\n}\n\nimpl std::fmt::Debug for Hooks {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        fn hook_status<T>(val: &Option<T>) -> &'static str {\n            match val {\n                Some(_) => \"enabled\",\n                None => \"disabled\",\n            }\n        }\n\n        f.debug_struct(\"Hooks\")\n            .field(\"connection_hook\", &hook_status(&self.connection_hook))\n            .finish()\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/settings/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Configuration for QUIC connections.\n\nmod config;\nmod hooks;\nmod quic;\nmod tls;\n\npub(crate) use self::config::*;\n\npub use self::hooks::*;\npub use self::quic::*;\npub use self::tls::*;\n\n/// Combined configuration parameters required to establish a QUIC connection.\n///\n/// [`ConnectionParams`] aggregates the parameters required for all QUIC\n/// connections, regardless of whether it's a client- or server-side connection.\n/// To construct them, either `ConnectionParams::new_server` or\n/// `ConnectionParams::new_client` must be used. The parameters can be modified\n/// freely after construction.\n#[derive(Default)]\n#[non_exhaustive] // force use of constructor functions\npub struct ConnectionParams<'a> {\n    /// QUIC connection settings.\n    pub settings: QuicSettings,\n    /// Optional TLS credentials to authenticate with.\n    pub tls_cert: Option<TlsCertificatePaths<'a>>,\n    /// Hooks to use for the connection.\n    pub hooks: Hooks,\n    /// Set the session to attempt resumption.\n    pub session: Option<Vec<u8>>,\n}\n\nimpl core::fmt::Debug for ConnectionParams<'_> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        // Avoid printing 'session' since it contains connection secrets.\n        f.debug_struct(\"ConnectionParams\")\n            .field(\"settings\", &self.settings)\n            .field(\"tls_cert\", &self.tls_cert)\n            .field(\"hooks\", &self.hooks)\n            .finish()\n    }\n}\n\nimpl<'a> ConnectionParams<'a> {\n    /// Creates [`ConnectionParams`] for a QUIC server.\n    /// Servers should always specify TLS credentials.\n    #[inline]\n    pub fn new_server(\n        settings: QuicSettings, tls_cert: TlsCertificatePaths<'a>, hooks: Hooks,\n    ) -> Self {\n        Self {\n            settings,\n            tls_cert: Some(tls_cert),\n            hooks,\n            session: None,\n        }\n    }\n\n    /// Creates [`ConnectionParams`] for a QUIC client.\n    /// Clients may enable mTLS by specifying TLS credentials.\n    #[inline]\n    pub fn new_client(\n        settings: QuicSettings, tls_cert: Option<TlsCertificatePaths<'a>>,\n        hooks: Hooks,\n    ) -> Self {\n        Self {\n            settings,\n            tls_cert,\n            hooks,\n            session: None,\n        }\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/settings/quic.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse foundations::settings::settings;\nuse serde_with::serde_as;\nuse serde_with::DurationMilliSeconds;\nuse std::time::Duration;\n\n/// QUIC configuration parameters.\n#[serde_as]\n#[settings]\n#[non_exhaustive]\npub struct QuicSettings {\n    /// Configures the list of supported application protocols.\n    ///\n    /// Defaults to `[b\"h3\"]`.\n    #[serde(skip, default = \"QuicSettings::default_alpn\")]\n    pub alpn: Vec<Vec<u8>>,\n\n    /// Configures whether to enable DATAGRAM frame support. H3 connections\n    /// copy this setting from the underlying QUIC connection.\n    ///\n    /// Defaults to `true`.\n    #[serde(default = \"QuicSettings::default_enable_dgram\")]\n    pub enable_dgram: bool,\n\n    /// Max queue length for received DATAGRAM frames.\n    ///\n    /// Defaults to `2^16`.\n    #[serde(default = \"QuicSettings::default_dgram_max_queue_len\")]\n    pub dgram_recv_max_queue_len: usize,\n\n    /// Max queue length for sending DATAGRAM frames.\n    ///\n    /// Defaults to `2^16`.\n    #[serde(default = \"QuicSettings::default_dgram_max_queue_len\")]\n    pub dgram_send_max_queue_len: usize,\n\n    /// Configures whether to enable early data (0-RTT) support. Currently only\n    /// supported for servers.\n    ///\n    /// Defaults to `false`.\n    pub enable_early_data: bool,\n\n    /// Sets the `initial_max_data` transport parameter.\n    ///\n    /// Defaults to 10 MB.\n    #[serde(default = \"QuicSettings::default_initial_max_data\")]\n    pub initial_max_data: u64,\n\n    /// Sets the `initial_max_stream_data_bidi_local` transport parameter.\n    ///\n    /// Defaults to 1 MB.\n    #[serde(default = \"QuicSettings::default_initial_max_stream_data\")]\n    pub initial_max_stream_data_bidi_local: u64,\n\n    /// Sets the `initial_max_stream_data_bidi_remote` transport parameter.\n    ///\n    /// Defaults to 1 MB.\n    #[serde(default = \"QuicSettings::default_initial_max_stream_data\")]\n    pub initial_max_stream_data_bidi_remote: u64,\n\n    /// Sets the `initial_max_stream_data_uni` transport parameter.\n    ///\n    /// Defaults to 1 MB.\n    #[serde(default = \"QuicSettings::default_initial_max_stream_data\")]\n    pub initial_max_stream_data_uni: u64,\n\n    /// Sets the `initial_max_streams_bidi` transport parameter.\n    ///\n    /// Defaults to `100`.\n    #[serde(default = \"QuicSettings::default_initial_max_streams\")]\n    pub initial_max_streams_bidi: u64,\n\n    /// Sets the `initial_max_streams_uni` transport parameter.\n    ///\n    /// Defaults to `100`.\n    #[serde(default = \"QuicSettings::default_initial_max_streams\")]\n    pub initial_max_streams_uni: u64,\n\n    /// Configures the max idle timeout of the connection in milliseconds. The\n    /// real idle timeout is the minimum of this and the peer's\n    /// `max_idle_timeout`.\n    ///\n    /// Defaults to 56 seconds.\n    #[serde(\n        rename = \"max_idle_timeout_ms\",\n        default = \"QuicSettings::default_max_idle_timeout\"\n    )]\n    #[serde_as(as = \"Option<DurationMilliSeconds>\")]\n    pub max_idle_timeout: Option<Duration>,\n\n    /// Configures whether the local endpoint supports active connection\n    /// migration.\n    ///\n    /// Defaults to `true` (meaning disabled).\n    #[serde(default = \"QuicSettings::default_disable_active_migration\")]\n    pub disable_active_migration: bool,\n\n    /// Sets the `active_connection_id_limit` transport parameter.\n    ///\n    /// Defaults to 2. Note that values less than 2 will be ignored.\n    #[serde(default = \"QuicSettings::default_active_connection_id_limit\")]\n    pub active_connection_id_limit: u64,\n\n    /// Sets the maximum incoming UDP payload size.\n    ///\n    /// Defaults to 1350 bytes.\n    #[serde(default = \"QuicSettings::default_max_recv_udp_payload_size\")]\n    pub max_recv_udp_payload_size: usize,\n\n    /// Sets the maximum outgoing UDP payload size.\n    ///\n    /// Defaults to 1350 bytes.\n    #[serde(default = \"QuicSettings::default_max_send_udp_payload_size\")]\n    pub max_send_udp_payload_size: usize,\n\n    /// Whether to validate client IPs in QUIC initials.\n    ///\n    /// If set to `true`, any received QUIC initial will immediately spawn a\n    /// connection and start crypto operations for the handshake. Otherwise,\n    /// the client is asked to execute a stateless retry first (the default).\n    pub disable_client_ip_validation: bool,\n\n    /// Path to a file in which TLS secrets will be logged in\n    /// [SSLKEYLOGFILE format](https://tlswg.org/sslkeylogfile/draft-ietf-tls-keylogfile.html).\n    pub keylog_file: Option<String>,\n\n    /// Path to a directory where QLOG files will be saved.\n    pub qlog_dir: Option<String>,\n\n    /// Congestion control algorithm to use.\n    ///\n    /// For available values, see\n    /// [`CongestionControlAlgorithm`](quiche::CongestionControlAlgorithm).\n    ///\n    /// Defaults to `cubic`.\n    #[serde(default = \"QuicSettings::default_cc_algorithm\")]\n    pub cc_algorithm: String,\n\n    /// The default initial congestion window size in terms of packet count.\n    ///\n    /// Defaults to 10.\n    #[serde(default = \"QuicSettings::default_initial_congestion_window_packets\")]\n    pub initial_congestion_window_packets: usize,\n\n    /// Configures whether to enable relaxed loss detection on spurious loss.\n    ///\n    /// Defaults to `false`.\n    pub enable_relaxed_loss_threshold: bool,\n\n    /// Configures whether to do path MTU discovery.\n    ///\n    /// Defaults to `false`.\n    pub discover_path_mtu: bool,\n\n    /// Configures the maximum number of PMTUD probe attempts before treating\n    /// a size as failed.\n    ///\n    /// Defaults to 3 per [RFC 8899 Section 5.1.2](https://datatracker.ietf.org/doc/html/rfc8899#section-5.1.2).\n    /// If 0 is passed, the default value is used.\n    #[serde(default = \"QuicSettings::default_pmtud_max_probes\")]\n    pub pmtud_max_probes: u8,\n\n    /// Whether to use HyStart++ (only with `cubic` and `reno` CC).\n    ///\n    /// Defaults to `true`.\n    #[serde(default = \"QuicSettings::default_enable_hystart\")]\n    pub enable_hystart: bool,\n\n    /// Optionally enables pacing for outgoing packets.\n    ///\n    /// Note: this also requires pacing-compatible\n    /// [`SocketCapabilities`](crate::socket::SocketCapabilities).\n    pub enable_pacing: bool,\n\n    /// Sets the max value for pacing rate.\n    ///\n    /// By default, there is no limit.\n    pub max_pacing_rate: Option<u64>,\n\n    /// Optionally enables expensive versions of the\n    /// `accepted_initial_quic_packet_count`\n    /// and `rejected_initial_quic_packet_count` metrics.\n    ///\n    /// The expensive versions add a label for the peer IP subnet (`/24` for\n    /// IPv4, `/32` for IPv6). They thus generate many more time series if\n    /// peers are arbitrary eyeballs from the global Internet.\n    pub enable_expensive_packet_count_metrics: bool,\n\n    /// Forwards [`quiche`] logs into the logging system currently used by\n    /// [`foundations`].\n    ///\n    /// Defaults to `false`.\n    ///\n    /// # Warning\n    /// This should **only be used for local debugging**. `quiche` can emit lots\n    /// (and lots, and lots) of logs (the TRACE level emits a log record for\n    /// every packet and frame) and you can very easily overwhelm your\n    /// logging pipeline.\n    pub capture_quiche_logs: bool,\n\n    /// A timeout for the QUIC handshake, in milliseconds.\n    ///\n    /// Disabled by default.\n    #[serde(rename = \"handshake_timeout_ms\")]\n    #[serde_as(as = \"Option<DurationMilliSeconds>\")]\n    pub handshake_timeout: Option<Duration>,\n\n    /// The maximum number of newly-created connections that will be queued for\n    /// the application to receive. Not applicable to client-side usage.\n    ///\n    /// Defaults to 1024 connections.\n    #[serde(default = \"QuicSettings::default_listen_backlog\")]\n    pub listen_backlog: usize,\n\n    /// Whether or not to verify the peer's certificate.\n    ///\n    /// Defaults to `false`, meaning no peer verification is performed. Note\n    /// that clients should usually set this value to `true` - see\n    /// [`verify_peer()`] for more.\n    ///\n    /// [`verify_peer()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.verify_peer\n    pub verify_peer: bool,\n\n    /// The maximum size of the receiver connection flow control window.\n    ///\n    /// Defaults to 24MB.\n    #[serde(default = \"QuicSettings::default_max_connection_window\")]\n    pub max_connection_window: u64,\n\n    /// The maximum size of the receiveer stream flow control window.\n    ///\n    /// Defaults to 16MB.\n    #[serde(default = \"QuicSettings::default_max_stream_window\")]\n    pub max_stream_window: u64,\n\n    /// If true, send an advisory STREAMS_BLOCKED frame when the\n    /// application's local stream creation attempts fail due to the\n    /// peer advertised MAX_STREAMS limit.\n    ///\n    /// Defaults to false.\n    pub enable_send_streams_blocked: bool,\n\n    /// Configures whether to send GREASE values.\n    ///\n    /// Defaults to true.\n    #[serde(default = \"QuicSettings::default_grease\")]\n    pub grease: bool,\n\n    /// Sets the anti-amplification limit factor.\n    ///\n    /// Defaults to 3.\n    #[serde(default = \"QuicSettings::default_amplification_factor\")]\n    pub max_amplification_factor: usize,\n\n    /// Sets the send capacity factor.\n    ///\n    /// A factor greater than 1 allows the connection to buffer more outbound\n    /// data than can be sent at this moment. This can improve throughput by\n    /// reducing time spent waiting for new data.\n    ///\n    /// Defaults to 1.\n    #[serde(default = \"QuicSettings::default_send_capacity_factor\")]\n    pub send_capacity_factor: f64,\n\n    /// Sets the `ack_delay_exponent` transport parameter.\n    ///\n    /// Defaults to 3.\n    #[serde(default = \"QuicSettings::default_ack_delay_exponent\")]\n    pub ack_delay_exponent: u64,\n\n    /// Sets the `max_ack_delay` transport parameter.\n    ///\n    /// Defaults to 25.\n    #[serde(default = \"QuicSettings::default_max_ack_delay\")]\n    pub max_ack_delay: u64,\n\n    /// Configures the max number of queued received PATH_CHALLENGE frames.\n    ///\n    /// Defaults to 3.\n    #[serde(default = \"QuicSettings::default_max_path_challenge_recv_queue_len\")]\n    pub max_path_challenge_recv_queue_len: usize,\n\n    /// Sets the initial stateless reset token.\n    ///\n    /// Note that this applies only to server-side connections - on client-side\n    /// connections, this is a no-op.\n    ///\n    /// Defaults to `None`.\n    pub stateless_reset_token: Option<u128>,\n\n    /// Sets whether the QUIC connection should avoid reusing DCIDs over\n    /// different paths.\n    ///\n    /// Defaults to `false`. See [`set_disable_dcid_reuse()`] for more.\n    ///\n    /// [`set_disable_dcid_reuse()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.disable_dcid_reuse\n    pub disable_dcid_reuse: bool,\n\n    /// Specifies the number of bytes used to track unknown transport\n    /// parameters.\n    ///\n    /// Defaults to `None`, e.g., unknown transport parameters will not be\n    /// tracked. See [`enable_track_unknown_transport_parameters()`] for\n    /// more.\n    ///\n    /// [`enable_track_unknown_transport_parameters()`]: https://docs.rs/quiche/latest/quiche/struct.Config.html#method.enable_track_unknown_transport_parameters\n    pub track_unknown_transport_parameters: Option<usize>,\n}\n\nimpl QuicSettings {\n    #[inline]\n    fn default_alpn() -> Vec<Vec<u8>> {\n        quiche::h3::APPLICATION_PROTOCOL\n            .iter()\n            .map(|v| v.to_vec())\n            .collect()\n    }\n\n    #[inline]\n    fn default_enable_dgram() -> bool {\n        true\n    }\n\n    #[inline]\n    fn default_dgram_max_queue_len() -> usize {\n        65536\n    }\n\n    #[inline]\n    fn default_initial_max_data() -> u64 {\n        10_000_000\n    }\n\n    #[inline]\n    fn default_initial_max_stream_data() -> u64 {\n        1_000_000\n    }\n\n    #[inline]\n    fn default_initial_max_streams() -> u64 {\n        100\n    }\n\n    #[inline]\n    fn default_max_idle_timeout() -> Option<Duration> {\n        Some(Duration::from_secs(56))\n    }\n\n    #[inline]\n    fn default_max_recv_udp_payload_size() -> usize {\n        1350\n    }\n\n    #[inline]\n    fn default_max_send_udp_payload_size() -> usize {\n        1350\n    }\n\n    #[inline]\n    fn default_disable_active_migration() -> bool {\n        true\n    }\n\n    #[inline]\n    fn default_cc_algorithm() -> String {\n        \"cubic\".to_string()\n    }\n\n    #[inline]\n    fn default_initial_congestion_window_packets() -> usize {\n        10\n    }\n\n    #[inline]\n    fn default_enable_hystart() -> bool {\n        true\n    }\n\n    #[inline]\n    fn default_listen_backlog() -> usize {\n        // Given a worst-case 1 minute handshake timeout and up to 4096 concurrent\n        // handshakes, we will dequeue at least 70 connections per second.\n        // This means this backlog size limits the queueing latency to\n        // ~15s.\n        1024\n    }\n\n    #[inline]\n    fn default_max_connection_window() -> u64 {\n        24 * 1024 * 1024\n    }\n\n    #[inline]\n    fn default_max_stream_window() -> u64 {\n        16 * 1024 * 1024\n    }\n\n    #[inline]\n    fn default_grease() -> bool {\n        true\n    }\n\n    #[inline]\n    fn default_amplification_factor() -> usize {\n        3\n    }\n\n    #[inline]\n    fn default_send_capacity_factor() -> f64 {\n        1.0\n    }\n\n    #[inline]\n    fn default_ack_delay_exponent() -> u64 {\n        3\n    }\n\n    #[inline]\n    fn default_max_ack_delay() -> u64 {\n        25\n    }\n\n    #[inline]\n    fn default_active_connection_id_limit() -> u64 {\n        2\n    }\n\n    #[inline]\n    fn default_max_path_challenge_recv_queue_len() -> usize {\n        3\n    }\n\n    #[inline]\n    fn default_pmtud_max_probes() -> u8 {\n        3\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::QuicSettings;\n    use std::time::Duration;\n\n    #[test]\n    fn timeouts_parse_as_milliseconds() {\n        let quic = serde_json::from_str::<QuicSettings>(\n            r#\"{ \"handshake_timeout_ms\": 5000, \"max_idle_timeout_ms\": 7000 }\"#,\n        )\n        .unwrap();\n\n        assert_eq!(quic.handshake_timeout.unwrap(), Duration::from_secs(5));\n        assert_eq!(quic.max_idle_timeout.unwrap(), Duration::from_secs(7));\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/settings/tls.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n/// TLS credentials to authenticate the endpoint.\n#[derive(Clone, Copy, Debug)]\npub struct TlsCertificatePaths<'p> {\n    /// Path to the endpoint's TLS certificate.\n    pub cert: &'p str,\n    /// Path to the endpoint's private key.\n    pub private_key: &'p str,\n    /// `cert`'s PKI certificate type.\n    pub kind: CertificateKind,\n}\n\n/// Types of PKI certificates supported by the crate.\n#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]\npub enum CertificateKind {\n    /// Standard X509 TLS certificate.\n    #[default]\n    X509,\n    /// [Raw public key] TLS certificate.\n    ///\n    ///\n    /// [Raw public key]: https://datatracker.ietf.org/doc/html/rfc7250\n    RawPublicKey,\n}\n"
  },
  {
    "path": "tokio-quiche/src/socket/capabilities.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n#[cfg(target_os = \"linux\")]\nmod linux_imports {\n    pub use libc::c_int;\n    pub use libc::c_void;\n    pub use libc::sock_txtime;\n    pub use libc::socklen_t;\n    pub use libc::IPPROTO_IP;\n    pub use libc::IPPROTO_IPV6;\n    pub use libc::IPV6_MTU_DISCOVER;\n    pub use libc::IPV6_PMTUDISC_PROBE;\n    pub use libc::IP_MTU_DISCOVER;\n    pub use libc::IP_PMTUDISC_PROBE;\n    pub use libc::SOL_SOCKET;\n    pub use libc::SO_RCVMARK;\n    pub use nix::errno::Errno;\n    pub use nix::sys::socket::getsockopt;\n    pub use nix::sys::socket::setsockopt;\n    pub use nix::sys::socket::sockopt::IpFreebind;\n    pub use nix::sys::socket::sockopt::IpTransparent;\n    pub use nix::sys::socket::sockopt::Ipv4OrigDstAddr;\n    pub use nix::sys::socket::sockopt::Ipv4PacketInfo;\n    pub use nix::sys::socket::sockopt::Ipv6OrigDstAddr;\n    pub use nix::sys::socket::sockopt::Ipv6RecvPacketInfo;\n    #[cfg(feature = \"perf-quic-listener-metrics\")]\n    pub use nix::sys::socket::sockopt::ReceiveTimestampns;\n    pub use nix::sys::socket::sockopt::RxqOvfl;\n    pub use nix::sys::socket::sockopt::TxTime;\n    pub use nix::sys::socket::sockopt::UdpGroSegment;\n    pub use nix::sys::socket::sockopt::UdpGsoSegment;\n    pub use nix::sys::socket::SetSockOpt;\n    pub use std::io;\n    pub use std::os::fd::AsFd;\n    pub use std::os::fd::AsRawFd;\n    pub use std::os::fd::BorrowedFd;\n}\n\n#[cfg(target_os = \"linux\")]\nuse linux_imports::*;\n\n#[cfg(target_os = \"linux\")]\n#[derive(Clone)]\nstruct IpMtuDiscoverProbe;\n\n#[cfg(target_os = \"linux\")]\nimpl SetSockOpt for IpMtuDiscoverProbe {\n    type Val = ();\n\n    fn set<F: AsFd>(&self, fd: &F, _val: &Self::Val) -> nix::Result<()> {\n        let pmtud_mode: c_int = IP_PMTUDISC_PROBE;\n        let ret = unsafe {\n            libc::setsockopt(\n                fd.as_fd().as_raw_fd(),\n                IPPROTO_IP,\n                IP_MTU_DISCOVER,\n                &pmtud_mode as *const c_int as *const c_void,\n                std::mem::size_of::<c_int>() as socklen_t,\n            )\n        };\n\n        match ret {\n            0 => Ok(()),\n            _ => Err(Errno::last()),\n        }\n    }\n}\n\n#[cfg(target_os = \"linux\")]\n#[derive(Clone)]\nstruct Ipv6MtuDiscoverProbe;\n\n#[cfg(target_os = \"linux\")]\nimpl SetSockOpt for Ipv6MtuDiscoverProbe {\n    type Val = ();\n\n    fn set<F: AsFd>(&self, fd: &F, _val: &Self::Val) -> nix::Result<()> {\n        let pmtud_mode: c_int = IPV6_PMTUDISC_PROBE;\n        let ret = unsafe {\n            libc::setsockopt(\n                fd.as_fd().as_raw_fd(),\n                IPPROTO_IPV6,\n                IPV6_MTU_DISCOVER,\n                &pmtud_mode as *const c_int as *const c_void,\n                std::mem::size_of::<c_int>() as socklen_t,\n            )\n        };\n\n        match ret {\n            0 => Ok(()),\n            _ => Err(Errno::last()),\n        }\n    }\n}\n\n#[cfg(target_os = \"linux\")]\n#[derive(Clone)]\nstruct RcvMark;\n\n#[cfg(target_os = \"linux\")]\nimpl SetSockOpt for RcvMark {\n    type Val = ();\n\n    fn set<F: AsFd>(&self, fd: &F, _val: &Self::Val) -> nix::Result<()> {\n        // https://elixir.bootlin.com/linux/v6.17/source/net/core/sock.c#L1523\n        const ENABLE_SOCKOPT: i32 = 1;\n\n        let ret = unsafe {\n            libc::setsockopt(\n                fd.as_fd().as_raw_fd(),\n                SOL_SOCKET,\n                SO_RCVMARK,\n                &ENABLE_SOCKOPT as *const c_int as *const c_void,\n                std::mem::size_of::<c_int>() as socklen_t,\n            )\n        };\n\n        match ret {\n            0 => Ok(()),\n            _ => Err(Errno::last()),\n        }\n    }\n}\n\n/// Builder to enable Linux sockopts which improve QUIC performance.\n#[cfg(target_os = \"linux\")]\npub struct SocketCapabilitiesBuilder<'s> {\n    socket: BorrowedFd<'s>,\n    cap: SocketCapabilities,\n}\n\n#[cfg(target_os = \"linux\")]\nimpl<'s> SocketCapabilitiesBuilder<'s> {\n    /// Creates a new sockopt builder for `socket`.\n    pub fn new<S: AsFd>(socket: &'s S) -> Self {\n        Self {\n            socket: socket.as_fd(),\n            cap: Default::default(),\n        }\n    }\n\n    /// Enables [`UDP_SEGMENT`](https://man7.org/linux/man-pages/man7/udp.7.html),\n    /// a generic segmentation offload (GSO).\n    ///\n    /// GSO improves transmit performance by treating multiple sequential UDP\n    /// packets as a single entity in the kernel. Segmentation into\n    /// individual packets happens in the NIC, if it supports GSO. The\n    /// parameter specifies the packet size.\n    pub fn gso(&mut self) -> io::Result<()> {\n        // We initialize GSO on the socket with the maximum possible segment size\n        // to prevent accidentally setting it too small and running into\n        // issues when increasing max_send_udp_payload_size later on.\n        //\n        // https://elixir.bootlin.com/linux/v6.14.6/source/net/ipv4/udp.c#L2998\n        // https://elixir.bootlin.com/linux/v6.14.6/source/include/vdso/limits.h#L5\n        setsockopt(&self.socket.as_fd(), UdpGsoSegment, &(u16::MAX as i32))?;\n        self.cap.has_gso = true;\n        Ok(())\n    }\n\n    /// Enables [`SO_RXQ_OVFL`](https://man7.org/linux/man-pages/man7/socket.7.html),\n    /// which reports dropped packets due to insufficient buffer space.\n    pub fn check_udp_drop(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), RxqOvfl, &1)?;\n\n        self.cap.check_udp_drop = true;\n        Ok(())\n    }\n\n    /// Enables [`SO_TXTIME`](https://man7.org/linux/man-pages/man8/tc-etf.8.html)\n    /// to control packet transmit timestamps for QUIC pacing.\n    pub fn txtime(&mut self) -> io::Result<()> {\n        let cfg = sock_txtime {\n            clockid: libc::CLOCK_MONOTONIC,\n            flags: 0,\n        };\n        setsockopt(&self.socket.as_fd(), TxTime, &cfg)?;\n\n        self.cap.has_txtime = true;\n        Ok(())\n    }\n\n    /// Enables [`SO_TIMESTAMPNS`](https://man7.org/linux/man-pages/man7/socket.7.html),\n    /// which records a wall-clock timestamp for each received packet.\n    #[cfg(feature = \"perf-quic-listener-metrics\")]\n    pub fn rxtime(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), ReceiveTimestampns, &true)?;\n\n        self.cap.has_rxtime = true;\n        Ok(())\n    }\n\n    /// Enables [`UDP_GRO`](https://man7.org/linux/man-pages/man7/udp.7.html),\n    /// a generic receive offload (GRO).\n    ///\n    /// GRO improves receive performance by allowing the kernel to yield\n    /// multiple UDP packets in one [`recvmsg(2)`](https://man7.org/linux/man-pages/man2/recv.2.html)\n    /// call. It is the equivalent of GSO for the receive path.\n    pub fn gro(&mut self) -> io::Result<()> {\n        UdpGroSegment.set(&self.socket.as_fd(), &true)?;\n\n        self.cap.has_gro = true;\n        Ok(())\n    }\n\n    /// Enables [`IP_PKTINFO`](https://man7.org/linux/man-pages/man7/ip.7.html)\n    /// to control the source IP in outbound IPv4 packets.\n    pub fn ipv4_pktinfo(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), Ipv4PacketInfo, &true)?;\n\n        self.cap.has_ippktinfo = true;\n        Ok(())\n    }\n\n    /// Enables [`IP_RECVORIGDSTADDR`](https://man7.org/linux/man-pages/man7/ip.7.html),\n    /// which reports each packet's real IPv4 destination address.\n    ///\n    /// This can be different from the socket's local address due to netfilter\n    /// TPROXY rules or eBPF redirects.\n    pub fn ipv4_recvorigdstaddr(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), Ipv4OrigDstAddr, &true)?;\n\n        self.cap.has_iprecvorigdstaddr = true;\n        Ok(())\n    }\n\n    /// Enables [`IPV6_RECVPKTINFO`](https://man7.org/linux/man-pages/man7/ipv6.7.html)\n    /// to control the source IP in outbound IPv6 packets.\n    pub fn ipv6_pktinfo(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), Ipv6RecvPacketInfo, &true)?;\n\n        self.cap.has_ipv6pktinfo = true;\n        Ok(())\n    }\n\n    /// Enables [`IPV6_RECVORIGDSTADDR`](https://elixir.bootlin.com/linux/v6.12/source/net/ipv6/datagram.c#L722-L743),\n    /// which reports each packet's real IPv6 destination address.\n    ///\n    /// This can be different from the socket's local address due to netfilter\n    /// TPROXY rules or eBPF redirects.\n    pub fn ipv6_recvorigdstaddr(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), Ipv6OrigDstAddr, &true)?;\n\n        self.cap.has_ipv6recvorigdstaddr = true;\n        Ok(())\n    }\n\n    /// Sets [`IP_MTU_DISCOVER`](https://man7.org/linux/man-pages/man7/ip.7.html), to\n    /// `IP_PMTUDISC_PROBE`, which disables kernel PMTUD and sets the `DF`\n    /// (Don't Fragment) flag.\n    pub fn ip_mtu_discover_probe(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), IpMtuDiscoverProbe, &())?;\n\n        self.cap.has_ip_mtu_discover_probe = true;\n        Ok(())\n    }\n\n    /// Sets [`IPV6_MTU_DISCOVER`](https://man7.org/linux/man-pages/man7/ipv6.7.html), to\n    /// `IPV6_PMTUDISC_PROBE`, which disables kernel PMTUD and sets the `DF`\n    /// (Don't Fragment) flag.\n    pub fn ipv6_mtu_discover_probe(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), Ipv6MtuDiscoverProbe, &())?;\n\n        self.cap.has_ipv6_mtu_discover_probe = true;\n        Ok(())\n    }\n\n    /// Tests whether [`IP_FREEBIND`](https://man7.org/linux/man-pages/man7/ip.7.html)\n    /// or [`IP_TRANSPARENT`](https://man7.org/linux/man-pages/man7/ip.7.html) are\n    /// enabled for this socket.\n    ///\n    /// # Warning\n    /// These sockopts require elevated permissions to enable, so the builder\n    /// will only check their status. **If neither of them is enabled, the\n    /// `PKTINFO` sockopts will cause errors when sending packets.**\n    pub fn allows_nonlocal_source(&self) -> io::Result<bool> {\n        Ok(getsockopt(&self.socket.as_fd(), IpFreebind)? ||\n            getsockopt(&self.socket.as_fd(), IpTransparent)?)\n    }\n\n    pub fn rcvmark(&mut self) -> io::Result<()> {\n        setsockopt(&self.socket.as_fd(), RcvMark, &())?;\n\n        self.cap.has_mark = true;\n        Ok(())\n    }\n\n    /// Consumes the builder and returns the configured [`SocketCapabilities`].\n    pub fn finish(self) -> SocketCapabilities {\n        self.cap\n    }\n}\n\n// TODO(erittenhouse): use `dgram`'s SocketCapabilities when we migrate over\n#[cfg_attr(not(target_os = \"linux\"), expect(rustdoc::broken_intra_doc_links))]\n/// Indicators of sockopts configured for a socket.\n///\n/// On Linux, a socket can be configured using a [`SocketCapabilitiesBuilder`],\n/// which returns the sockopts that were applied successfully. By default, all\n/// options are assumed to be disabled (including on OSes besides Linux).\n///\n/// As a shortcut, you may call `apply_all_and_get_compatibility` to apply the\n/// maxmimum set of capabilities supported by this crate. The result will\n/// indicate which options were actually enabled.\n#[derive(Debug, Default)]\npub struct SocketCapabilities {\n    /// Indicates if the socket has `UDP_SEGMENT` enabled.\n    pub(crate) has_gso: bool,\n\n    /// Indicates if the socket has `SO_RXQ_OVFL` set.\n    // NOTE: RX-side sockopts are `expect(dead_code)` because we check for\n    // received cmsgs directly\n    #[cfg_attr(not(target_os = \"linux\"), expect(dead_code))]\n    pub(crate) check_udp_drop: bool,\n\n    /// Indicates if the socket was configured with `SO_TXTIME`.\n    pub(crate) has_txtime: bool,\n\n    /// Indicates if the socket has `SO_TIMESTAMPNS` enabled.\n    #[cfg_attr(\n        not(all(target_os = \"linux\", feature = \"perf-quic-listener-metrics\")),\n        expect(dead_code)\n    )]\n    pub(crate) has_rxtime: bool,\n\n    /// Indicates if the socket has `UDP_GRO` enabled.\n    #[cfg_attr(not(target_os = \"linux\"), expect(dead_code))]\n    pub(crate) has_gro: bool,\n\n    /// Indicates if the socket has `IP_PKTINFO` set.\n    pub(crate) has_ippktinfo: bool,\n\n    /// Indicates if the socket has `IP_RECVORIGDSTADDR` set.\n    #[cfg_attr(not(target_os = \"linux\"), expect(dead_code))]\n    pub(crate) has_iprecvorigdstaddr: bool,\n\n    /// Indicates if the socket has `IPV6_RECVPKTINFO` set.\n    pub(crate) has_ipv6pktinfo: bool,\n\n    /// Indicates if the socket has `IPV6_RECVORIGDSTADDR` set.\n    #[cfg_attr(not(target_os = \"linux\"), expect(dead_code))]\n    pub(crate) has_ipv6recvorigdstaddr: bool,\n\n    // Indicates if the socket has `IP_MTU_DISCOVER` set to `IP_PMTUDISC_PROBE`.\n    #[cfg_attr(not(target_os = \"linux\"), expect(dead_code))]\n    pub(crate) has_ip_mtu_discover_probe: bool,\n\n    // Indicates if the socket has `IPV6_MTU_DISCOVER` set to\n    // `IPV6_PMTUDISC_PROBE`.\n    #[cfg_attr(not(target_os = \"linux\"), expect(dead_code))]\n    pub(crate) has_ipv6_mtu_discover_probe: bool,\n\n    /// Indicates if the socket is set to receive `SO_MARK` messages via\n    /// `SO_RCVMARK`.\n    #[cfg_attr(not(target_os = \"linux\"), expect(dead_code))]\n    pub(crate) has_mark: bool,\n}\n\nimpl SocketCapabilities {\n    /// Tries to enable all supported sockopts and returns indicators\n    /// of which settings were successfully applied.\n    #[cfg(target_os = \"linux\")]\n    pub fn apply_all_and_get_compatibility<S>(socket: &S) -> Self\n    where\n        S: AsFd,\n    {\n        let mut b = SocketCapabilitiesBuilder::new(socket);\n        let _ = b.gso();\n        let _ = b.check_udp_drop();\n        let _ = b.txtime();\n        #[cfg(feature = \"perf-quic-listener-metrics\")]\n        let _ = b.rxtime();\n        let _ = b.gro();\n        let _ = b.rcvmark();\n\n        // We can't determine if this is an IPv4 or IPv6 socket, so try setting\n        // the relevant options for both\n        let _ = b.ip_mtu_discover_probe();\n        let _ = b.ipv6_mtu_discover_probe();\n        if let Ok(true) = b.allows_nonlocal_source() {\n            let _ = b.ipv4_pktinfo();\n            let _ = b.ipv4_recvorigdstaddr();\n            let _ = b.ipv6_pktinfo();\n            let _ = b.ipv6_recvorigdstaddr();\n        }\n        b.finish()\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/socket/connected.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse datagram_socket::DatagramSocketRecv;\nuse datagram_socket::DatagramSocketSend;\nuse std::io;\nuse std::net::SocketAddr;\nuse std::sync::Arc;\nuse tokio::net::UdpSocket;\n\nuse super::SocketCapabilities;\n\n/// A connected datagram socket with separate `send` and `recv` halves.\n///\n/// [`Socket`] abstracts over both real UDP-based connections and in-process\n/// tunneled flows like (multi-hop) MASQUE flows. It uses the\n/// [`datagram_socket`] traits for this purpose.\n#[derive(Debug)]\npub struct Socket<Tx, Rx> {\n    /// The sending half of the connection. This generally supports concurrent\n    /// senders.\n    pub send: Tx,\n    /// The receiving half of the connection. This is generally owned by a\n    /// single caller.\n    pub recv: Rx,\n    /// The address of the local endpoint.\n    pub local_addr: SocketAddr,\n    /// The address of the remote endpoint.\n    pub peer_addr: SocketAddr,\n    /// The [`SocketCapabilities`] to use for this socket.\n    ///\n    /// By default, [`Socket`]s are constructed with all capabilities\n    /// disabled. On Linux, you can use `apply_max_capabilities()` to (try\n    /// to) enable all supported capabilities.\n    pub capabilities: SocketCapabilities,\n}\n\n/// A type-erased variant of [`Socket`] with boxed `Tx` and `Rx` halves.\npub type BoxedSocket = Socket<\n    Box<dyn DatagramSocketSend + Send + 'static>,\n    Box<dyn DatagramSocketRecv + Sync + 'static>,\n>;\n\nimpl<Tx, Rx> Socket<Tx, Rx> {\n    /// Creates a [`Socket`] from a [`UdpSocket`] by wrapping the file\n    /// descriptor in an [`Arc`].\n    pub fn from_udp(\n        socket: UdpSocket,\n    ) -> io::Result<Socket<Arc<UdpSocket>, Arc<UdpSocket>>> {\n        let local_addr = socket.local_addr()?;\n        let peer_addr = socket.peer_addr()?;\n\n        let send = Arc::new(socket);\n        let recv = Arc::clone(&send);\n\n        Ok(Socket {\n            send,\n            recv,\n            local_addr,\n            peer_addr,\n            capabilities: SocketCapabilities::default(),\n        })\n    }\n}\n\nimpl<Tx, Rx> Socket<Tx, Rx>\nwhere\n    Tx: DatagramSocketSend,\n    Rx: DatagramSocketRecv,\n{\n    /// Checks whether both `send` and `recv` refer to the same underlying\n    /// UDP socket FD and returns a reference to that socket.\n    ///\n    /// # Note\n    /// The file descriptor _numbers_ have to be identical. A pair of FDs\n    /// created by [`dup(2)`](https://man7.org/linux/man-pages/man2/dup.2.html) will\n    /// return `None`.\n    #[cfg(unix)]\n    pub fn as_udp_socket(&self) -> Option<&UdpSocket> {\n        use std::os::fd::AsRawFd;\n\n        let send = self.send.as_udp_socket()?;\n        let recv = self.recv.as_udp_socket()?;\n        (send.as_raw_fd() == recv.as_raw_fd()).then_some(send)\n    }\n\n    /// Tries to enable all sockopts supported by the crate for this socket.\n    ///\n    /// This does nothing unless `send` and `recv` refer to the same UDP socket\n    /// FD. See `SocketCapabilities::apply_all_and_get_compatibility` for\n    /// details.\n    #[cfg(target_os = \"linux\")]\n    pub fn apply_max_capabilities(&mut self) {\n        let Some(socket) = self.as_udp_socket() else {\n            return;\n        };\n\n        let capabilities =\n            SocketCapabilities::apply_all_and_get_compatibility(socket);\n        self.capabilities = capabilities;\n    }\n}\n\nimpl TryFrom<UdpSocket> for Socket<Arc<UdpSocket>, Arc<UdpSocket>> {\n    type Error = io::Error;\n\n    fn try_from(socket: UdpSocket) -> Result<Self, Self::Error> {\n        Self::from_udp(socket)\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/socket/listener.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::fmt;\nuse std::io;\n#[cfg(unix)]\nuse std::os::fd::AsFd;\n#[cfg(unix)]\nuse std::os::fd::AsRawFd;\n#[cfg(unix)]\nuse std::os::fd::BorrowedFd;\n#[cfg(unix)]\nuse std::os::fd::RawFd;\nuse std::sync::Arc;\nuse tokio::net::UdpSocket;\n\nuse super::SocketCapabilities;\nuse crate::quic::SimpleConnectionIdGenerator;\nuse crate::ConnectionIdGenerator;\n\n/// Wrapper around a [`UdpSocket`] for server-side QUIC connections.\n///\n/// The wrapper carries socket-specific parameters, in contrast to the\n/// [`settings`](crate::settings) structs which apply to _all_ sockets\n/// for a given QUIC server.\n///\n/// To create a [`QuicListener`], you may either instantiate the struct yourself\n/// or use one of the `TryFrom` implementations.\npub struct QuicListener {\n    /// The wrapped [tokio] socket.\n    pub socket: UdpSocket,\n    /// The [`ConnectionIdGenerator`] to use for connections arriving on this\n    /// socket. This can be one shared instance for all sockets, but\n    /// socket-specific ID generators are also possible.\n    pub cid_generator: Arc<dyn ConnectionIdGenerator<'static>>,\n    /// The [`SocketCapabilities`] to use for this socket.\n    ///\n    /// By default, [`QuicListener`]s are constructed with all capabilities\n    /// disabled. On Linux, you can use `apply_max_capabilities()` to (try\n    /// to) enable all supported capabilities.\n    pub capabilities: SocketCapabilities,\n}\n\nimpl QuicListener {\n    /// Tries to enable all sockopts supported by the crate for this socket.\n    /// See `SocketCapabilities::apply_all_and_get_compatibility` for details.\n    #[cfg(target_os = \"linux\")]\n    pub fn apply_max_capabilities(&mut self) {\n        let capabilities =\n            SocketCapabilities::apply_all_and_get_compatibility(&self.socket);\n        self.capabilities = capabilities;\n    }\n}\n\nimpl fmt::Debug for QuicListener {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"QuicListener\")\n            .field(\"socket\", &self.socket)\n            .field(\"cid_generator\", &\"dyn ConnectionIdGenerator\")\n            .field(\"capabilities\", &self.capabilities)\n            .finish()\n    }\n}\n\nimpl TryFrom<UdpSocket> for QuicListener {\n    type Error = io::Error;\n\n    fn try_from(socket: UdpSocket) -> Result<Self, Self::Error> {\n        Ok(Self {\n            socket,\n            cid_generator: Arc::new(SimpleConnectionIdGenerator),\n            capabilities: SocketCapabilities::default(),\n        })\n    }\n}\n\nimpl TryFrom<std::net::UdpSocket> for QuicListener {\n    type Error = io::Error;\n\n    fn try_from(socket: std::net::UdpSocket) -> Result<Self, Self::Error> {\n        socket.set_nonblocking(true)?;\n        let socket = UdpSocket::from_std(socket)?;\n        Self::try_from(socket)\n    }\n}\n\n#[cfg(unix)]\nimpl AsFd for QuicListener {\n    fn as_fd(&self) -> BorrowedFd<'_> {\n        self.socket.as_fd()\n    }\n}\n\n#[cfg(unix)]\nimpl AsRawFd for QuicListener {\n    fn as_raw_fd(&self) -> RawFd {\n        self.socket.as_raw_fd()\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/src/socket/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! Network socket utilities and wrappers.\n\nmod capabilities;\nmod connected;\nmod listener;\n\npub use self::capabilities::SocketCapabilities;\n#[cfg(target_os = \"linux\")]\npub use self::capabilities::SocketCapabilitiesBuilder;\npub use self::connected::BoxedSocket;\npub use self::connected::Socket;\npub use self::listener::QuicListener;\n"
  },
  {
    "path": "tokio-quiche/tests/fixtures/h3i_fixtures.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse h3i::actions::h3::send_headers_frame;\nuse h3i::actions::h3::Action;\nuse h3i::actions::h3::StreamEvent;\nuse h3i::actions::h3::StreamEventType;\nuse h3i::actions::h3::WaitType;\nuse h3i::client::connection_summary::ConnectionSummary;\nuse h3i::client::ClientError;\nuse h3i::frame::H3iFrame;\nuse h3i::quiche::h3::Header;\nuse h3i::quiche::h3::{\n    self,\n};\nuse h3i::quiche::ConnectionError;\nuse h3i::quiche::WireErrorCode;\nuse url::Url;\n\n/// Default h3i config, connects to \"test.com\"\npub fn h3i_config(url: &str) -> h3i::config::Config {\n    let url = url.strip_prefix(\"http://\").unwrap_or(url);\n    let final_url = url.split('/').next().unwrap_or(url);\n\n    h3i::config::Config::new()\n        .with_host_port(\"test.com\".to_string())\n        .with_idle_timeout(2000)\n        .with_max_streams_bidi(100)\n        .with_max_streams_uni(100)\n        .with_max_data(1000000)\n        .with_max_stream_data_bidi_local(1000000)\n        .with_max_stream_data_bidi_remote(1000000)\n        .with_max_stream_data_uni(100000)\n        .with_connect_to(final_url.to_string())\n        .verify_peer(false)\n        .build()\n        .unwrap()\n}\n\npub fn default_headers() -> Vec<Header> {\n    default_headers_with_authority(\"test.com\")\n}\n\npub fn default_headers_with_authority(host: &str) -> Vec<Header> {\n    vec![\n        Header::new(b\":method\", b\"GET\"),\n        Header::new(b\":scheme\", b\"https\"),\n        Header::new(b\":authority\", host.as_bytes()),\n        Header::new(b\":path\", b\"/\"),\n    ]\n}\n\npub fn url_headers(url: &Url) -> Vec<Header> {\n    use url::Position::AfterQuery;\n    use url::Position::BeforeHost;\n    use url::Position::BeforePath;\n    let authority = &url[BeforeHost..BeforePath];\n    let path = &url[BeforePath..AfterQuery];\n\n    vec![\n        Header::new(b\":method\", b\"GET\"),\n        Header::new(b\":scheme\", url.scheme().as_bytes()),\n        Header::new(b\":authority\", authority.as_bytes()),\n        Header::new(b\":path\", path.as_bytes()),\n    ]\n}\n\npub async fn summarize_connection(\n    h3i: h3i::config::Config, actions: Vec<Action>,\n) -> ConnectionSummary {\n    tokio::task::spawn_blocking(move || {\n        h3i::client::sync_client::connect(h3i, actions, None).unwrap()\n    })\n    .await\n    .unwrap()\n}\n\npub async fn request(\n    url: &str, count: u64,\n) -> Result<ConnectionSummary, ClientError> {\n    let h3i = h3i_config(url);\n    let url = Url::parse(url).expect(\"h3i request URL is invalid\");\n    let headers = url_headers(&url);\n\n    let mut actions = Vec::new();\n    for req in 0..count {\n        let stream_id = req * 4;\n        actions.push(send_headers_frame(stream_id, true, headers.clone()));\n        actions.push(Action::FlushPackets);\n        actions.push(Action::Wait {\n            wait_type: WaitType::StreamEvent(StreamEvent {\n                stream_id,\n                event_type: StreamEventType::Finished,\n            }),\n        });\n    }\n\n    actions.push(Action::ConnectionClose {\n        error: ConnectionError {\n            is_app: true,\n            error_code: WireErrorCode::NoError as _,\n            reason: Vec::new(),\n        },\n    });\n\n    tokio::task::spawn_blocking(move || {\n        h3i::client::sync_client::connect(h3i, actions, None)\n    })\n    .await\n    .unwrap()\n}\n\npub fn received_status_code_on_stream(\n    summary: &ConnectionSummary, stream: u64, code: u16,\n) -> bool {\n    summary\n        .stream_map\n        .headers_on_stream(stream)\n        .iter()\n        .any(|e| {\n            let u16 =\n                std::str::from_utf8(e.status_code().expect(\"no status code\"))\n                    .expect(\"invalid utf8 in status code\")\n                    .parse::<u16>()\n                    .expect(\"unparseable status code\");\n\n            u16 == code\n        })\n}\n\npub fn stream_body(summary: &ConnectionSummary, stream: u64) -> Option<String> {\n    let mut has_body = false;\n    let body: String = summary\n        .stream_map\n        .stream(stream)\n        .into_iter()\n        .filter_map(|f| {\n            if let H3iFrame::QuicheH3(h3::frame::Frame::Data { payload }) = f {\n                has_body = true;\n                return Some(\n                    String::from_utf8(payload).expect(\"response body not UTF-8\"),\n                );\n            }\n            None\n        })\n        .collect();\n\n    has_body.then_some(body)\n}\n"
  },
  {
    "path": "tokio-quiche/tests/fixtures/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse datagram_socket::QuicAuditStats;\nuse tokio_quiche::buf_factory::BufFactory;\nuse tokio_quiche::http3::driver::H3Event;\nuse tokio_quiche::http3::driver::InboundFrame;\nuse tokio_quiche::http3::driver::InboundFrameStream;\nuse tokio_quiche::http3::driver::IncomingH3Headers;\nuse tokio_quiche::http3::driver::OutboundFrame;\nuse tokio_quiche::http3::driver::OutboundFrameSender;\nuse tokio_quiche::http3::driver::ServerH3Event;\nuse tokio_quiche::listen;\nuse tokio_quiche::metrics::DefaultMetrics;\nuse tokio_quiche::quic::ConnectionHook;\nuse tokio_quiche::quiche::h3::Header;\nuse tokio_quiche::quiche::h3::NameValue;\nuse tokio_quiche::quiche::h3::{\n    self,\n};\nuse tokio_quiche::settings::Hooks;\nuse tokio_quiche::settings::TlsCertificatePaths;\nuse tokio_quiche::ConnectionParams;\nuse tokio_quiche::ServerH3Controller;\n\nuse futures::stream::FuturesUnordered;\nuse futures::Future;\nuse futures::SinkExt;\nuse futures::StreamExt;\nuse regex::Regex;\nuse std::collections::HashMap;\nuse std::collections::HashSet;\nuse std::net::SocketAddr;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::AtomicUsize;\nuse std::sync::atomic::Ordering;\nuse std::sync::Arc;\nuse tokio::select;\nuse tokio::sync::mpsc;\n\n// Re-export for convenience\npub use tokio_quiche::http3::settings::Http3Settings;\npub use tokio_quiche::quic::ConnectionShutdownBehaviour;\npub use tokio_quiche::settings::QuicSettings;\npub use tokio_quiche::QuicResult;\npub use tokio_quiche::QuicResultExt;\npub use tokio_quiche::ServerH3Connection;\npub use tokio_quiche::ServerH3Driver;\n\npub mod h3i_fixtures;\n\nuse h3i_fixtures::stream_body;\n\npub const TEST_CERT_FILE: &str = concat!(\n    env!(\"CARGO_MANIFEST_DIR\"),\n    \"/\",\n    \"../quiche/examples/cert.crt\"\n);\npub const TEST_KEY_FILE: &str = concat!(\n    env!(\"CARGO_MANIFEST_DIR\"),\n    \"/\",\n    \"../quiche/examples/cert.key\"\n);\n\npub struct TestConnectionHook {\n    was_called: Arc<AtomicBool>,\n}\n\nimpl TestConnectionHook {\n    pub fn new() -> Arc<Self> {\n        Arc::new(Self {\n            was_called: Arc::new(AtomicBool::new(false)),\n        })\n    }\n\n    pub fn was_called(&self) -> bool {\n        self.was_called.load(Ordering::SeqCst)\n    }\n}\n\nimpl ConnectionHook for TestConnectionHook {\n    fn create_custom_ssl_context_builder(\n        &self, _settings: TlsCertificatePaths<'_>,\n    ) -> Option<boring::ssl::SslContextBuilder> {\n        self.was_called.store(true, Ordering::SeqCst);\n        None\n    }\n}\n\npub async fn request(\n    url: String, count: u64,\n) -> QuicResult<HashMap<u64, String>> {\n    let summary = h3i_fixtures::request(&url, count)\n        .await\n        .expect(\"requests failed\");\n    let map = (0..count)\n        .map(|req| {\n            let stream_id = req * 4;\n            let body =\n                stream_body(&summary, stream_id).expect(\"missing response body\");\n            (stream_id, body)\n        })\n        .collect();\n    Ok(map)\n}\n\npub async fn serve_connection_details(\n    h3_controller: &mut ServerH3Controller, request_counter: Arc<AtomicUsize>,\n) -> QuicResult<()> {\n    let event_rx = h3_controller.event_receiver_mut();\n    let mut request_futs = FuturesUnordered::new();\n\n    loop {\n        select! {\n            Some(event) = event_rx.recv() => {\n                match event {\n                    ServerH3Event::Core(event) => {\n                        match event {\n                            H3Event::IncomingSettings {..} | H3Event::BodyBytesReceived { .. } | H3Event::StreamClosed { .. } | H3Event::IncomingHeaders(..) => {},\n                            H3Event::ConnectionError(err) => { break Err(err.into()); }\n                            H3Event::ConnectionShutdown(Some(err)) => { break Err(err.into()); }\n                            _ => unreachable!()\n                        }\n                    }\n\n                    ServerH3Event::Headers{ incoming_headers, ..} => {\n                        let IncomingH3Headers {\n                                stream_id, headers, send, recv, ..\n                            } = incoming_headers;\n\n                            request_counter.fetch_add(1, Ordering::SeqCst);\n                            request_futs.push(handle_forwarded_headers_frame(stream_id, headers, send, recv));\n                    }\n                }\n            }\n            Some(_) = request_futs.next() => {}\n            else => { break Ok(()); }\n        }\n    }\n}\n\npub async fn handle_connection(mut connection: ServerH3Connection) {\n    let _ = serve_connection_details(\n        &mut connection.h3_controller,\n        Default::default(),\n    )\n    .await;\n}\n\npub async fn handle_forwarded_headers_frame(\n    stream_id: u64, list: Vec<Header>, mut send: OutboundFrameSender,\n    mut recv: InboundFrameStream,\n) {\n    send.send(OutboundFrame::Headers(\n        vec![h3::Header::new(b\":status\", b\"200\")],\n        None,\n    ))\n    .await\n    .unwrap();\n\n    let path = list\n        .iter()\n        .find_map(|l| (l.name() == b\":path\").then(|| l.value().to_vec()))\n        .unwrap();\n\n    while let Some(frame) = recv.recv().await {\n        match frame {\n            InboundFrame::Body(_, fin) =>\n                if fin {\n                    let res = format!(\n                        \"{stream_id},GET {}|\",\n                        String::from_utf8(path).unwrap()\n                    );\n                    send.send(OutboundFrame::body(\n                        BufFactory::buf_from_slice(res.as_bytes()),\n                        true,\n                    ))\n                    .await\n                    .unwrap();\n                    return;\n                },\n            InboundFrame::Datagram(_) => unreachable!(),\n        }\n    }\n}\n\npub fn start_server() -> (\n    String,\n    Arc<TestConnectionHook>,\n    mpsc::UnboundedReceiver<Arc<QuicAuditStats>>,\n) {\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.max_send_udp_payload_size = 1400;\n    quic_settings.max_recv_udp_payload_size = 1400;\n\n    let hook = TestConnectionHook::new();\n\n    let (url, audit_stats_rx) = start_server_with_settings(\n        quic_settings,\n        Http3Settings::default(),\n        hook.clone(),\n        handle_connection,\n    );\n    (url, hook, audit_stats_rx)\n}\n\npub fn start_server_with_settings<F, Fut>(\n    quic_settings: QuicSettings, http3_settings: Http3Settings,\n    hook: Arc<impl ConnectionHook + Send + Sync + 'static>, hdl: F,\n) -> (String, mpsc::UnboundedReceiver<Arc<QuicAuditStats>>)\nwhere\n    F: Fn(ServerH3Connection) -> Fut + Send + Clone + 'static,\n    Fut: Future<Output = ()> + Send,\n{\n    let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    let url = format!(\"http://127.0.0.1:{}\", socket.local_addr().unwrap().port());\n\n    let tls_cert_settings = TlsCertificatePaths {\n        cert: TEST_CERT_FILE,\n        private_key: TEST_KEY_FILE,\n        kind: tokio_quiche::settings::CertificateKind::X509,\n    };\n\n    let hooks = Hooks {\n        connection_hook: Some(hook),\n    };\n\n    let params =\n        ConnectionParams::new_server(quic_settings, tls_cert_settings, hooks);\n    let mut stream = listen(vec![socket], params, DefaultMetrics)\n        .unwrap()\n        .remove(0);\n\n    let (audit_stats_tx, audit_stats_rx) = mpsc::unbounded_channel();\n\n    tokio::spawn(async move {\n        loop {\n            let (h3_driver, h3_controller) =\n                ServerH3Driver::new(http3_settings.clone());\n            let conn = stream.next().await.unwrap().unwrap().start(h3_driver);\n            let h3_over_quic = ServerH3Connection::new(conn, h3_controller);\n\n            let audit_stats = Arc::clone(h3_over_quic.audit_log_stats());\n            let hdl = hdl.clone();\n            let tx = audit_stats_tx.clone();\n            tokio::spawn(async move {\n                hdl(h3_over_quic).await;\n                let _ = tx.send(audit_stats);\n            });\n        }\n    });\n\n    (url, audit_stats_rx)\n}\n\npub fn extract_host_ipv4(url: &str) -> SocketAddr {\n    let url = url::Url::parse(url).expect(\"url should be valid\");\n    match (url.host(), url.port()) {\n        (Some(url::Host::Ipv4(addr)), Some(port)) =>\n            SocketAddr::new(addr.into(), port),\n        _ => panic!(\"invalid server address\"),\n    }\n}\n\npub fn map_responses(\n    responses: Vec<HashMap<u64, String>>,\n) -> HashMap<usize, HashSet<usize>> {\n    let mut map = HashMap::<_, HashSet<_>>::default();\n    let res_info_re =\n        Regex::new(r\"^(?P<stream_id>\\d+),GET /(?P<conn_num>\\d+)$\").unwrap();\n\n    for resp in responses {\n        for (_, content) in resp {\n            for res in content.split('|') {\n                if res.is_empty() {\n                    continue;\n                }\n\n                let caps = res_info_re.captures(res).unwrap();\n                let conn_num =\n                    caps.name(\"conn_num\").unwrap().as_str().parse().unwrap();\n                let stream_id =\n                    caps.name(\"stream_id\").unwrap().as_str().parse().unwrap();\n\n                map.entry(conn_num).or_default().insert(stream_id);\n            }\n        }\n    }\n\n    map\n}\n"
  },
  {
    "path": "tokio-quiche/tests/integration_tests/async_callbacks.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::fixtures::*;\nuse h3i_fixtures::received_status_code_on_stream;\n\nuse boring::ssl::BoxSelectCertFinish;\nuse boring::ssl::ClientHello;\nuse boring::ssl::SslContextBuilder;\nuse boring::ssl::SslFiletype;\nuse boring::ssl::SslMethod;\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::Ordering;\nuse std::sync::Arc;\nuse tokio::task::yield_now;\nuse tokio_quiche::quic::ConnectionHook;\nuse tokio_quiche::settings::TlsCertificatePaths;\n\n#[tokio::test]\nasync fn test_hello_world_async_callbacks() {\n    // TODO: migrate this to rxtx-h3, copied from examples/client as a simple\n    // Hello World to sanity check that the client builder works.\n\n    struct TestAsyncCallbackConnectionHook {\n        was_called: Arc<AtomicBool>,\n    }\n\n    impl ConnectionHook for TestAsyncCallbackConnectionHook {\n        fn create_custom_ssl_context_builder(\n            &self, _settings: TlsCertificatePaths<'_>,\n        ) -> Option<SslContextBuilder> {\n            let mut ssl_ctx_builder =\n                SslContextBuilder::new(SslMethod::tls()).ok()?;\n            ssl_ctx_builder.set_async_select_certificate_callback(|_| {\n                Ok(Box::pin(async {\n                    yield_now().await;\n                    Ok(Box::new(|_: ClientHello<'_>| Ok(()))\n                        as BoxSelectCertFinish)\n                }))\n            });\n\n            ssl_ctx_builder\n                .set_private_key_file(TEST_KEY_FILE, SslFiletype::PEM)\n                .unwrap();\n\n            ssl_ctx_builder\n                .set_certificate_chain_file(TEST_CERT_FILE)\n                .unwrap();\n\n            self.was_called.store(true, Ordering::SeqCst);\n\n            Some(ssl_ctx_builder)\n        }\n    }\n\n    let hook = Arc::new(TestAsyncCallbackConnectionHook {\n        was_called: Arc::new(AtomicBool::new(false)),\n    });\n    let (url, _) = start_server_with_settings(\n        QuicSettings::default(),\n        Http3Settings::default(),\n        hook.clone(),\n        handle_connection,\n    );\n\n    let url = format!(\"{url}/1\");\n    let summary = h3i_fixtures::request(&url, 1)\n        .await\n        .expect(\"request failed\");\n\n    assert!(received_status_code_on_stream(&summary, 0, 200));\n    assert!(hook.was_called.load(Ordering::SeqCst));\n}\n\n#[tokio::test]\nasync fn test_async_callbacks_fail_after_initial_send() {\n    // TODO: migrate this to rxtx-h3, copied from examples/client as a simple\n    // Hello World to sanity check that the client builder works.\n    use h3i::client::ClientError;\n\n    struct TestAsyncCallbackConnectionHook {}\n\n    impl ConnectionHook for TestAsyncCallbackConnectionHook {\n        fn create_custom_ssl_context_builder(\n            &self, _settings: TlsCertificatePaths<'_>,\n        ) -> Option<SslContextBuilder> {\n            let mut ssl_ctx_builder =\n                SslContextBuilder::new(SslMethod::tls()).ok()?;\n            ssl_ctx_builder.set_async_select_certificate_callback(|_| {\n                Ok(Box::pin(async {\n                    // Async callbacks in tokio quiche are driven by calls to\n                    // quiche's `send` and `recv` methods.\n                    // `send` and `recv` will call SSL_do_handshake once\n                    // per invocation. As such, at least 3 successful invocations\n                    // to `send` and `recv` are needed to\n                    // trigger a handshake failure in the `send`\n                    // invocation that stems from the `wait_for_data_or_handshake`\n                    // future in the select branch.\n                    yield_now().await;\n                    yield_now().await;\n                    yield_now().await;\n                    Err(boring::ssl::AsyncSelectCertError)\n                }))\n            });\n\n            ssl_ctx_builder\n                .set_private_key_file(TEST_KEY_FILE, SslFiletype::PEM)\n                .unwrap();\n\n            ssl_ctx_builder\n                .set_certificate_chain_file(TEST_CERT_FILE)\n                .unwrap();\n\n            Some(ssl_ctx_builder)\n        }\n    }\n\n    let hook = Arc::new(TestAsyncCallbackConnectionHook {});\n    let (url, _) = start_server_with_settings(\n        QuicSettings::default(),\n        Http3Settings::default(),\n        hook.clone(),\n        handle_connection,\n    );\n\n    let url = format!(\"{url}/1\");\n    let client_res = h3i_fixtures::request(&url, 1).await;\n    assert!(matches!(client_res, Err(ClientError::HandshakeFail)));\n}\n"
  },
  {
    "path": "tokio-quiche/tests/integration_tests/connection_close.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::time::Duration;\n\nuse crate::fixtures::*;\nuse h3i_fixtures::default_headers;\nuse h3i_fixtures::h3i_config;\nuse h3i_fixtures::received_status_code_on_stream;\nuse h3i_fixtures::summarize_connection;\n\nuse h3i::actions::h3::send_headers_frame;\nuse h3i::actions::h3::Action;\nuse h3i::actions::h3::StreamEvent;\nuse h3i::actions::h3::StreamEventType;\nuse h3i::actions::h3::WaitType;\nuse h3i::quiche;\nuse h3i::quiche::h3::Header;\n\n#[tokio::test]\nasync fn test_requests_per_connection_limit() -> QuicResult<()> {\n    const MAX_REQS: u64 = 10;\n\n    let hook = TestConnectionHook::new();\n    let (url, _) = start_server_with_settings(\n        QuicSettings::default(),\n        Http3Settings {\n            max_requests_per_connection: Some(MAX_REQS),\n            ..Default::default()\n        },\n        hook,\n        handle_connection,\n    );\n\n    let h3i = h3i_config(&url);\n    let mut actions = vec![];\n\n    for i in 0..MAX_REQS {\n        actions.push(send_headers_frame(i * 4, true, default_headers()));\n        actions.push(Action::FlushPackets);\n        actions.push(Action::Wait {\n            wait_type: WaitType::StreamEvent(StreamEvent {\n                stream_id: i * 4,\n                event_type: StreamEventType::Headers,\n            }),\n        });\n    }\n\n    // This last action should fail due to request limits on the connection being\n    // breached\n    actions.push(send_headers_frame(MAX_REQS * 4, true, default_headers()));\n    actions.push(Action::FlushPackets);\n\n    let summary = summarize_connection(h3i, actions).await;\n\n    for i in 0..MAX_REQS {\n        assert!(\n            received_status_code_on_stream(&summary, i * 4, 200),\n            \"request {i} didn't have a status code OK\",\n        );\n    }\n    assert!(summary.stream_map.stream(MAX_REQS * 4).is_empty());\n\n    let error = summary\n        .conn_close_details\n        .peer_error()\n        .expect(\"no error received\");\n    assert_eq!(error.error_code, quiche::h3::WireErrorCode::NoError as u64);\n\n    Ok(())\n}\n\n#[tokio::test]\nasync fn test_max_header_list_size_limit() -> QuicResult<()> {\n    let hook = TestConnectionHook::new();\n    let (url, mut audit_stats_rx) = start_server_with_settings(\n        QuicSettings::default(),\n        Http3Settings {\n            max_header_list_size: Some(5_000),\n            ..Default::default()\n        },\n        hook,\n        handle_connection,\n    );\n\n    let h3i = h3i_config(&url);\n\n    let mut small_headers = default_headers();\n    small_headers.push(Header::new(b\"a\", vec![b'0'; 4000].as_slice()));\n    let mut big_headers = default_headers();\n    big_headers.push(Header::new(b\"a\", vec![b'0'; 5000].as_slice()));\n\n    let actions = vec![\n        send_headers_frame(0, true, small_headers),\n        Action::FlushPackets,\n        Action::Wait {\n            wait_type: WaitType::StreamEvent(StreamEvent {\n                stream_id: 0,\n                event_type: StreamEventType::Headers,\n            }),\n        },\n        send_headers_frame(4, true, big_headers),\n    ];\n\n    let summary = summarize_connection(h3i, actions).await;\n\n    assert!(received_status_code_on_stream(&summary, 0, 200));\n    assert!(summary.stream_map.stream(4).is_empty());\n\n    let error = summary\n        .conn_close_details\n        .peer_error()\n        .expect(\"no error received\");\n    assert_eq!(\n        error.error_code,\n        quiche::h3::WireErrorCode::ExcessiveLoad as u64\n    );\n\n    // Verify the QuicAuditStats has the correct error code set\n    let audit_stats = audit_stats_rx\n        .recv()\n        .await\n        .expect(\"audit stats not received\");\n\n    // The server sent the EXCESSIVE_LOAD error, so it should be recorded as a\n    // sent application error code\n    assert_eq!(\n        audit_stats.sent_conn_close_application_error_code(),\n        quiche::h3::WireErrorCode::ExcessiveLoad as i64,\n        \"QuicAuditStats should have recorded the sent H3_EXCESSIVE_LOAD error code\"\n    );\n\n    Ok(())\n}\n\n#[tokio::test]\nasync fn test_no_connection_close_frame_on_idle_timeout() -> QuicResult<()> {\n    const IDLE_TIMEOUT: Duration = Duration::from_secs(1);\n\n    let hook = TestConnectionHook::new();\n\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.max_idle_timeout = Some(IDLE_TIMEOUT);\n\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings::default(),\n        hook,\n        handle_connection,\n    );\n\n    let h3i = h3i_config(&url);\n    let actions = vec![Action::Wait {\n        wait_type: WaitType::WaitDuration(IDLE_TIMEOUT.mul_f32(1.5)),\n    }];\n\n    let summary = summarize_connection(h3i, actions).await;\n    assert!(summary.conn_close_details.no_err());\n\n    Ok(())\n}\n"
  },
  {
    "path": "tokio-quiche/tests/integration_tests/headers.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::fixtures::*;\n\nuse futures::SinkExt;\n\nuse tokio_quiche::buf_factory::BufFactory;\nuse tokio_quiche::http3::driver::H3Event;\nuse tokio_quiche::http3::driver::IncomingH3Headers;\nuse tokio_quiche::http3::driver::OutboundFrame;\nuse tokio_quiche::http3::driver::ServerH3Event;\nuse tokio_quiche::quiche::h3::Header;\n\n#[tokio::test]\nasync fn test_additional_headers() {\n    let hook = TestConnectionHook::new();\n\n    let (url, _) = start_server_with_settings(\n        QuicSettings::default(),\n        Http3Settings::default(),\n        hook,\n        move |mut h3_conn| async move {\n            let event_rx = h3_conn.h3_controller.event_receiver_mut();\n\n            while let Some(event) = event_rx.recv().await {\n                match event {\n                    ServerH3Event::Core(event) =>\n                        if let H3Event::ConnectionShutdown(_) = event {\n                            break;\n                        },\n\n                    ServerH3Event::Headers {\n                        incoming_headers, ..\n                    } => {\n                        let IncomingH3Headers { mut send, .. } = incoming_headers;\n\n                        // Send initial headers.\n                        send.send(OutboundFrame::Headers(\n                            vec![Header::new(b\":status\", b\"103\")],\n                            None,\n                        ))\n                        .await\n                        .unwrap();\n\n                        // Delay sending additional headers to the next cycle\n                        // just to make sure things work properly if the frames\n                        // aren't sent back to back.\n                        tokio::task::yield_now().await;\n\n                        // Send additional headers.\n                        send.send(OutboundFrame::Headers(\n                            vec![Header::new(b\":status\", b\"200\")],\n                            None,\n                        ))\n                        .await\n                        .unwrap();\n\n                        // Send fin\n                        send.send(OutboundFrame::Body(\n                            BufFactory::get_empty_buf(),\n                            true,\n                        ))\n                        .await\n                        .unwrap();\n                    },\n                }\n            }\n        },\n    );\n\n    let summary = h3i_fixtures::request(&url, 1)\n        .await\n        .expect(\"request failed\");\n\n    let mut headers = summary.stream_map.headers_on_stream(0).into_iter();\n\n    assert_eq!(\n        headers.next().expect(\"initial headers\").status_code(),\n        Some(&Vec::from(\"103\".as_bytes()))\n    );\n    assert_eq!(\n        headers.next().expect(\"additional headers\").status_code(),\n        Some(&Vec::from(\"200\".as_bytes()))\n    );\n    assert!(headers.next().is_none());\n}\n"
  },
  {
    "path": "tokio-quiche/tests/integration_tests/migration.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse h3i::quiche;\nuse std::net::SocketAddr;\nuse tokio_quiche::quic::SimpleConnectionIdGenerator;\nuse tokio_quiche::ConnectionIdGenerator as _;\n\nuse crate::fixtures::*;\n\n#[tokio::test]\nasync fn test_passive_migration() {\n    run_migration_test(false, 12345).await;\n}\n\n#[tokio::test]\nasync fn test_active_migration() {\n    run_migration_test(true, 23456).await;\n}\n\n/// Tests that the client can migrate either actively or passively.\n///\n/// Active migration means the client intentionally chooses a new local address\n/// to switch to. In this case, it must select a new DCID that was previously\n/// issued to it by the server. The server then also switches to a new DCID\n/// that was generated by the client.\n///\n/// Passive migration occurs when the client's address changes while keeping\n/// the same source and destination CIDs. The client is not necessarily aware\n/// of the change (e.g. due to NAT rebinding). Under these circumstances, the\n/// endpoints are allowed to keep talking to each other with the existing\n/// DCIDs.\n///\n/// The test simply binds a UDP socket on one address which is then used to\n/// complete the handshake and send an initial HTTP/3 request. Next, it\n/// switches (actively or passively) to a new socket bound to a different port\n/// and sends an additional HTTP/3 request. If both requests complete that\n/// means that the client was successfully migrated to the new address.\n///\n/// This requires using \"plain\" quiche as a client to properly control when and\n/// where packets are sent to, which is not possible using h3i.\nasync fn run_migration_test(active: bool, base_port: u16) {\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.active_connection_id_limit = 2;\n    quic_settings.disable_active_migration = !active;\n    quic_settings.disable_dcid_reuse = false;\n\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings::default(),\n        TestConnectionHook::new(),\n        handle_connection,\n    );\n    let server_addr = extract_host_ipv4(&url);\n\n    let mut client_config =\n        quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n    client_config.set_application_protos(&[b\"h3\"]).unwrap();\n    client_config.set_initial_max_data(1500);\n    client_config.set_initial_max_stream_data_bidi_local(1500);\n    client_config.set_initial_max_stream_data_bidi_remote(1500);\n    client_config.set_initial_max_stream_data_uni(1500);\n    client_config.set_initial_max_streams_bidi(10);\n    client_config.set_initial_max_streams_uni(3);\n    client_config.set_active_connection_id_limit(2);\n    // QUICv1 server can't initiate migration but disable it explicitly anyway.\n    client_config.set_disable_active_migration(true);\n    client_config.verify_peer(false);\n\n    let client_scid = SimpleConnectionIdGenerator.new_connection_id();\n\n    let client_addr = SocketAddr::new(\"127.0.0.1\".parse().unwrap(), base_port);\n    let socket = tokio::net::UdpSocket::bind(client_addr).await.unwrap();\n\n    let mut conn = quiche::connect(\n        Some(\"test.com\"),\n        &client_scid,\n        client_addr,\n        server_addr,\n        &mut client_config,\n    )\n    .unwrap();\n\n    if active {\n        // Supply a second SCID to the server to facilitate active migration\n        let extra_scid = SimpleConnectionIdGenerator.new_connection_id();\n        conn.new_scid(&extra_scid, 0xAABBCCDDEEFF0123454678, false)\n            .unwrap();\n    }\n\n    // Handshake.\n    while !conn.is_established() {\n        emit_flight(&socket, &mut conn).await;\n        process_flight(&socket, client_addr, &mut conn).await;\n    }\n\n    // Create a new HTTP/3 connection once the QUIC connection is established.\n    let h3_config = quiche::h3::Config::new().unwrap();\n    let mut h3_conn =\n        quiche::h3::Connection::with_transport(&mut conn, &h3_config).unwrap();\n\n    // Client sends first request on the initial path.\n    let req = vec![\n        quiche::h3::Header::new(b\":method\", b\"GET\"),\n        quiche::h3::Header::new(b\":scheme\", b\"https\"),\n        quiche::h3::Header::new(b\":authority\", b\"test.com\"),\n        quiche::h3::Header::new(b\":path\", b\"/\"),\n        quiche::h3::Header::new(b\"user-agent\", b\"quiche\"),\n    ];\n\n    h3_conn.send_request(&mut conn, &req, true).unwrap();\n    emit_flight(&socket, &mut conn).await;\n    process_flight(&socket, client_addr, &mut conn).await;\n\n    assert_eq!(process_h3_events(&mut h3_conn, &mut conn), (true, true));\n\n    // Client migrates to new address.\n    let migrated_addr = SocketAddr::new(client_addr.ip(), base_port + 1);\n    let migrated_socket =\n        tokio::net::UdpSocket::bind(migrated_addr).await.unwrap();\n\n    let client_addr = if active {\n        // We actively switch the connection to `migrated_addr` and report that\n        // address for all packets we receive from now on.\n        conn.migrate_source(migrated_addr)\n            .expect(\"active migration should succeed\");\n        migrated_addr\n    } else {\n        // We keep using the original client address to simulate the fact that the\n        // client doesn't know that the path changes (e.g. due to NAT rebinding).\n        client_addr\n    };\n\n    // Client sends second request on the new address.\n    h3_conn.send_request(&mut conn, &req, true).unwrap();\n    emit_flight(&migrated_socket, &mut conn).await;\n\n    let stats = conn.stats();\n    assert_eq!(stats.path_challenge_rx_count, 0);\n\n    process_flight(&migrated_socket, client_addr, &mut conn).await;\n\n    let stats = conn.stats();\n    assert_eq!(stats.path_challenge_rx_count, 1);\n\n    // Client responds to PATH_CHALLENGE.\n    emit_flight(&migrated_socket, &mut conn).await;\n\n    // Client receives response for the second request.\n    process_flight(&migrated_socket, client_addr, &mut conn).await;\n\n    assert_eq!(process_h3_events(&mut h3_conn, &mut conn), (true, true));\n}\n\nasync fn emit_flight(\n    socket: &tokio::net::UdpSocket, conn: &mut quiche::Connection,\n) {\n    let flight = match quiche::test_utils::emit_flight(conn) {\n        Ok(v) => v,\n\n        Err(quiche::Error::Done) => return,\n\n        Err(e) => panic!(\"failed to emit flight: {e:?}\"),\n    };\n\n    for p in flight {\n        // We avoid using the `from` field here on purpose, as in case of\n        // passive migration the client might be unaware that their address\n        // changed.\n        socket.send_to(&p.0, p.1.to).await.unwrap();\n    }\n}\n\nasync fn process_flight(\n    socket: &tokio::net::UdpSocket, client_addr: std::net::SocketAddr,\n    conn: &mut quiche::Connection,\n) {\n    let mut buf = [0; 65535];\n\n    socket.readable().await.unwrap();\n\n    loop {\n        let (len, from) = match socket.try_recv_from(&mut buf) {\n            Ok(v) => v,\n            Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => break,\n            Err(e) => panic!(\"failed to receive packets: {e:?}\"),\n        };\n\n        // We use an explicit `client_addr` here rather than the socket's\n        // address to simulate cases where the client is not aware of its own\n        // address changing during passive migration.\n        let recv_info = quiche::RecvInfo {\n            to: client_addr,\n            from,\n        };\n\n        // Process potentially coalesced packets.\n        let _ = conn.recv(&mut buf[..len], recv_info).unwrap();\n    }\n}\n\nfn process_h3_events(\n    h3_conn: &mut quiche::h3::Connection, conn: &mut quiche::Connection,\n) -> (bool, bool) {\n    let mut buf = [0; 65535];\n\n    let mut got_headers = false;\n\n    loop {\n        match h3_conn.poll(conn) {\n            Ok((_, quiche::h3::Event::Headers { .. })) => got_headers = true,\n\n            Ok((stream_id, quiche::h3::Event::Data)) => {\n                // Drain stream and drop the data.\n                while h3_conn.recv_body(conn, stream_id, &mut buf).is_ok() {}\n            },\n\n            Ok((_, quiche::h3::Event::Finished)) => {\n                // Request is complete, return.\n                return (got_headers, true);\n            },\n\n            _ => {},\n        }\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/tests/integration_tests/mod.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::fixtures::*;\nuse h3i_fixtures::received_status_code_on_stream;\n\nuse foundations::telemetry::with_test_telemetry;\nuse foundations::telemetry::TestTelemetryContext;\nuse futures::StreamExt;\nuse futures_util::future::try_join_all;\nuse std::time::Duration;\nuse tokio::time::timeout;\nuse tokio_quiche::listen;\nuse tokio_quiche::metrics::DefaultMetrics;\nuse tokio_quiche::settings::Hooks;\nuse tokio_quiche::settings::TlsCertificatePaths;\nuse tokio_quiche::ConnectionParams;\nuse tokio_quiche::InitialQuicConnection;\n\npub mod async_callbacks;\npub mod connection_close;\npub mod headers;\npub mod migration;\npub mod stream_limit;\npub mod timeouts;\npub mod zero_rtt;\n\n#[tokio::test]\nasync fn echo() {\n    const CONN_COUNT: usize = 5;\n\n    let req_count = |conn_num| conn_num * 100;\n    let (url, hook, _) = start_server();\n    let mut reqs = vec![];\n\n    for i in 1..=CONN_COUNT {\n        let url = format!(\"{url}/{i}\");\n\n        reqs.push(request(url, req_count(i) as u64))\n    }\n\n    let res = try_join_all(reqs).await.unwrap();\n    let res_map = map_responses(res);\n\n    assert_eq!(res_map.len(), CONN_COUNT);\n\n    for i in 1..=CONN_COUNT {\n        let resps = res_map.get(&i).unwrap();\n\n        assert_eq!(resps.len(), req_count(i));\n    }\n\n    assert!(hook.was_called());\n}\n\n#[tokio::test]\nasync fn e2e() {\n    let (url, hook, _) = start_server();\n    let url = format!(\"{url}/1\");\n\n    let res = request(url, 1).await.unwrap();\n    let res_map = map_responses(vec![res]);\n\n    assert_eq!(res_map.len(), 1);\n\n    let resps = res_map.get(&1).unwrap();\n    assert_eq!(resps.len(), 1);\n    assert!(hook.was_called());\n}\n\n#[tokio::test]\nasync fn e2e_client_ip_validation_disabled() {\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.max_recv_udp_payload_size = 1400;\n    quic_settings.max_send_udp_payload_size = 1400;\n    quic_settings.max_idle_timeout = Some(Duration::from_secs(5));\n    quic_settings.disable_client_ip_validation = true;\n\n    let hook = TestConnectionHook::new();\n\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings::default(),\n        hook.clone(),\n        handle_connection,\n    );\n    let url = format!(\"{url}/1\");\n    let reqs = vec![request(url, 1)];\n\n    let res = try_join_all(reqs).await.unwrap();\n    let res_map = map_responses(res);\n\n    assert_eq!(res_map.len(), 1);\n\n    let resps = res_map.get(&1).unwrap();\n    assert_eq!(resps.len(), 1);\n    assert!(hook.was_called());\n}\n\n#[with_test_telemetry(tokio::test)]\nasync fn quiche_logs_forwarded_server_side(cx: TestTelemetryContext) {\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.capture_quiche_logs = true;\n\n    let hook = TestConnectionHook::new();\n\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings::default(),\n        hook,\n        handle_connection,\n    );\n    let url = format!(\"{url}/1\");\n    let reqs = vec![request(url, 1)];\n\n    let res = try_join_all(reqs).await.unwrap();\n    let res_map = map_responses(res);\n\n    assert_eq!(res_map.len(), 1);\n\n    // Unfortunately, the Foundations `fields` struct is empty for some reason.\n    // This is a bit of a hacky test, but it checks for a string that should\n    // come from Quiche's Trace logs\n    assert!(cx.log_records().iter().any(|record| (record\n        .message\n        .contains(\"rx pkt\") ||\n        record.message.contains(\"tx pkt\")) &&\n        record.level.as_str() == \"TRACE\"));\n}\n\n#[tokio::test]\nasync fn test_ioworker_state_machine_pause() {\n    let socket = std::net::UdpSocket::bind(\"127.0.0.1:0\").unwrap();\n    let url = format!(\"http://127.0.0.1:{}\", socket.local_addr().unwrap().port());\n\n    let tls_cert_settings = TlsCertificatePaths {\n        cert: TEST_CERT_FILE,\n        private_key: TEST_KEY_FILE,\n        kind: tokio_quiche::settings::CertificateKind::X509,\n    };\n\n    let hooks = Hooks {\n        connection_hook: Some(TestConnectionHook::new()),\n    };\n\n    let params = ConnectionParams::new_server(\n        QuicSettings::default(),\n        tls_cert_settings,\n        hooks,\n    );\n    let mut stream = listen(vec![socket], params, DefaultMetrics)\n        .unwrap()\n        .remove(0);\n\n    tokio::spawn(async move {\n        loop {\n            let (h3_driver, h3_controller) =\n                ServerH3Driver::new(Http3Settings::default());\n            let conn = stream.next().await.unwrap().unwrap();\n\n            let (quic_connection, worker) =\n                conn.handshake(h3_driver).await.expect(\"handshake failed\");\n\n            InitialQuicConnection::resume(worker);\n\n            let h3_over_quic =\n                ServerH3Connection::new(quic_connection, h3_controller);\n            tokio::spawn(async move {\n                handle_connection(h3_over_quic).await;\n            });\n        }\n    });\n\n    let url = format!(\"{url}/1\");\n    let summary = timeout(Duration::from_secs(2), h3i_fixtures::request(&url, 1))\n        .await\n        .expect(\"request timed out\")\n        .expect(\"request failed\");\n\n    assert!(received_status_code_on_stream(&summary, 0, 200));\n}\n\n#[tokio::test]\n#[cfg(target_os = \"linux\")]\nasync fn test_so_mark_receive_data() {\n    let (url, _, mut audit_stats_rx) = start_server();\n\n    let url = format!(\"{url}/1\");\n    let summary = timeout(Duration::from_secs(2), h3i_fixtures::request(&url, 1))\n        .await\n        .expect(\"request timed out\")\n        .expect(\"request failed\");\n\n    assert!(received_status_code_on_stream(&summary, 0, 200));\n\n    let audit_stats = audit_stats_rx.recv().await.expect(\"should receive stats\");\n    let so_mark_data = audit_stats.initial_so_mark_data();\n    // We don't actually set SO_MARK anywhere, so we just want to ensure that the\n    // data is `Some`, indicating that we at least received the cmsg from the\n    // socket.\n    assert_eq!(so_mark_data.unwrap(), &[0, 0, 0, 0]);\n}\n"
  },
  {
    "path": "tokio-quiche/tests/integration_tests/stream_limit.rs",
    "content": "// Copyright (C) 2026, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::sync::Arc;\nuse std::sync::Mutex;\n\nuse crate::fixtures::*;\nuse h3i_fixtures::default_headers;\nuse h3i_fixtures::h3i_config;\nuse h3i_fixtures::received_status_code_on_stream;\nuse h3i_fixtures::summarize_connection;\nuse tokio::sync::oneshot;\nuse tokio_quiche::quic::QuicConnectionStats;\n\nuse h3i::actions::h3::send_headers_frame;\nuse h3i::actions::h3::send_headers_frame_with_expected_result;\nuse h3i::actions::h3::Action;\nuse h3i::actions::h3::ExpectedStreamSendResult;\nuse h3i::actions::h3::RequiredStreamsQuota;\nuse h3i::actions::h3::StreamEvent;\nuse h3i::actions::h3::StreamEventType;\nuse h3i::actions::h3::WaitType;\n\n/// The server advertises a limit of 2 concurrent bidirectional streams.\n/// The client opens those 2 streams successfully, then attempts a third\n/// which is expected to return Err(StreamLimit). Verify that the server's\n/// `streams_blocked_bidi_recv_count` stat is incremented after processing\n/// the STREAMS_BLOCKED frame from the client.\n#[tokio::test]\nasync fn test_bidi_stream_limit_reached() -> QuicResult<()> {\n    const MAX_STREAMS: u64 = 2;\n\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.initial_max_streams_bidi = MAX_STREAMS;\n\n    let hook = TestConnectionHook::new();\n    let (server_stats_tx, server_stats_rx) =\n        oneshot::channel::<Arc<Mutex<QuicConnectionStats>>>();\n    let server_stats_tx = Arc::new(Mutex::new(Some(server_stats_tx)));\n    let (url, _audit_stats_rx) = start_server_with_settings(\n        quic_settings,\n        Http3Settings::default(),\n        hook,\n        move |connection: ServerH3Connection| {\n            let stats_handle = Arc::clone(connection.stats());\n            let tx = Arc::clone(&server_stats_tx);\n            async move {\n                handle_connection(connection).await;\n                if let Some(tx) = tx.lock().unwrap().take() {\n                    let _ = tx.send(stats_handle);\n                }\n            }\n        },\n    );\n\n    let h3i = h3i_config(&url);\n\n    let mut actions = vec![];\n\n    // Open the two streams the server allows and wait for their responses.\n    for i in 0..MAX_STREAMS {\n        let stream_id = i * 4;\n        actions.push(send_headers_frame(stream_id, true, default_headers()));\n    }\n\n    // Attempt to open a third stream. The server only allows MAX_STREAMS\n    // concurrent bidirectional streams, so stream_send must return\n    // Err(StreamLimit). The action logs an error if it does not.\n    let blocked_stream_id = MAX_STREAMS * 4;\n    actions.push(send_headers_frame_with_expected_result(\n        blocked_stream_id,\n        true,\n        default_headers(),\n        ExpectedStreamSendResult::Error(quiche::Error::StreamLimit),\n    ));\n    actions.push(Action::FlushPackets);\n    // Wait until the server has processed the responses for streams 0 and 4,\n    // collected both streams (requiring ACKs from the client), and sent a\n    // MAX_STREAMS_BIDI frame that the client has received and applied.\n    // peer_streams_left_bidi >= 1 is only true after all of that has happened.\n    actions.push(Action::Wait {\n        wait_type: WaitType::CanOpenNumStreams(RequiredStreamsQuota {\n            num: 1,\n            bidi: true,\n        }),\n    });\n    // Attempt to create the stream that previously blocked. It should succeed\n    // since the server sent a MAX_STREAMS update after streams 0 and 4 closed.\n    actions.push(send_headers_frame(\n        blocked_stream_id,\n        true,\n        default_headers(),\n    ));\n    actions.push(Action::FlushPackets);\n    // Wait for all streams to finish.\n    for i in 0..(MAX_STREAMS + 1) {\n        let stream_id = i * 4;\n        actions.push(Action::Wait {\n            wait_type: WaitType::StreamEvent(StreamEvent {\n                stream_id,\n                event_type: StreamEventType::Finished,\n            }),\n        });\n    }\n    actions.push(Action::ConnectionClose {\n        error: quiche::ConnectionError {\n            is_app: true,\n            error_code: quiche::WireErrorCode::NoError as _,\n            reason: Vec::new(),\n        },\n    });\n\n    let summary = summarize_connection(h3i, actions).await;\n\n    // All requests should have returned 200 responses, including the retried\n    // blocked_stream_id (stream 8 succeeds on retry after the server sends a\n    // MAX_STREAMS update once streams 0 and 4 complete).\n    for i in 0..(MAX_STREAMS + 1) {\n        assert!(\n            received_status_code_on_stream(&summary, i * 4, 200),\n            \"stream {} should have received a 200 response\",\n            i * 4,\n        );\n    }\n\n    // The client sent STREAMS_BLOCKED because we enabled\n    // send_streams_blocked. Retrieve the server-side quiche::Stats and verify\n    // that the server received exactly one STREAMS_BLOCKED (bidi) frame.\n    let server_stats_handle = server_stats_rx\n        .await\n        .expect(\"server should have sent its stats handle\");\n    let server_stats = server_stats_handle.lock().unwrap();\n\n    assert_eq!(\n        server_stats.stats.streams_blocked_bidi_recv_count, 1,\n        \"server should have received exactly one STREAMS_BLOCKED (bidi) frame\",\n    );\n\n    Ok(())\n}\n"
  },
  {
    "path": "tokio-quiche/tests/integration_tests/timeouts.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse std::sync::atomic::AtomicBool;\nuse std::sync::atomic::AtomicUsize;\nuse std::sync::atomic::Ordering;\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse boring::ssl::BoxSelectCertFinish;\nuse boring::ssl::ClientHello;\nuse boring::ssl::SslContextBuilder;\nuse boring::ssl::SslFiletype;\nuse boring::ssl::SslMethod;\nuse h3i::actions::h3::send_headers_frame;\nuse h3i::actions::h3::Action;\nuse h3i::actions::h3::WaitType;\nuse h3i::quiche::ConnectionError;\nuse h3i::quiche::{\n    self,\n};\nuse tokio::net::UdpSocket;\nuse tokio::time::timeout;\nuse tokio_quiche::http3::driver::H3ConnectionError;\nuse tokio_quiche::quic::ConnectionHook;\nuse tokio_quiche::settings::TlsCertificatePaths;\nuse url::Url;\n\nuse crate::fixtures::h3i_fixtures::*;\nuse crate::fixtures::*;\n\n// TODO(erittenhouse): figure out a way to avoid all of this duplication\n#[tokio::test]\nasync fn test_handshake_duration_ioworker() {\n    use h3i::client::ClientError;\n\n    const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(1);\n    struct TestAsyncCallbackConnectionHook {\n        was_called: Arc<AtomicBool>,\n    }\n\n    impl ConnectionHook for TestAsyncCallbackConnectionHook {\n        fn create_custom_ssl_context_builder(\n            &self, _settings: TlsCertificatePaths<'_>,\n        ) -> Option<SslContextBuilder> {\n            let mut ssl_ctx_builder =\n                SslContextBuilder::new(SslMethod::tls()).ok()?;\n            let cloned_bool = Arc::clone(&self.was_called);\n\n            ssl_ctx_builder.set_async_select_certificate_callback(move |_| {\n                cloned_bool.store(true, Ordering::SeqCst);\n\n                Ok(Box::pin(async {\n                    // Pause during the handshake. Give some extra time to\n                    // (hopefully) avoid flakiness.\n                    tokio::time::sleep(HANDSHAKE_TIMEOUT.mul_f32(1.5)).await;\n                    Ok(Box::new(|_: ClientHello<'_>| Ok(()))\n                        as BoxSelectCertFinish)\n                }))\n            });\n\n            ssl_ctx_builder\n                .set_private_key_file(TEST_KEY_FILE, SslFiletype::PEM)\n                .unwrap();\n\n            ssl_ctx_builder\n                .set_certificate_chain_file(TEST_CERT_FILE)\n                .unwrap();\n\n            Some(ssl_ctx_builder)\n        }\n    }\n\n    let hook = Arc::new(TestAsyncCallbackConnectionHook {\n        was_called: Arc::new(AtomicBool::new(false)),\n    });\n\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.max_idle_timeout = Some(Duration::from_secs(5));\n    quic_settings.handshake_timeout = Some(HANDSHAKE_TIMEOUT);\n\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings {\n            post_accept_timeout: Some(HANDSHAKE_TIMEOUT),\n            ..Default::default()\n        },\n        hook.clone(),\n        handle_connection,\n    );\n\n    // TODO: migrate to h3i client to assert a CONNECTION_CLOSE was received. This\n    // will have to be the sync version so as to isolate the tokio-quiche IO\n    // loop.\n    //\n    // Unfortunately we can't PCAP this test since encryption keys don't seem to\n    // get dumped.\n    //\n    // build() spawns the InboundPacketRouter and sends the Initial, which will\n    // kick the handshake off on the server-side. If all goes well, the server\n    // will close the connection and the router will time the connection out.\n    let url = format!(\"{url}/1\");\n    let client_res = h3i_fixtures::request(&url, 1).await;\n\n    assert!(matches!(client_res, Err(ClientError::HandshakeFail)));\n    assert!(hook.was_called.load(Ordering::SeqCst));\n}\n\n#[tokio::test]\nasync fn test_handshake_timeout_with_one_client_flight() {\n    let hook = TestConnectionHook::new();\n\n    const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(1);\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.handshake_timeout = Some(HANDSHAKE_TIMEOUT);\n\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings::default(),\n        hook.clone(),\n        handle_connection,\n    );\n\n    let peer_addr = Url::parse(&url)\n        .expect(\"fixture should return a valid URL\")\n        .socket_addrs(|| None)\n        .expect(\"URL should resolve to a SocketAddr\")[0];\n\n    let mut quiche_config = quiche::Config::new(1).unwrap();\n    quiche_config.verify_peer(false);\n    quiche_config.set_application_protos(&[b\"h3\"]).unwrap();\n    quiche_config.set_max_recv_udp_payload_size(1350);\n    quiche_config.set_max_send_udp_payload_size(1350);\n\n    let socket = UdpSocket::bind(\"0.0.0.0:0\").await.unwrap();\n    socket.connect(peer_addr).await.unwrap();\n\n    let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n    boring::rand::rand_bytes(&mut scid).unwrap();\n    let scid = quiche::ConnectionId::from_ref(&scid);\n\n    let local_addr = socket.local_addr().unwrap();\n    let mut quiche_conn = quiche::connect(\n        Some(\"test.com\"),\n        &scid,\n        local_addr,\n        peer_addr,\n        &mut quiche_config,\n    )\n    .unwrap();\n\n    // Send first Initial packet\n    let mut out = [0; 65535];\n    let (write, _) = quiche_conn.send(&mut out).expect(\"initial send failed\");\n    socket.send(&out[..write]).await.unwrap();\n\n    // Receive Retry packet\n    let (len, from) = socket.recv_from(&mut out).await.unwrap();\n    let recv_info = quiche::RecvInfo {\n        from,\n        to: socket.local_addr().unwrap(),\n    };\n    let _ = quiche_conn.recv(&mut out[..len], recv_info);\n\n    // Send second Initial packet, which will spawn the TQ Handshake IOW\n    let (written, _) = quiche_conn.send(&mut out).unwrap();\n    socket.send(&out[..written]).await.unwrap();\n\n    let err = timeout(\n        // Give a small buffer for the handshake timeout to fire\n        HANDSHAKE_TIMEOUT.mul_f32(1.5),\n        async move {\n            loop {\n                let (len, from) = socket.recv_from(&mut out).await.unwrap();\n                let recv_info = quiche::RecvInfo {\n                    from,\n                    to: socket.local_addr().unwrap(),\n                };\n                let _ = quiche_conn.recv(&mut out[..len], recv_info);\n\n                if let Some(e) = quiche_conn.peer_error().cloned() {\n                    return e;\n                }\n            }\n        },\n    )\n    .await;\n\n    assert_eq!(err.unwrap(), ConnectionError {\n        is_app: false,\n        error_code: quiche::WireErrorCode::ApplicationError as u64,\n        reason: vec![]\n    });\n}\n\n#[tokio::test]\nasync fn test_post_accept_timeout() {\n    const POST_ACCEPT_TIMEOUT: Duration = Duration::from_secs(1);\n\n    // Absence of async callbacks should render the handshake portion of the\n    // post-accept timeout (see test_handshake_duration_ioworker) calculation\n    // negligble.\n    let hook = TestConnectionHook::new();\n\n    // Track if we've seen any requests over this connection.\n    let request_counter = Arc::new(AtomicUsize::new(0));\n    let clone = Arc::clone(&request_counter);\n\n    let mut quic_settings = QuicSettings::default();\n    // Since this is longer than the H3Driver's post-accept timeout, if\n    // the connection fails we know that it's from the\n    // post-accept timeout rather than Quiche's idle timeout.\n    quic_settings.max_idle_timeout = Some(Duration::from_secs(5));\n\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings {\n            post_accept_timeout: Some(POST_ACCEPT_TIMEOUT),\n            ..Default::default()\n        },\n        hook,\n        move |mut h3_conn| {\n            let counter = Arc::clone(&clone);\n            async move {\n                let err =\n                    serve_connection_details(&mut h3_conn.h3_controller, counter)\n                        .await\n                        .expect_err(\"serve_connection didn't return an error\");\n                let h3_err: &H3ConnectionError = err\n                    .downcast_ref()\n                    .expect(\"Didn't receive an H3ConnectionError error\");\n                assert_eq!(h3_err, &H3ConnectionError::PostAcceptTimeout);\n            }\n        },\n    );\n\n    let h3i_config = h3i_config(&url);\n    let actions = vec![Action::Wait {\n        wait_type: WaitType::WaitDuration(POST_ACCEPT_TIMEOUT.mul_f32(1.5)),\n    }];\n\n    let summary = summarize_connection(h3i_config, actions).await;\n\n    // Since the server's idle timeout is longer than the H3Driver's post-accept\n    // timeout, the connection should have closed without any requests being\n    // received.\n    assert_eq!(request_counter.load(Ordering::SeqCst), 0);\n\n    let err = summary\n        .conn_close_details\n        .peer_error()\n        .expect(\"no error received\");\n    assert!(err.is_app);\n    assert_eq!(err.error_code, quiche::h3::WireErrorCode::NoError as u64);\n}\n\n#[tokio::test]\nasync fn test_post_accept_timeout_is_reset() {\n    const POST_ACCEPT_TIMEOUT: Duration = Duration::from_secs(1);\n\n    // Absence of async callbacks should render the handshake portion of the\n    // post-accept timeout (see test_handshake_duration_ioworker) calculation\n    // negligble.\n    let hook = TestConnectionHook::new();\n\n    let request_counter = Arc::new(AtomicUsize::new(0));\n    let clone = Arc::clone(&request_counter);\n\n    let mut quic_settings = QuicSettings::default();\n    // Since this is longer than the H3Driver's post-accept timeout, if\n    // the connection fails we know that it's from the\n    // post-accept timeout rather than Quiche's idle timeout.\n    quic_settings.max_idle_timeout = Some(Duration::from_secs(5));\n\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings {\n            post_accept_timeout: Some(POST_ACCEPT_TIMEOUT),\n            ..Default::default()\n        },\n        hook,\n        move |mut h3_conn| {\n            let counter = Arc::clone(&clone);\n            async move {\n                serve_connection_details(&mut h3_conn.h3_controller, counter)\n                    .await\n                    .expect(\"serve_connection failed\");\n            }\n        },\n    );\n\n    let h3i_config = h3i_config(&url);\n    let actions = vec![\n        // Just to ensure that normal waits don't accidentally trigger the\n        // post-accept timeout\n        Action::Wait {\n            wait_type: WaitType::WaitDuration(POST_ACCEPT_TIMEOUT.mul_f32(0.50)),\n        },\n        send_headers_frame(0, true, default_headers()),\n        Action::FlushPackets,\n        // Post-accept timeout should be cancelled, so we can wait an arbitrary\n        // period (less than the idle timeout of course) and the\n        // post-accept timeout shouldn't fire\n        Action::Wait {\n            wait_type: WaitType::WaitDuration(POST_ACCEPT_TIMEOUT.mul_f32(1.1)),\n        },\n        send_headers_frame(4, true, default_headers()),\n        Action::FlushPackets,\n    ];\n\n    let summary = summarize_connection(h3i_config, actions).await;\n    assert!(summary.conn_close_details.no_err());\n\n    for i in [0, 4] {\n        assert!(received_status_code_on_stream(&summary, i, 200));\n    }\n\n    assert_eq!(request_counter.load(Ordering::SeqCst), 2);\n}\n"
  },
  {
    "path": "tokio-quiche/tests/integration_tests/zero_rtt.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse crate::integration_tests::h3i_fixtures;\nuse crate::integration_tests::start_server_with_settings;\nuse crate::integration_tests::Http3Settings;\nuse crate::integration_tests::QuicSettings;\nuse crate::integration_tests::TestConnectionHook;\nuse futures::SinkExt;\nuse h3i;\nuse h3i::actions::h3::send_headers_frame;\nuse h3i::actions::h3::Action;\nuse h3i::actions::h3::StreamEvent;\nuse h3i::actions::h3::StreamEventType;\nuse h3i::actions::h3::WaitType;\nuse h3i::client::connection_summary::ConnectionSummary;\nuse quiche::h3::NameValue;\nuse quiche::ConnectionError;\nuse quiche::WireErrorCode;\nuse std::future::Future;\nuse std::sync::Arc;\nuse std::sync::Mutex;\nuse tokio_quiche::buf_factory::BufFactory;\nuse tokio_quiche::http3::driver::H3Event;\nuse tokio_quiche::http3::driver::IncomingH3Headers;\nuse tokio_quiche::http3::driver::OutboundFrame;\nuse tokio_quiche::http3::driver::ServerH3Event;\nuse tokio_quiche::quiche::h3::Header;\nuse tokio_quiche::ServerH3Connection;\n\n#[derive(Debug, Default, Clone)]\nstruct TestContext {\n    did_recv_early_data_request: bool,\n    requests_handled_count: usize,\n    hosts_seen: Vec<String>,\n}\n\n#[tokio::test]\nasync fn handle_0_rtt_request() {\n    let context = Arc::new(Mutex::new(TestContext::default()));\n    let early_stream_id = 0;\n    let stream_id = 4;\n\n    let context_clone = context.clone();\n    let mut quic_settings = QuicSettings::default();\n    quic_settings.enable_early_data = true;\n    let (url, _) = start_server_with_settings(\n        quic_settings,\n        Http3Settings::default(),\n        TestConnectionHook::new(),\n        move |h3_conn| helper_server_handler(h3_conn, &context_clone),\n    );\n\n    let nst_data = {\n        let summary = {\n            let h3i_config = h3i_fixtures::h3i_config(&url);\n            helper_connect_with_early_data(\n                h3i_config,\n                None,\n                helper_frame_actions(stream_id),\n            )\n            .await\n        };\n\n        {\n            let context = context.lock().unwrap();\n            assert_eq!(context.hosts_seen.len(), 1);\n            assert!(context.hosts_seen.contains(&\"test.com\".to_string()));\n            assert_eq!(context.requests_handled_count, 1);\n            assert!(!context.did_recv_early_data_request);\n        }\n\n        // Get Session data from this connection to resume the 0-RTT connection.\n        summary.conn_close_details.session.unwrap()\n    };\n\n    helper_reset_test(&context);\n\n    {\n        let early_frame_actions = vec![\n            send_headers_frame(\n                early_stream_id,\n                false,\n                h3i_fixtures::default_headers_with_authority(\"early.test.com\"),\n            ),\n            Action::FlushPackets,\n        ];\n\n        let mut h3i_config = h3i_fixtures::h3i_config(&url);\n        // Provide session to the client to enable resumption.\n        h3i_config.session = Some(nst_data);\n        h3i_config.enable_early_data = true;\n        let _summary = helper_connect_with_early_data(\n            h3i_config,\n            Some(early_frame_actions),\n            helper_frame_actions(stream_id),\n        )\n        .await;\n\n        {\n            let context = context.lock().unwrap();\n            assert_eq!(context.hosts_seen.len(), 2);\n            assert_eq!(context.hosts_seen, vec![\"early.test.com\", \"test.com\"]);\n            assert_eq!(context.requests_handled_count, 2);\n            assert!(context.did_recv_early_data_request);\n        }\n    }\n}\n\npub async fn helper_connect_with_early_data(\n    h3i_config: h3i::config::Config, early_actions: Option<Vec<Action>>,\n    actions: Vec<Action>,\n) -> ConnectionSummary {\n    tokio::task::spawn_blocking(move || {\n        h3i::client::sync_client::connect_with_early_data(\n            h3i_config,\n            early_actions,\n            actions,\n            None,\n        )\n        .unwrap()\n    })\n    .await\n    .unwrap()\n}\n\nfn helper_reset_test(context: &Arc<Mutex<TestContext>>) {\n    let mut context = context.lock().unwrap();\n    *context = TestContext::default();\n}\n\nfn helper_frame_actions(stream_id: u64) -> Vec<Action> {\n    vec![\n        send_headers_frame(stream_id, true, h3i_fixtures::default_headers()),\n        Action::FlushPackets,\n        Action::Wait {\n            wait_type: WaitType::StreamEvent(StreamEvent {\n                stream_id,\n                event_type: StreamEventType::Finished,\n            }),\n        },\n        Action::ConnectionClose {\n            error: ConnectionError {\n                is_app: true,\n                error_code: WireErrorCode::NoError as _,\n                reason: Vec::new(),\n            },\n        },\n    ]\n}\n\nfn helper_server_handler(\n    mut h3_conn: ServerH3Connection, context: &Arc<Mutex<TestContext>>,\n) -> impl Future<Output = ()> {\n    let context = context.clone();\n\n    async move {\n        let event_rx = h3_conn.h3_controller.event_receiver_mut();\n\n        while let Some(event) = event_rx.recv().await {\n            match event {\n                ServerH3Event::Core(event) => {\n                    if let H3Event::ConnectionShutdown(_) = event {\n                        break;\n                    }\n                },\n\n                ServerH3Event::Headers {\n                    incoming_headers,\n                    is_in_early_data,\n                    ..\n                } => {\n                    let IncomingH3Headers {\n                        mut send, headers, ..\n                    } = incoming_headers;\n\n                    let authority = headers\n                        .iter()\n                        .find(|v| v.name().eq(\":authority\".as_bytes()))\n                        .unwrap();\n\n                    {\n                        let mut context = context.lock().unwrap();\n                        context.requests_handled_count += 1;\n                        context.did_recv_early_data_request |= *is_in_early_data;\n                        let host = std::str::from_utf8(authority.value())\n                            .unwrap()\n                            .to_string();\n                        context.hosts_seen.push(host);\n                    }\n\n                    // Send headers.\n                    send.send(OutboundFrame::Headers(\n                        vec![Header::new(b\":status\", b\"200\")],\n                        None,\n                    ))\n                    .await\n                    .unwrap();\n\n                    send.send(OutboundFrame::Body(\n                        BufFactory::get_empty_buf(),\n                        true,\n                    ))\n                    .await\n                    .unwrap();\n                },\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tokio-quiche/tests/main.rs",
    "content": "// Copyright (C) 2025, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\npub mod fixtures;\npub mod integration_tests;\n"
  },
  {
    "path": "tools/android/build_android_ndk19.sh",
    "content": "#!/bin/sh\n#\n# Build quiche for Android NDK 19 or higher\n#\n# ANDROID_NDK_HOME : android ndk location\n# TOOLCHAIN_DIR : where create a toolchain (optional)\n#\nset -eu\n\n# Change this value if you need a different API level\n# 21 is the minimum API tested\nAPI_LEVEL=21\n\nif [ ! -d \"${ANDROID_NDK_HOME-}\" ]; then\n    ANDROID_NDK_HOME=/usr/local/share/android-ndk\nfi\n\nif [ ! -d \"${TOOLCHAIN_DIR-}\" ]; then\n    TOOLCHAIN_DIR=$(pwd)\nfi\n\necho \"> building quiche for android API $API_LEVEL...\"\n\nfor arch in arm64-v8a armeabi-v7a x86_64 x86\ndo\n    echo \"> building $arch...\"\n\n    cargo ndk -t $arch -p $API_LEVEL -- build --features ffi $*\ndone\n"
  },
  {
    "path": "tools/gen_fuzz_seeds.sh",
    "content": "#!/bin/bash\n\nset -e\n\ncleanup() {\n    echo \"Cleaning up...\"\n    rm -r $CLIENT_DIR $SERVER_DIR\n    kill %1\n}\n\ntrap cleanup EXIT\n\nCLIENT_DIR=$(mktemp -d)\nSERVER_DIR=$(mktemp -d)\n\n# Build apps first.\ncargo build --features fuzzing -p quiche_apps\n\n# Run server in the background.\ntarget/debug/quiche-server --cert fuzz/cert.crt --key fuzz/cert.key --dump-packets $SERVER_DIR &\n\n# Wait for server to be ready.\nsleep 1\n\n# Run client.\nRUST_LOG=trace target/debug/quiche-client --no-verify https://127.0.0.1:4433 --dump-packets $CLIENT_DIR\n\n# Combine client-received packets into client's seed.\ncat $CLIENT_DIR/*.pkt > fuzz/corpus/packet_recv_client/seed\n\n# Combine server-received packets into server's seed.\ncat $SERVER_DIR/*.pkt > fuzz/corpus/packet_recv_server/seed\n\n# Combine server-received packets with \"fuzz\" delimit into server's seed for multiple packets.\nls $SERVER_DIR/*.pkt | while read i; do cat $i; echo -n fuzz; done > fuzz/corpus/packets_recv_server/seed\n\n# Minimize fuzz corpora.\ncargo +nightly fuzz cmin -Oa packet_recv_client\ncargo +nightly fuzz cmin -Oa packet_recv_server\ncargo +nightly fuzz cmin -Oa packets_recv_server\n"
  },
  {
    "path": "tools/http3_test/Cargo.toml",
    "content": "[package]\nname = \"http3_test\"\nversion = \"0.2.0\"\nauthors = [\"Lucas Pardue <lucaspardue.24.7@gmail.com>\"]\nedition = \"2018\"\npublish = false\n\n[features]\ntest_resets = []\n\n[dependencies]\nenv_logger = \"0.10\"\nlog = \"0.4\"\nmio = { version = \"0.8\", features = [\"net\", \"os-poll\"] }\nquiche = { path = \"../../quiche\" }\nring = \"0.17\"\nserde_json = \"1.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nurl = \"1\"\n"
  },
  {
    "path": "tools/http3_test/README.md",
    "content": "This crate provides an API to build httpbin test requests and expected outcomes.\nThese can be used with the quiche HTTP/3 module to communicate with an httpbin\ntest server.\n\nBuilding\n--------\n\n```bash\n $ cargo build\n```\n\nRunning\n--------\nWe use cargo test to execute different httpbin tests. By default this points to\nhttps://cloudflare-quic.com/b\n\n```bash\n $ cargo test\n```\n\nEnvironment Variable Overrides\n------------------------------\n\nA set of environment variables allow tuning of test behaviour:\n\n* HTTPBIN_ENDPOINT - httpbin server URL, the authority is used in SNI.\n                     Default value is https://cloudflare-quic.com/b\n\n* HTTPBIN_HOST     - httpbin server IP address and port (<SERVER>:<PORT>).\n                     Overrides the effective target authority defined in\n                     HTTPBIN_ENDPOINT. Default value does not override.\n\n* VERIFY_PEER      - boolean value (\"true\" or \"false\") that controls if\n                     the test client attempts to validate the httpbin\n                     server certificate. Default value is true.\n\n* IDLE_TIMEOUT     - client's idle timeout in integer milliseconds.\n                     Default value is 60000 (60 seconds).\n\n* QUIC_VERSION     - wire protocol version used by the client as a hex value\n                     (e.g. \"0xbabababa\"). By default an invalid version is used\n                     to trigger version negotiation. If set to \"current\", the\n                     current supported version is used.\n\n* EXTRA_HEADERS    - additional request headers in JSON format.\n                     Currently used by `headers` test only.\n\n* EXPECT_REQ_HEADERS - expected request header/value pairs in JSON format.\n                       Currently used by `headers` test only.\n\n* EARLY_DATA       - boolean value (\"true\" or \"false\") that controls if\n                     the test client attempts to 0-rtt connection.\n                     Default value is false.\n\n* SESSION_FILE     - the file name used for storing QUIC session ticket.\n                     With EARLY_DATA, it can be used for the test client\n                     to attempt 0-rtt connection resumption.\n\nFor example, to test a non-default server, use the HTTPBIN_ENDPOINT environment\nvariable\n\n```bash\n $ HTTPBIN_ENDPOINT=https://<some_other_endpoint> cargo test\n```\n"
  },
  {
    "path": "tools/http3_test/src/lib.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n//! 🔧 HTTP/3 integration test utilities.\n//!\n//! This crate provides utilities to help integration tests against HTTP/3\n//! endpoints. Structures and methods can be combined with a [`quiche`]\n//! HTTP/3 client to run tests against a server. This client could be a\n//! binary or run as part of cargo test.\n//!\n//! ## Creating a test\n//!\n//! A test is an instance of [`Http3Test`], which consists of a set of\n//! [`Http3Req`] and a single [`Http3Assert`].\n//!\n//!\n//! Creating a single request:\n//!\n//! ```no_run\n//! let mut url = url::Url::parse(\"https://cloudflare-quic.com/b/get\").unwrap();\n//! let mut reqs = Vec::new();\n//!\n//! reqs.push(http3_test::Http3Req::new(b\"GET\", &url, None, None));\n//! ```\n//!\n//! Assertions are used to check the received response headers and body\n//! against expectations. Each test has a [`Http3Assert`] which\n//! can access the received data. For example, to check the response\n//! status code is a 200 we could write the function:\n//!\n//! ```no_run\n//! fn assert_status(reqs: &[http3_test::Http3Req]) {\n//!     let status = reqs[0]\n//!         .resp_hdrs\n//!         .iter()\n//!         .find(|&x| x.name() == \":status\")\n//!         .unwrap();\n//!     assert_eq!(status.value(), \"200\");\n//! }\n//! ```\n//!\n//! However, because checking response headers is so common, for convenience\n//! the expected headers can be provided during [`Http3Assert`] construction:\n//!\n//! ```no_run\n//! let mut url = url::Url::parse(\"https://cloudflare-quic.com/b/get\").unwrap();\n//! let mut reqs = Vec::new();\n//!\n//! let expect_hdrs = Some(vec![quiche::h3::Header::new(b\":status\", \"200\")]);\n//! reqs.push(http3_test::Http3Req::new(b\"GET\", &url, None, expect_hdrs));\n//! ```\n//!\n//! The [`assert_headers!`] macro can be used to validate the received headers,\n//! this means we can write a much simpler assertion:\n//!\n//! ```no_run\n//! fn assert_status(reqs: &[http3_test::Http3Req]) {\n//!     http3_test::assert_headers!(reqs[0]);\n//! }\n//! ```\n//!\n//! Whatever methods you choose to use, once the requests and assertions are\n//! made we can create the test:\n//!\n//! ```no_run\n//! let mut url = url::Url::parse(\"https://cloudflare-quic.com/b/get\").unwrap();\n//! let mut reqs = Vec::new();\n//!\n//! let expect_hdrs = Some(vec![quiche::h3::Header::new(b\":status\", \"200\")]);\n//! reqs.push(http3_test::Http3Req::new(b\"GET\", &url, None, expect_hdrs));\n//!\n//! // Using a closure...\n//! let assert =\n//!     |reqs: &[http3_test::Http3Req]| http3_test::assert_headers!(reqs[0]);\n//!\n//! let mut test = http3_test::Http3Test::new(url, reqs, assert, true);\n//! ```\n//!\n//! ## Sending test requests\n//!\n//! Testing a server requires a quiche connection and an HTTP/3 connection.\n//!\n//! Request are issued with the [`send_requests()`] method. The concurrency\n//! of requests within a single Http3Test is set in [`new()`]. If concurrency is\n//! disabled [`send_requests()`] will to send a single request and return.\n//! So call the method multiple times to issue more requests. Once all\n//! requests have been sent, further calls will return `quiche::h3:Error::Done`.\n//!\n//! Example:\n//! ```no_run\n//! # let mut url = url::Url::parse(\"https://cloudflare-quic.com/b/get\").unwrap();\n//! # let mut reqs = Vec::new();\n//! # let expect_hdrs = Some(vec![quiche::h3::Header::new(b\":status\", \"200\")]);\n//! # reqs.push(http3_test::Http3Req::new(b\"GET\", &url, None, expect_hdrs));\n//! # // Using a closure...\n//! # let assert = |reqs: &[http3_test::Http3Req]| {\n//! #   http3_test::assert_headers!(reqs[0]);\n//! # };\n//! let mut test = http3_test::Http3Test::new(url, reqs, assert, true);\n//!\n//! let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! let scid = [0xba; 16];\n//! let mut conn = quiche::connect(None, &scid, &mut config).unwrap();\n//! let h3_config = quiche::h3::Config::new()?;\n//! let mut http3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?;\n//!\n//! test.send_requests(&mut conn, &mut http3_conn).unwrap();\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! ## Handling responses\n//!\n//! Response data is used to validate test cases so it is important to\n//! store received data in the test object. This can be done with the\n//! [`add_response_headers()`] and [`add_response_body()`] methods. Note\n//! that the stream ID is used to correlate the response with the correct\n//! request.\n//!\n//! For example, when handling HTTP/3 connection events using `poll()`:\n//!\n//! ```no_run\n//! # let mut url = url::Url::parse(\"https://cloudflare-quic.com/b/get\").unwrap();\n//! # let mut reqs = Vec::new();\n//! # let expect_hdrs = Some(vec![quiche::h3::Header::new(b\":status\", \"200\")]);\n//! # reqs.push(http3_test::Http3Req::new(b\"GET\", &url, None, expect_hdrs));\n//! # // Using a closure...\n//! # let assert = |reqs: &[http3_test::Http3Req]| {\n//! #   http3_test::assert_headers!(reqs[0]);\n//! # };\n//! # let mut test = http3_test::Http3Test::new(url, reqs, assert, true);\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! # let scid = [0xba; 16];\n//! # let mut conn = quiche::connect(None, &scid, &mut config).unwrap();\n//! # let h3_config = quiche::h3::Config::new()?;\n//! # let mut http3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?;\n//! match http3_conn.poll(&mut conn) {\n//!     Ok((stream_id, quiche::h3::Event::Headers{list, more_frames})) => {\n//!         test.add_response_headers(stream_id, &list);\n//!     },\n//!\n//!     Ok((stream_id, quiche::h3::Event::Data)) => {\n//!         let mut buf = [0; 65535];\n//!         if let Ok(read) = http3_conn.recv_body(&mut conn, stream_id, &mut buf)\n//!         {\n//!             test.add_response_body(stream_id, &buf, read);\n//!         }\n//!     },\n//!\n//!     _ => ()\n//! }\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! ## Tests assertion\n//!\n//! The [`assert()`] method executes the provided test assertion using the\n//! entire set of [`Http3Req`]s. Calling this prematurely is likely to result\n//! in failure, so it is important to store response data and track the number\n//! of completed requests matches the total for a test.\n//!\n//! ```no_run\n//! # let mut url = url::Url::parse(\"https://cloudflare-quic.com/b/get\").unwrap();\n//! # let mut reqs = Vec::new();\n//! # let expect_hdrs = Some(vec![quiche::h3::Header::new(b\":status\", \"200\")]);\n//! # reqs.push(http3_test::Http3Req::new(b\"GET\", &url, None, expect_hdrs));\n//! # // Using a closure...\n//! # let assert = |reqs: &[http3_test::Http3Req]| {\n//! #   http3_test::assert_headers!(reqs[0]);\n//! # };\n//! # let mut test = http3_test::Http3Test::new(url, reqs, assert, true);\n//! # let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();\n//! # let scid = [0xba; 16];\n//! # let mut conn = quiche::connect(None, &scid, &mut config).unwrap();\n//! # let h3_config = quiche::h3::Config::new()?;\n//! # let mut http3_conn = quiche::h3::Connection::with_transport(&mut conn, &h3_config)?;\n//! let mut requests_complete = 0;\n//! let request_count = test.requests_count();\n//! match http3_conn.poll(&mut conn) {\n//!     Ok((_stream_id, quiche::h3::Event::Finished)) => {\n//!         requests_complete += 1;\n//!         if requests_complete == request_count {\n//!             test.assert()\n//!         }\n//!     },\n//!     _ => ()\n//! }\n//! # Ok::<(), quiche::h3::Error>(())\n//! ```\n//!\n//! [`quiche`]: https://github.com/cloudflare/quiche/\n//! [test]: struct.Http3Test.html\n//! [`Http3Test`]: struct.Http3Test.html\n//! [`Http3Assert`]: struct.Http3Assert.html\n//! [`Http3req`]: struct.Http3Req.html\n//! [`assert_headers!`]: macro.assert_headers.html\n//! [`new()`]: struct.Http3Test.html#method.new\n//! [`send_requests()`]: struct.Http3Test.html#method.send_requests\n//! [`requests_count()`]: struct.Http3Test.html#method.requests_count\n//! [`assert()`]: struct.Http3Test.html#method.assert\n//! [`add_response_headers()`]:\n//! struct.Http3Test.html#method.add_response_headers [`add_response_body()`]:\n//! struct.Http3Test.html#method.add_response_body\n\n#[macro_use]\nextern crate log;\n\nuse std::collections::HashMap;\n\nuse quiche::h3::Header;\nuse quiche::h3::NameValue;\n\npub const USER_AGENT: &[u8] = b\"quiche-http3-integration-client\";\n\n/// Stores the request, the expected response headers, and the actual response.\n///\n/// The assert_headers! macro is provided for convenience to validate the\n/// received headers match the expected headers.\n#[derive(Clone)]\npub struct Http3Req {\n    pub url: url::Url,\n    pub hdrs: Vec<Header>,\n    pub body: Option<Vec<u8>>,\n    pub expect_resp_hdrs: Option<Vec<Header>>,\n    pub resp_hdrs: Vec<Header>,\n    pub resp_body: Vec<u8>,\n    pub reset_stream_code: Option<u64>,\n}\n\nimpl Http3Req {\n    pub fn new(\n        method: &str, url: &url::Url, body: Option<Vec<u8>>,\n        expect_resp_hdrs: Option<Vec<Header>>,\n    ) -> Http3Req {\n        let mut path = String::from(url.path());\n        if let Some(query) = url.query() {\n            path.push('?');\n            path.push_str(query);\n        }\n\n        let mut hdrs = vec![\n            Header::new(b\":method\", method.as_bytes()),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        if let Some(body) = &body {\n            hdrs.push(Header::new(\n                b\"content-length\",\n                body.len().to_string().as_bytes(),\n            ));\n        }\n\n        Http3Req {\n            url: url.clone(),\n            hdrs,\n            body,\n            expect_resp_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        }\n    }\n\n    /// Add a new [`Header`] to the request. If the request already contains a\n    /// header with the new header's name, the existing header's value will\n    /// be replaced with that of the new one.\n    pub fn add_or_replace_header(\n        header_list: &mut Vec<Header>, new_header: Header,\n    ) {\n        if let Some(hdr_in_list) = find_header(header_list, &new_header) {\n            *hdr_in_list = new_header;\n\n            return;\n        }\n\n        header_list.push(new_header.clone());\n    }\n}\n\nfn find_header<'a>(\n    header_list: &'a mut [Header], header: &Header,\n) -> Option<&'a mut Header> {\n    header_list\n        .iter_mut()\n        .find(|curr| curr.name() == header.name())\n}\n\n/// Asserts that the Http3Req received response headers match the expected\n/// response headers.\n///\n/// Header values are compared with [`assert_eq!`] and this macro will panic\n/// similarly.\n///\n/// If an expected header is not present this macro will panic and print the\n/// missing header name.AsMut\n///\n/// [`assert_eq!`]: std/macro.assert.html\n#[macro_export]\nmacro_rules! assert_headers {\n    ($req:expr) => ({\n        if let Some(expect_hdrs) = &$req.expect_resp_hdrs {\n            for hdr in expect_hdrs {\n                match $req.resp_hdrs.iter().find(|&x| x.name() == hdr.name()) {\n                    Some(h) => assert_eq!(hdr.value(), h.value()),\n\n                    None =>\n                        panic!(\"assertion failed: expected response header field {} not present!\", std::str::from_utf8(hdr.name()).unwrap()),\n                }\n            }\n        }\n    });\n    ($req:expr,) => ({ $crate::assert_headers!($req)});\n    ($req:expr, $($arg:tt)+) => ({\n        if let Some(expect_hdrs) = &$req.expect_resp_hdrs {\n            for hdr in expect_hdrs {\n                match $req.resp_hdrs.iter().find(|&x| x.name() == hdr.name()) {\n                    Some(h) => { assert_eq!(hdr.value(), h.value(), $($arg)+);},\n\n                    None => {\n                        panic!(\"assertion failed: expected response header field {} not present! {}\", hdr.name(), $($arg)+);\n                    }\n                }\n            }\n        }\n    });\n}\n\n/// A helper function pointer type for assertions.\n///\n/// Each test assertion can check the set of Http3Req\n/// however they like.\npub type Http3Assert = fn(&[Http3Req]);\n\n#[derive(Debug, PartialEq, Eq)]\npub enum Http3TestError {\n    HandshakeFail,\n    HttpFail,\n    Other(String),\n}\n\npub struct ArbitraryStreamData {\n    pub stream_id: u64,\n    pub data: Vec<u8>,\n    pub fin: bool,\n}\n\n/// The main object for getting things done.\n///\n/// The factory method new() is used to set up a vector of Http3Req objects and\n/// map them to a test assertion function. The public functions are used to send\n/// requests and store response data. Internally we track some other state to\n/// make sure everything goes smoothly.\n///\n/// Many tests have similar inputs or assertions, so utility functions help\n/// cover many of the common cases like testing different status codes or\n/// checking that a response body is echoed back.\npub struct Http3Test {\n    endpoint: url::Url,\n    reqs: Vec<Http3Req>,\n    stream_data: Option<Vec<ArbitraryStreamData>>,\n    assert: Http3Assert,\n    issued_reqs: HashMap<u64, usize>,\n    concurrent: bool,\n    current_idx: usize,\n}\n\nimpl Http3Test {\n    pub fn new(\n        endpoint: url::Url, reqs: Vec<Http3Req>, assert: Http3Assert,\n        concurrent: bool,\n    ) -> Http3Test {\n        Http3Test {\n            endpoint,\n            reqs,\n            stream_data: None,\n            assert,\n            issued_reqs: HashMap::new(),\n            concurrent,\n            current_idx: 0,\n        }\n    }\n\n    pub fn with_stream_data(\n        endpoint: url::Url, reqs: Vec<Http3Req>,\n        stream_data: Vec<ArbitraryStreamData>, assert: Http3Assert,\n        concurrent: bool,\n    ) -> Http3Test {\n        Http3Test {\n            endpoint,\n            reqs,\n            stream_data: Some(stream_data),\n            assert,\n            issued_reqs: HashMap::new(),\n            concurrent,\n            current_idx: 0,\n        }\n    }\n\n    /// Returns the total number of requests in a test.\n    pub fn requests_count(&mut self) -> usize {\n        self.reqs.len()\n    }\n\n    pub fn endpoint(&self) -> url::Url {\n        self.endpoint.clone()\n    }\n\n    /// Send one or more requests based on test type and the concurrency\n    /// property. If any send fails, a quiche::h3::Error is returned.\n    pub fn send_requests(\n        &mut self, conn: &mut quiche::Connection,\n        h3_conn: &mut quiche::h3::Connection,\n    ) -> quiche::h3::Result<()> {\n        if let Some(stream_data) = &self.stream_data {\n            for d in stream_data {\n                match conn.stream_send(d.stream_id, &d.data, d.fin) {\n                    Ok(_) => (),\n\n                    Err(e) => {\n                        error!(\n                            \"failed to send data on stream {}: {:?}\",\n                            d.stream_id, e\n                        );\n                        return Err(From::from(e));\n                    },\n                }\n            }\n        }\n\n        if self.reqs.len() - self.current_idx == 0 {\n            return Err(quiche::h3::Error::Done);\n        }\n\n        let reqs_to_make = if self.concurrent {\n            self.reqs.len() - self.current_idx\n        } else {\n            1\n        };\n\n        for _ in 0..reqs_to_make {\n            let req = &self.reqs[self.current_idx];\n\n            info!(\"sending HTTP request {:?}\", req.hdrs);\n\n            let s =\n                match h3_conn.send_request(conn, &req.hdrs, req.body.is_none()) {\n                    Ok(stream_id) => stream_id,\n\n                    Err(e) => {\n                        error!(\"failed to send request {e:?}\");\n                        return Err(e);\n                    },\n                };\n\n            self.issued_reqs.insert(s, self.current_idx);\n\n            if let Some(body) = &req.body {\n                info!(\"sending body {body:?}\");\n\n                if let Err(e) = h3_conn.send_body(conn, s, body, true) {\n                    error!(\"failed to send request body {e:?}\");\n                    return Err(e);\n                }\n            }\n\n            self.current_idx += 1;\n        }\n\n        Ok(())\n    }\n\n    /// Append response headers for an issued request.\n    pub fn add_response_headers(&mut self, stream_id: u64, headers: &[Header]) {\n        let i = self.issued_reqs.get(&stream_id).unwrap();\n        self.reqs[*i].resp_hdrs.extend_from_slice(headers);\n    }\n\n    /// Append data to the response body for an issued request.\n    pub fn add_response_body(\n        &mut self, stream_id: u64, data: &[u8], data_len: usize,\n    ) {\n        let i = self.issued_reqs.get(&stream_id).unwrap();\n        self.reqs[*i].resp_body.extend_from_slice(&data[..data_len]);\n    }\n\n    /// Sets the error code when a RESET_STREAM was received for an issued\n    /// request.\n    pub fn set_reset_stream_error(&mut self, stream_id: u64, error: u64) {\n        let i = self.issued_reqs.get(&stream_id).unwrap();\n        self.reqs[*i].reset_stream_code = Some(error);\n    }\n\n    /// Execute the test assertion(s).\n    pub fn assert(&mut self) {\n        (self.assert)(&self.reqs);\n    }\n}\n\npub mod runner;\n"
  },
  {
    "path": "tools/http3_test/src/runner.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nuse quiche::h3::NameValue;\n\nuse ring::rand::*;\n\nuse crate::Http3TestError;\n\npub fn run(\n    test: &mut crate::Http3Test, peer_addr: std::net::SocketAddr,\n    verify_peer: bool, idle_timeout: u64, max_data: u64, early_data: bool,\n    session_file: Option<String>,\n) -> Result<(), Http3TestError> {\n    const MAX_DATAGRAM_SIZE: usize = 1350;\n\n    let mut buf = [0; 65535];\n    let mut out = [0; MAX_DATAGRAM_SIZE];\n\n    let max_stream_data = max_data;\n\n    let version = if let Some(v) = std::env::var_os(\"QUIC_VERSION\") {\n        match v.to_str() {\n            Some(\"current\") => quiche::PROTOCOL_VERSION,\n\n            Some(v) => u32::from_str_radix(v, 16).unwrap(),\n\n            _ => 0xbaba_baba,\n        }\n    } else {\n        0xbaba_baba\n    };\n\n    let mut reqs_count = 0;\n\n    let mut reqs_complete = 0;\n\n    // Setup the event loop.\n    let mut poll = mio::Poll::new().unwrap();\n    let mut events = mio::Events::with_capacity(1024);\n\n    info!(\"connecting to {peer_addr:}\");\n\n    // Bind to INADDR_ANY or IN6ADDR_ANY depending on the IP family of the\n    // server address. This is needed on macOS and BSD variants that don't\n    // support binding to IN6ADDR_ANY for both v4 and v6.\n    let bind_addr = match peer_addr {\n        std::net::SocketAddr::V4(_) => \"0.0.0.0:0\",\n        std::net::SocketAddr::V6(_) => \"[::]:0\",\n    };\n\n    // Create the UDP socket backing the QUIC connection, and register it with\n    // the event loop.\n    let mut socket =\n        mio::net::UdpSocket::bind(bind_addr.parse().unwrap()).unwrap();\n    poll.registry()\n        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)\n        .unwrap();\n\n    // Create the configuration for the QUIC connection.\n    let mut config = quiche::Config::new(version).unwrap();\n\n    config.verify_peer(verify_peer);\n\n    config\n        .set_application_protos(quiche::h3::APPLICATION_PROTOCOL)\n        .unwrap();\n\n    config.set_max_idle_timeout(idle_timeout);\n    config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);\n    config.set_initial_max_data(max_data);\n    config.set_initial_max_stream_data_bidi_local(max_stream_data);\n    config.set_initial_max_stream_data_bidi_remote(max_stream_data);\n    config.set_initial_max_stream_data_uni(max_stream_data);\n    config.set_initial_max_streams_bidi(100);\n    config.set_initial_max_streams_uni(100);\n    config.set_disable_active_migration(true);\n\n    if early_data {\n        config.enable_early_data();\n        debug!(\"early data enabled\");\n    }\n\n    let mut http3_conn = None;\n\n    if std::env::var_os(\"SSLKEYLOGFILE\").is_some() {\n        config.log_keys();\n    }\n\n    // Generate a random source connection ID for the connection.\n    let mut scid = [0; quiche::MAX_CONN_ID_LEN];\n    SystemRandom::new().fill(&mut scid[..]).unwrap();\n\n    let scid = quiche::ConnectionId::from_ref(&scid);\n\n    // Create a QUIC connection and initiate handshake.\n    let url = &test.endpoint();\n\n    let local_addr = socket.local_addr().unwrap();\n\n    let mut conn =\n        quiche::connect(url.domain(), &scid, local_addr, peer_addr, &mut config)\n            .unwrap();\n\n    if let Some(session_file) = &session_file {\n        if let Ok(session) = std::fs::read(session_file) {\n            conn.set_session(&session).ok();\n        }\n    }\n\n    let (write, send_info) = conn.send(&mut out).expect(\"initial send failed\");\n\n    while let Err(e) = socket.send_to(&out[..write], send_info.to) {\n        if e.kind() == std::io::ErrorKind::WouldBlock {\n            debug!(\"send() would block\");\n            continue;\n        }\n\n        return Err(Http3TestError::Other(format!(\"send() failed: {e:?}\")));\n    }\n\n    debug!(\"written {write}\");\n\n    let req_start = std::time::Instant::now();\n\n    loop {\n        if !conn.is_in_early_data() || http3_conn.is_some() {\n            poll.poll(&mut events, conn.timeout()).unwrap();\n        }\n\n        // Read incoming UDP packets from the socket and feed them to quiche,\n        // until there are no more packets to read.\n        'read: loop {\n            // If the event loop reported no events, it means that the timeout\n            // has expired, so handle it without attempting to read packets. We\n            // will then proceed with the send loop.\n            if events.is_empty() {\n                debug!(\"timed out\");\n\n                conn.on_timeout();\n\n                break 'read;\n            }\n\n            let (len, from) = match socket.recv_from(&mut buf) {\n                Ok(v) => v,\n\n                Err(e) => {\n                    // There are no more UDP packets to read, so end the read\n                    // loop.\n                    if e.kind() == std::io::ErrorKind::WouldBlock {\n                        debug!(\"recv() would block\");\n                        break 'read;\n                    }\n\n                    return Err(Http3TestError::Other(format!(\n                        \"recv() failed: {e:?}\",\n                    )));\n                },\n            };\n\n            debug!(\"got {len} bytes\");\n\n            let recv_info = quiche::RecvInfo {\n                from,\n                to: local_addr,\n            };\n\n            // Process potentially coalesced packets.\n            let read = match conn.recv(&mut buf[..len], recv_info) {\n                Ok(v) => v,\n\n                Err(quiche::Error::Done) => {\n                    debug!(\"done reading\");\n                    break;\n                },\n\n                Err(e) => {\n                    error!(\"recv failed: {e:?}\");\n                    break 'read;\n                },\n            };\n\n            debug!(\"processed {read} bytes\");\n        }\n\n        if conn.is_closed() {\n            info!(\"connection closed, {:?}\", conn.stats());\n\n            if !conn.is_established() {\n                error!(\"connection timed out after {:?}\", req_start.elapsed(),);\n\n                return Err(Http3TestError::HandshakeFail);\n            }\n\n            if reqs_complete != reqs_count {\n                error!(\"Client timed out after {:?} and only completed {}/{} requests\",\n                req_start.elapsed(), reqs_complete, reqs_count);\n                return Err(Http3TestError::HttpFail);\n            }\n\n            if let Some(session_file) = session_file {\n                if let Some(session) = conn.session() {\n                    std::fs::write(session_file, session).ok();\n                }\n            }\n\n            break;\n        }\n\n        // Create a new HTTP/3 connection and end an HTTP request as soon as\n        // the QUIC connection is established.\n        if (conn.is_established() || conn.is_in_early_data()) &&\n            http3_conn.is_none()\n        {\n            let h3_config = quiche::h3::Config::new().unwrap();\n\n            let mut h3_conn =\n                quiche::h3::Connection::with_transport(&mut conn, &h3_config)\n                    .unwrap();\n\n            reqs_count = test.requests_count();\n\n            match test.send_requests(&mut conn, &mut h3_conn) {\n                Ok(_) => (),\n\n                Err(quiche::h3::Error::Done) => (),\n\n                Err(e) => {\n                    return Err(Http3TestError::Other(format!(\n                        \"error sending: {e:?}\"\n                    )));\n                },\n            };\n\n            http3_conn = Some(h3_conn);\n        }\n\n        if let Some(http3_conn) = &mut http3_conn {\n            // Process HTTP/3 events.\n            loop {\n                match http3_conn.poll(&mut conn) {\n                    Ok((stream_id, quiche::h3::Event::Headers { list, .. })) => {\n                        info!(\n                            \"got response headers {:?} on stream id {}\",\n                            hdrs_to_strings(&list),\n                            stream_id\n                        );\n\n                        test.add_response_headers(stream_id, &list);\n                    },\n\n                    Ok((stream_id, quiche::h3::Event::Data)) => {\n                        while let Ok(read) =\n                            http3_conn.recv_body(&mut conn, stream_id, &mut buf)\n                        {\n                            info!(\n                                \"got {read} bytes of response data on stream {stream_id}\"\n                            );\n\n                            test.add_response_body(stream_id, &buf, read);\n                        }\n                    },\n\n                    Ok((_stream_id, quiche::h3::Event::Finished)) => {\n                        reqs_complete += 1;\n\n                        info!(\"{reqs_complete}/{reqs_count} responses received\");\n\n                        if reqs_complete == reqs_count {\n                            info!(\n                                \"Completed test run. {}/{} response(s) received in {:?}, closing...\",\n                                reqs_complete,\n                                reqs_count,\n                                req_start.elapsed()\n                            );\n\n                            match conn.close(true, 0x100, b\"kthxbye\") {\n                                // Already closed.\n                                Ok(_) | Err(quiche::Error::Done) => (),\n\n                                Err(e) => {\n                                    return Err(Http3TestError::Other(format!(\n                                        \"error closing conn: {e:?}\"\n                                    )));\n                                },\n                            }\n\n                            test.assert();\n\n                            break;\n                        }\n\n                        match test.send_requests(&mut conn, http3_conn) {\n                            Ok(_) => (),\n                            Err(quiche::h3::Error::Done) => (),\n                            Err(e) => {\n                                return Err(Http3TestError::Other(format!(\n                                    \"error sending request: {e:?}\"\n                                )));\n                            },\n                        }\n                    },\n\n                    Ok((stream_id, quiche::h3::Event::Reset(e))) => {\n                        reqs_complete += 1;\n\n                        info!(\"request was reset by peer with {e}\");\n                        test.set_reset_stream_error(stream_id, e);\n\n                        if reqs_complete == reqs_count {\n                            info!(\n                                \"Completed test run. {}/{} response(s) received in {:?}, closing...\",\n                                reqs_complete,\n                                reqs_count,\n                                req_start.elapsed()\n                            );\n\n                            match conn.close(true, 0x100, b\"kthxbye\") {\n                                // Already closed.\n                                Ok(_) | Err(quiche::Error::Done) => (),\n\n                                Err(e) => {\n                                    return Err(Http3TestError::Other(format!(\n                                        \"error closing conn: {e:?}\"\n                                    )));\n                                },\n                            }\n\n                            test.assert();\n\n                            break;\n                        }\n                    },\n\n                    Ok((_, quiche::h3::Event::PriorityUpdate)) => (),\n\n                    Ok((_goaway_id, quiche::h3::Event::GoAway)) => (),\n\n                    Err(quiche::h3::Error::Done) => {\n                        break;\n                    },\n\n                    Err(e) => {\n                        error!(\"HTTP/3 processing failed: {e:?}\");\n\n                        break;\n                    },\n                }\n            }\n        }\n\n        // Generate outgoing QUIC packets and send them on the UDP socket, until\n        // quiche reports that there are no more packets to be sent.\n        loop {\n            let (write, send_info) = match conn.send(&mut out) {\n                Ok(v) => v,\n\n                Err(quiche::Error::Done) => {\n                    debug!(\"done writing\");\n                    break;\n                },\n\n                Err(e) => {\n                    error!(\"send failed: {e:?}\");\n                    conn.close(false, 0x1, b\"fail\").ok();\n                    break;\n                },\n            };\n\n            if let Err(e) = socket.send_to(&out[..write], send_info.to) {\n                if e.kind() == std::io::ErrorKind::WouldBlock {\n                    debug!(\"send() would block\");\n                    break;\n                }\n\n                return Err(Http3TestError::Other(format!(\n                    \"send() failed: {e:?}\",\n                )));\n            }\n\n            debug!(\"written {write}\");\n        }\n\n        if conn.is_closed() {\n            info!(\"connection closed, {:?}\", conn.stats());\n\n            if reqs_complete != reqs_count {\n                error!(\"Client timed out after {:?} and only completed {}/{} requests\",\n                req_start.elapsed(), reqs_complete, reqs_count);\n                return Err(Http3TestError::HttpFail);\n            }\n\n            if let Some(session_file) = session_file {\n                if let Some(session) = conn.session() {\n                    std::fs::write(session_file, session).ok();\n                }\n            }\n\n            break;\n        }\n    }\n\n    Ok(())\n}\n\npub fn hdrs_to_strings(hdrs: &[quiche::h3::Header]) -> Vec<(String, String)> {\n    hdrs.iter()\n        .map(|h| {\n            let name = String::from_utf8_lossy(h.name()).to_string();\n            let value = String::from_utf8_lossy(h.value()).to_string();\n\n            (name, value)\n        })\n        .collect()\n}\n"
  },
  {
    "path": "tools/http3_test/tests/httpbin_tests.rs",
    "content": "// Copyright (C) 2019, Cloudflare, Inc.\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 are\n// met:\n//\n//     * Redistributions of source code must retain the above copyright notice,\n//       this list of conditions and the following disclaimer.\n//\n//     * 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//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n// IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\n// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nmod httpbin_tests {\n    use std::collections::HashMap;\n    use std::net::ToSocketAddrs;\n\n    use http3_test::Http3TestError::*;\n    use http3_test::*;\n    use quiche::h3::*;\n\n    use std::sync::Once;\n\n    static INIT: Once = Once::new();\n\n    fn endpoint(testpoint: Option<&str>) -> url::Url {\n        let endpoint = match std::env::var_os(\"HTTPBIN_ENDPOINT\") {\n            Some(val) => val.into_string().unwrap(),\n\n            None => String::from(\"https://cloudflare-quic.com/b\"),\n        };\n\n        let mut url = url::Url::parse(&endpoint).unwrap();\n\n        if let Some(testpoint) = testpoint {\n            let path = format!(\"{}/{}\", url.path(), &testpoint);\n            url.set_path(&path);\n        }\n\n        url\n    }\n\n    fn host() -> std::net::SocketAddr {\n        let url = match std::env::var_os(\"HTTPBIN_HOST\") {\n            Some(val) => {\n                let host = val.into_string().unwrap();\n                let url = format!(\"{}{}\", \"https://\", host);\n\n                url::Url::parse(&url).unwrap()\n            },\n\n            None => endpoint(None),\n        };\n\n        url.to_socket_addrs().unwrap().next().unwrap()\n    }\n\n    fn verify_peer() -> bool {\n        match std::env::var_os(\"VERIFY_PEER\") {\n            Some(val) => !matches!(val.to_str().unwrap(), \"false\"),\n            None => true,\n        }\n    }\n\n    fn idle_timeout() -> u64 {\n        match std::env::var_os(\"IDLE_TIMEOUT\") {\n            Some(val) => val.into_string().unwrap().parse::<u64>().unwrap(),\n            None => 60000,\n        }\n    }\n\n    fn extra_headers() -> Option<serde_json::Map<String, serde_json::Value>> {\n        if let Some(val) = std::env::var_os(\"EXTRA_HEADERS\") {\n            let json_string = val.into_string().unwrap();\n            let parsed: serde_json::Value =\n                serde_json::from_str(&json_string).unwrap();\n            return Some(parsed.as_object().unwrap().clone());\n        }\n\n        None\n    }\n\n    fn expect_req_headers() -> Option<serde_json::Map<String, serde_json::Value>>\n    {\n        if let Some(val) = std::env::var_os(\"EXPECT_REQ_HEADERS\") {\n            let json_string = val.into_string().unwrap();\n            let parsed: serde_json::Value =\n                serde_json::from_str(&json_string).unwrap();\n            return Some(parsed.as_object().unwrap().clone());\n        }\n\n        None\n    }\n\n    fn max_data() -> u64 {\n        match std::env::var_os(\"MAX_DATA\") {\n            Some(val) => val.into_string().unwrap().parse::<u64>().unwrap(),\n            None => 1_000_000,\n        }\n    }\n\n    fn early_data() -> bool {\n        match std::env::var_os(\"EARLY_DATA\") {\n            Some(val) => matches!(val.to_str().unwrap(), \"true\"),\n            None => false,\n        }\n    }\n\n    fn session_file() -> Option<String> {\n        std::env::var_os(\"SESSION_FILE\").map(|val| val.into_string().unwrap())\n    }\n\n    // A rudimentary structure to hold httpbin response data\n    #[derive(Debug, serde::Deserialize)]\n    struct HttpBinResponseBody {\n        args: Option<HashMap<String, String>>,\n        #[allow(dead_code)]\n        data: Option<String>,\n        #[allow(dead_code)]\n        files: Option<HashMap<String, String>>,\n        form: Option<HashMap<String, String>>,\n        headers: Option<HashMap<String, String>>,\n        json: Option<HashMap<String, String>>,\n        #[serde(rename = \"user-agent\")]\n        user_agent: Option<String>,\n        server: Option<String>,\n        #[serde(rename = \"content-type\")]\n        content_type: Option<Vec<String>>,\n        origin: Option<String>,\n        #[allow(dead_code)]\n        url: Option<String>,\n    }\n\n    fn jsonify(data: &[u8]) -> HttpBinResponseBody {\n        serde_json::from_slice(data).unwrap()\n    }\n\n    fn do_test(\n        reqs: Vec<Http3Req>, assert: Http3Assert, concurrent: bool,\n    ) -> std::result::Result<(), Http3TestError> {\n        INIT.call_once(|| env_logger::builder().format_timestamp_nanos().init());\n\n        let mut test = Http3Test::new(endpoint(None), reqs, assert, concurrent);\n        runner::run(\n            &mut test,\n            host(),\n            verify_peer(),\n            idle_timeout(),\n            max_data(),\n            early_data(),\n            session_file(),\n        )\n    }\n\n    fn do_test_with_stream_data(\n        reqs: Vec<Http3Req>, stream_data: Vec<ArbitraryStreamData>,\n        assert: Http3Assert, concurrent: bool,\n    ) -> std::result::Result<(), Http3TestError> {\n        INIT.call_once(|| env_logger::builder().format_timestamp_nanos().init());\n\n        let mut test = Http3Test::with_stream_data(\n            endpoint(None),\n            reqs,\n            stream_data,\n            assert,\n            concurrent,\n        );\n        runner::run(\n            &mut test,\n            host(),\n            verify_peer(),\n            idle_timeout(),\n            max_data(),\n            early_data(),\n            session_file(),\n        )\n    }\n\n    // Build a single request and expected response with status code\n    fn request_check_status(testpoint: &str, status: usize) -> Vec<Http3Req> {\n        let expect_hdrs =\n            Some(vec![Header::new(b\":status\", status.to_string().as_bytes())]);\n\n        let url = endpoint(Some(testpoint));\n\n        vec![Http3Req::new(\"GET\", &url, None, expect_hdrs)]\n    }\n\n    // Build a single request with a simple JSON body using the provided method\n    fn request_with_body(method: &str) -> Vec<Http3Req> {\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let url = endpoint(Some(&method.to_ascii_lowercase()));\n\n        let req_body = serde_json::json!({\"key1\": \"value1\", \"key2\": \"value2\"});\n\n        let mut req = Http3Req::new(\n            &method.to_ascii_uppercase(),\n            &url,\n            Some(req_body.to_string().into_bytes()),\n            expect_hdrs,\n        );\n\n        req.hdrs\n            .push(Header::new(b\"content-type\", b\"application/json\"));\n\n        vec![req]\n    }\n\n    fn assert_request_body(reqs: &[Http3Req]) {\n        assert_headers!(reqs[0]);\n\n        let json = jsonify(&reqs[0].resp_body).json.unwrap();\n\n        assert_eq!(json[\"key1\"], \"value1\");\n        assert_eq!(json[\"key2\"], \"value2\");\n    }\n\n    fn assert_headers_only(reqs: &[Http3Req]) {\n        for req in reqs {\n            assert_headers!(req);\n        }\n    }\n\n    #[test]\n    fn get() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let mut url = endpoint(Some(\"get\"));\n\n        // Request 1\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        // Request 2\n        url.set_query(Some(\"key1=value1&key2=value2\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n            assert_headers!(reqs[1]);\n\n            let json = jsonify(&reqs[1].resp_body);\n            if let Some(args) = json.args {\n                assert_eq!(args[\"key1\"], \"value1\");\n                assert_eq!(args[\"key2\"], \"value2\")\n            }\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_no_method() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            expect_resp_hdrs: expect_hdrs,\n            body: None,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_empty_method() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"\"),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_invalid_method() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"$GET\"),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_no_scheme() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_empty_scheme() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"\"),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_invalid_scheme() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", b\"$fail\"),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_no_authority() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_empty_authority() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":authority\", b\"\"),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_no_path() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_empty_path() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":path\", b\"\"),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_invalid_pseudoheader_name() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":$method\", b\"GET\"),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_duplicate_pseudoheader_bad_order() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"400\")]);\n\n        let url = endpoint(Some(\"get\"));\n        let path = String::from(url.path());\n\n        let hdrs = vec![\n            Header::new(b\":method\", b\"GET\"),\n            Header::new(b\":scheme\", url.scheme().as_bytes()),\n            Header::new(b\":path\", path.as_bytes()),\n            Header::new(b\":authority\", url.host_str().unwrap().as_bytes()),\n            Header::new(b\"user-agent\", USER_AGENT),\n            Header::new(b\":method\", b\"GET\"),\n        ];\n\n        let req = Http3Req {\n            url: url.clone(),\n            hdrs,\n            body: None,\n            expect_resp_hdrs: expect_hdrs,\n            resp_hdrs: Vec::new(),\n            resp_body: Vec::new(),\n            reset_stream_code: None,\n        };\n\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn req_too_large_headers() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        // This test explicitly tries to exceed the server's\n        // MAX_FIELD_SECTION_SIZE setting. Therefore, it is expected that\n        // the invoker supplies an additional header to this test\n        // via the EXTRA_HEADERS environment variable.\n        if let Some(headers) = &extra_headers() {\n            for (name, val) in headers {\n                println!(\"{name}: {val}\");\n                reqs[0].hdrs.push(Header::new(\n                    name.as_bytes(),\n                    val.as_str().unwrap().as_bytes(),\n                ));\n            }\n        };\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        assert_eq!(Err(HttpFail), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn frames_duplicate_settings() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        let mut stream_data = Vec::new();\n\n        // A buffer holding a valid SETTINGS frame encoded in wire format.\n        let d = vec![\n            4, 16, 213, 164, 106, 24, 4, 152, 223, 76, 209, 101, 114, 237, 239,\n            178, 71, 46,\n        ];\n\n        let data_frame = ArbitraryStreamData {\n            stream_id: 0,\n            data: d,\n            fin: false,\n        };\n\n        stream_data.push(data_frame);\n\n        assert_eq!(\n            Err(HttpFail),\n            do_test_with_stream_data(reqs, stream_data, assert, true)\n        );\n    }\n\n    #[test]\n    fn frames_max_push_on_request() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        let mut stream_data = Vec::new();\n\n        // A buffer containing a valid MAX_PUSH_ID frame encoded in wire format.\n        let d = vec![13, 1, 4];\n\n        let data_frame = ArbitraryStreamData {\n            stream_id: 0,\n            data: d,\n            fin: false,\n        };\n\n        stream_data.push(data_frame);\n\n        assert_eq!(\n            Err(HttpFail),\n            do_test_with_stream_data(reqs, stream_data, assert, true)\n        );\n    }\n\n    #[test]\n    fn frames_data_on_control() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        let mut stream_data = Vec::new();\n\n        // A buffer containing a 0-length DATA frame encoded in wire format.\n        let d = vec![0; 2];\n\n        let data_frame = ArbitraryStreamData {\n            stream_id: 0,\n            data: d,\n            fin: false,\n        };\n\n        stream_data.push(data_frame);\n\n        assert_eq!(\n            Err(HttpFail),\n            do_test_with_stream_data(reqs, stream_data, assert, true)\n        );\n    }\n\n    #[test]\n    fn frames_data_before_headers() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        let mut stream_data = Vec::new();\n\n        // A buffer containing a 0-length DATA frame encoded in wire format.\n        let d = vec![0; 2];\n\n        let data_frame = ArbitraryStreamData {\n            stream_id: 0,\n            data: d,\n            fin: false,\n        };\n\n        stream_data.push(data_frame);\n\n        assert_eq!(\n            Err(HttpFail),\n            do_test_with_stream_data(reqs, stream_data, assert, true)\n        );\n    }\n\n    #[test]\n    fn frames_too_small_headers() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        let mut stream_data = Vec::new();\n\n        // A buffer of containing an invalid HEADERS frame encoded in wire format.\n        let d = vec![1, 3, 1, 1, 1];\n\n        let data_frame = ArbitraryStreamData {\n            stream_id: 4,\n            data: d,\n            fin: false,\n        };\n\n        stream_data.push(data_frame);\n\n        assert_eq!(\n            Err(HttpFail),\n            do_test_with_stream_data(reqs, stream_data, assert, true)\n        );\n    }\n\n    #[test]\n    fn stream_close_control() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        let stream_data = vec![ArbitraryStreamData {\n            stream_id: 2,\n            data: b\"\".to_vec(),\n            fin: true,\n        }];\n\n        assert_eq!(\n            Err(HttpFail),\n            do_test_with_stream_data(reqs, stream_data, assert, true)\n        );\n    }\n\n    #[test]\n    fn stream_close_qpack_enc() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        let stream_data = vec![ArbitraryStreamData {\n            stream_id: 6,\n            data: b\"\".to_vec(),\n            fin: true,\n        }];\n\n        assert_eq!(\n            Err(HttpFail),\n            do_test_with_stream_data(reqs, stream_data, assert, true)\n        );\n    }\n\n    #[test]\n    fn stream_close_qpack_dec() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let url = endpoint(Some(\"get\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n        };\n\n        let stream_data = vec![ArbitraryStreamData {\n            stream_id: 10,\n            data: b\"\".to_vec(),\n            fin: true,\n        }];\n\n        assert_eq!(\n            Err(HttpFail),\n            do_test_with_stream_data(reqs, stream_data, assert, true)\n        );\n    }\n\n    #[test]\n    fn ip() {\n        let reqs = request_check_status(\"ip\", 200);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n\n            let json = jsonify(&reqs[0].resp_body);\n            assert!(json.origin.is_some())\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn useragent() {\n        let reqs = request_check_status(\"user-agent\", 200);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n\n            let json = jsonify(&reqs[0].resp_body);\n            assert_eq!(\n                json.user_agent,\n                String::from_utf8(USER_AGENT.to_vec()).ok()\n            );\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn headers() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let mut url = endpoint(Some(\"headers\"));\n        url.set_query(Some(\"show_env=1\")); // reveal X-Forwarded-* headers\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs.clone()));\n\n        if let Some(headers) = &extra_headers() {\n            for (name, val) in headers {\n                reqs[0].hdrs.push(Header::new(\n                    name.as_bytes(),\n                    val.as_str().unwrap().as_bytes(),\n                ));\n            }\n        };\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n\n            let json = jsonify(&reqs[0].resp_body);\n            assert_ne!(json.headers, None);\n            if let Some(headers) = json.headers {\n                if let Some(expected_headers) = &expect_req_headers() {\n                    for (name, val) in expected_headers {\n                        if let Some(expected_value) = val.as_str() {\n                            assert_eq!(\n                                headers.get(name),\n                                Some(&String::from(expected_value)),\n                                \"Header '{name}' doesn't match\"\n                            );\n                        } else {\n                            assert_eq!(\n                                headers.get(name),\n                                None,\n                                \"Header '{name}' exists\"\n                            );\n                        }\n                    }\n                }\n            }\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn post() {\n        let reqs = request_with_body(\"post\");\n        assert_eq!(Ok(()), do_test(reqs, assert_request_body, true));\n    }\n\n    #[test]\n    fn put() {\n        let reqs = request_with_body(\"put\");\n        assert_eq!(Ok(()), do_test(reqs, assert_request_body, true));\n    }\n\n    #[test]\n    fn patch() {\n        let reqs = request_with_body(\"patch\");\n        assert_eq!(Ok(()), do_test(reqs, assert_request_body, true));\n    }\n\n    #[test]\n    fn delete() {\n        let reqs = request_with_body(\"delete\");\n        assert_eq!(Ok(()), do_test(reqs, assert_request_body, true));\n    }\n\n    #[test]\n    fn encode_utf8() {\n        let mut reqs = Vec::new();\n\n        let expect_hdrs = Some(vec![\n            Header::new(b\":status\", b\"200\"),\n            Header::new(b\"content-type\", b\"text/html; charset=utf-8\"),\n        ]);\n\n        let url = endpoint(Some(\"encoding/utf8\"));\n\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn gzip() {\n        let mut reqs = Vec::new();\n\n        let expect_hdrs = Some(vec![\n            Header::new(b\":status\", b\"200\"),\n            Header::new(b\"content-encoding\", b\"gzip\"),\n        ]);\n\n        let url = endpoint(Some(\"gzip\"));\n\n        let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        req.hdrs.push(Header::new(b\"accept-encoding\", b\"gzip\"));\n\n        reqs.push(req);\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn deflate() {\n        let mut reqs = Vec::new();\n\n        // Not all servers actually take up the deflate option,\n        // so don't check content-type response header.\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let url = endpoint(Some(\"deflate\"));\n\n        let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        req.hdrs.push(Header::new(b\"accept-encoding\", b\"deflate\"));\n        reqs.push(req);\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn status() {\n        let mut reqs = Vec::new();\n\n        for i in (200..600).step_by(100) {\n            for j in 0..5 {\n                let expect_hdrs = Some(vec![Header::new(\n                    b\":status\",\n                    (i + j).to_string().as_bytes(),\n                )]);\n\n                let testpoint = format!(\"{}/{}\", \"status\", i + j);\n                let url = endpoint(Some(&testpoint));\n\n                reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n            }\n        }\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, false));\n    }\n\n    #[test]\n    fn response_headers() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let mut url = endpoint(Some(\"response-headers\"));\n        url.set_query(Some(\n            \"content-type=text/plain;+charset=UTF-8&server=httpbin\",\n        ));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n            let json = jsonify(&reqs[0].resp_body);\n\n            let server = json.server.unwrap();\n            assert_eq!(server, \"httpbin\");\n\n            let content_type = json.content_type.unwrap();\n            assert_eq!(content_type[0], \"application/json\");\n            assert_eq!(content_type[1], \"text/plain; charset=UTF-8\");\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn redirect() {\n        let mut reqs = Vec::new();\n        let mut url = endpoint(Some(\"redirect-to\"));\n\n        // Request 1\n        let expect_hdrs = Some(vec![\n            Header::new(b\":status\", b\"302\"),\n            Header::new(b\"location\", b\"https://example.com\"),\n        ]);\n\n        url.set_query(Some(\"url=https://example.com\"));\n\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        // Request 2\n        let expect_hdrs = Some(vec![\n            Header::new(b\":status\", b\"307\"),\n            Header::new(b\"location\", b\"https://example.com\"),\n        ]);\n        url.set_query(Some(\"url=https://example.com&status_code=307\"));\n\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        // Request 3\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"302\")]);\n        let url = endpoint(Some(\"relative-redirect/3\"));\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn cookies() {\n        // Tests cookie redirect cases since the client ignores cookies\n        let mut reqs = Vec::new();\n\n        // Request 1\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"302\")]);\n        let mut url = endpoint(Some(\"cookies/set\"));\n        url.set_query(Some(\"k1=v1\"));\n\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        // Request 2\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"302\")]);\n\n        let mut url = endpoint(Some(\"cookies/set\"));\n        url.set_query(Some(\"k1=v1\"));\n\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn basic_auth() {\n        let mut reqs = Vec::new();\n        let url = endpoint(Some(\"basic-auth/user/passwd\"));\n\n        let expect_hdrs = Some(vec![\n            Header::new(b\":status\", b\"401\"),\n            Header::new(b\"www-authenticate\", b\"Basic realm=\\\"Fake Realm\\\"\"),\n        ]);\n\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn stream() {\n        let mut reqs = Vec::new();\n\n        let sizes = [1, 50, 100];\n\n        for size in &sizes {\n            let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n            let testpoint = format!(\"{}/{}\", \"stream\", size);\n\n            let url = endpoint(Some(&testpoint));\n\n            reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n        }\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n            assert_headers!(reqs[1]);\n            assert_headers!(reqs[2]);\n\n            let line_count = std::str::from_utf8(&reqs[0].resp_body)\n                .unwrap()\n                .matches('\\n')\n                .count();\n            assert_eq!(line_count, 1);\n\n            let line_count = std::str::from_utf8(&reqs[1].resp_body)\n                .unwrap()\n                .matches('\\n')\n                .count();\n            assert_eq!(line_count, 50);\n\n            let line_count = std::str::from_utf8(&reqs[2].resp_body)\n                .unwrap()\n                .matches('\\n')\n                .count();\n            assert_eq!(line_count, 100);\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn delay() {\n        let mut reqs = Vec::new();\n\n        let delays = [1, 10, 30];\n\n        for delay in &delays {\n            let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n            let testpoint = format!(\"{}/{}\", \"delay\", delay);\n            let url = endpoint(Some(&testpoint));\n\n            reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n        }\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, false));\n    }\n\n    #[test]\n    fn drip() {\n        let mut reqs = Vec::new();\n\n        let durations = [1, 10, 30];\n\n        for duration in &durations {\n            let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n            let mut url = endpoint(Some(\"drip\"));\n            url.set_query(Some(&format!(\n                \"duration={duration}&numbytes=5&code=200\"\n            )));\n\n            reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n        }\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, false));\n    }\n\n    #[test]\n    #[cfg(feature = \"test_resets\")]\n    fn drip_delay_reset() {\n        let mut reqs = Vec::new();\n\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n        let mut url = endpoint(Some(\"drip\"));\n        url.set_query(Some(\"duration=30&numbytes=2&delay=1\"));\n\n        reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n\n            assert_eq!(reqs[0].reset_stream_code, Some(256));\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, false));\n    }\n\n    #[test]\n    fn range() {\n        let mut reqs = Vec::new();\n\n        // Request 1\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let url = endpoint(Some(\"range/102400\"));\n\n        let req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        reqs.push(req);\n\n        // Request 2\n        let expect_hdrs = Some(vec![\n            Header::new(b\":status\", b\"206\"),\n            Header::new(b\"content-range\", b\"bytes 0-49/102400\"),\n        ]);\n\n        let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        req.hdrs.push(Header::new(b\"range\", b\"bytes=0-49\"));\n        reqs.push(req);\n\n        // Request 3\n        let expect_hdrs = Some(vec![\n            Header::new(b\":status\", b\"206\"),\n            Header::new(b\"content-range\", b\"bytes 100-10000/102400\"),\n        ]);\n        let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        req.hdrs.push(Header::new(b\"range\", b\"bytes=100-10000\"));\n        reqs.push(req);\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn cache() {\n        let mut reqs = Vec::new();\n\n        // Request 1\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let url = endpoint(Some(\"cache\"));\n\n        let req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        reqs.push(req);\n\n        // Request 2\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"304\")]);\n\n        let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        req.hdrs.push(Header::new(\n            b\"if-modified-since\",\n            b\"Wed, 21 Oct 2015 07:28:00 GMT\",\n        ));\n        reqs.push(req);\n\n        // Request 3\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"304\")]);\n        let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        req.hdrs.push(Header::new(b\"if-none-match\", b\"*\"));\n        reqs.push(req);\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn bytes() {\n        let mut reqs = Vec::new();\n\n        let sizes = [10, 100, 1000, 10000, 100_000];\n\n        for size in &sizes {\n            let expect_hdrs = Some(vec![\n                Header::new(b\":status\", b\"200\"),\n                Header::new(b\"content-length\", size.to_string().as_bytes()),\n            ]);\n\n            let testpoint = format!(\"{}/{}\", \"bytes\", size);\n            let url = endpoint(Some(&testpoint));\n\n            reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n        }\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn stream_bytes() {\n        let mut reqs = Vec::new();\n\n        let sizes = [10, 100, 1000, 10000, 100_000];\n\n        for size in &sizes {\n            let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n            let testpoint = format!(\"{}/{}\", \"stream-bytes\", size);\n            let url = endpoint(Some(&testpoint));\n\n            reqs.push(Http3Req::new(\"GET\", &url, None, expect_hdrs));\n        }\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn image() {\n        let mut reqs = Vec::new();\n\n        // Request 1\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"406\")]);\n\n        let url = endpoint(Some(\"image\"));\n\n        let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        req.hdrs.push(Header::new(b\"accept\", b\"*/*\"));\n        reqs.push(req);\n\n        // Request 2\n        let expect_hdrs = Some(vec![\n            Header::new(b\":status\", b\"200\"),\n            Header::new(b\"content-type\", b\"image/png\"),\n        ]);\n        let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        req.hdrs.push(Header::new(b\"accept\", b\"image/*\"));\n        reqs.push(req);\n\n        // Multiple requests based on accept\n        let formats = [\"image/webp\", \"image/svg+xml\", \"image/jpeg\", \"image/png\"];\n        for format in &formats {\n            let expect_hdrs = Some(vec![\n                Header::new(b\":status\", b\"200\"),\n                Header::new(b\"content-type\", format.as_bytes()),\n            ]);\n\n            let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n            req.hdrs.push(Header::new(b\"accept\", format.as_bytes()));\n            reqs.push(req);\n        }\n\n        // Multiple requests based on path\n        for format in &formats {\n            let expect_hdrs = Some(vec![\n                Header::new(b\":status\", b\"200\"),\n                Header::new(b\"content-type\", format.as_bytes()),\n            ]);\n\n            let testpoint = if format == &\"image/svg+xml\" {\n                \"image/svg\"\n            } else {\n                format\n            };\n\n            let url = endpoint(Some(testpoint));\n            let mut req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n            req.hdrs.push(Header::new(b\"accept\", format.as_bytes()));\n            reqs.push(req);\n        }\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn form() {\n        let mut reqs = Vec::new();\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let url = endpoint(Some(\"post\"));\n\n        let req_body = \"custname=dave&custtel=1234&custemail=dave@example.com&size=large&topping=bacon&delivery=&comments=pronto\";\n\n        let mut req = Http3Req::new(\n            \"POST\",\n            &url,\n            Some(req_body.to_string().into_bytes()),\n            expect_hdrs,\n        );\n\n        req.hdrs.push(Header::new(\n            b\"content-type\",\n            b\"application/x-www-form-urlencoded\",\n        ));\n        reqs.push(req);\n\n        let assert = |reqs: &[Http3Req]| {\n            assert_headers!(reqs[0]);\n\n            let json = jsonify(&reqs[0].resp_body);\n            if let Some(form) = json.form {\n                assert_eq!(form[\"custname\"], \"dave\");\n                assert_eq!(form[\"custtel\"], \"1234\");\n                assert_eq!(form[\"custemail\"], \"dave@example.com\");\n                assert_eq!(form[\"size\"], \"large\");\n                assert_eq!(form[\"topping\"], \"bacon\");\n                assert_eq!(form[\"delivery\"], \"\");\n                assert_eq!(form[\"comments\"], \"pronto\");\n            }\n        };\n\n        assert_eq!(Ok(()), do_test(reqs, assert, true));\n    }\n\n    #[test]\n    fn html() {\n        let reqs = request_check_status(\"html\", 200);\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn xml() {\n        let reqs = request_check_status(\"xml\", 200);\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn robots() {\n        let reqs = request_check_status(\"robots.txt\", 200);\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn links() {\n        let reqs = request_check_status(\"links/10\", 302);\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn zero_length_body() {\n        let mut reqs = Vec::new();\n\n        let expect_hdrs = Some(vec![Header::new(b\":status\", b\"200\")]);\n\n        let url = endpoint(Some(\"stream/0\"));\n\n        let req = Http3Req::new(\"GET\", &url, None, expect_hdrs);\n        reqs.push(req.clone());\n        reqs.push(req);\n\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn mismatched_content_length_too_long() {\n        let mut too_long = request_with_body(\"post\")[0].clone();\n        Http3Req::add_or_replace_header(\n            &mut too_long.hdrs,\n            Header::new(b\"content-length\", b\"32\"),\n        );\n\n        if let Some(expected_resp_hdrs) = too_long.expect_resp_hdrs.as_mut() {\n            Http3Req::add_or_replace_header(\n                expected_resp_hdrs,\n                Header::new(b\":status\", b\"400\"),\n            );\n        }\n\n        let reqs = vec![too_long];\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n\n    #[test]\n    fn mismatched_content_length_too_short() {\n        let mut too_short = request_with_body(\"post\")[0].clone();\n\n        Http3Req::add_or_replace_header(\n            &mut too_short.hdrs,\n            Header::new(b\"content-length\", b\"34\"),\n        );\n\n        if let Some(expected_resp_hdrs) = too_short.expect_resp_hdrs.as_mut() {\n            Http3Req::add_or_replace_header(\n                expected_resp_hdrs,\n                Header::new(b\":status\", b\"400\"),\n            );\n        }\n\n        let reqs = vec![too_short];\n        assert_eq!(Ok(()), do_test(reqs, assert_headers_only, true));\n    }\n}\n"
  }
]