[
  {
    "path": ".github/workflows/changelog.yml",
    "content": "name: Changelog\non:\n  pull_request:\n    types: [opened, synchronize, reopened, labeled, unlabeled]\n\njobs:\n  validate:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n        with:\n          # We want to check modified files from base_ref (merge target) to the current commit,\n          # so we need the full history of the current branch.\n          fetch-depth: 0\n\n      - name: Check for skip label\n        id: check-skip\n        run: |\n          if [[ \"${{ contains(github.event.pull_request.labels.*.name, 'skip-changelog') }}\" == \"true\" ]]; then\n            echo \"skip=true\" >> $GITHUB_OUTPUT\n            echo \"Skipping changelog validation due to skip-changelog label\"\n          else\n            echo \"skip=false\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Validate changelog entry\n        if: steps.check-skip.outputs.skip == 'false'\n        run: |\n          # --quiet implies --exit-code (but doesn't produce output).\n          # --exit-code exits 1 if there are differences, or 0 if there are no differences.\n          # So: this command exits 1 if CHANGELOG.md was changed between the base and HEAD,\n          # or 0 if there are no diffs.\n          if git diff --quiet origin/\"${{ github.base_ref }}\"...HEAD -- CHANGELOG.md; then\n            echo \"::error::No changelog entry found. Please add an entry to CHANGELOG.md or add the 'skip-changelog' label if this change does not require a changelog entry.\"\n            exit 1\n          else\n            echo \"Changelog has been updated\"\n          fi\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Main\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [ubuntu-24.04, macos-14, windows-2022]\n        arch: [amd64, arm64]\n        exclude:\n          - os: windows-2022\n            arch: arm64\n        include:\n          - os: ubuntu-24.04\n            name: linux\n            rust_abi: unknown-linux-gnu\n          - os: macos-14\n            name: darwin\n            rust_abi: apple-darwin\n          - os: windows-2022\n            name: windows\n            rust_abi: pc-windows-msvc\n            extension: .exe\n          - arch: arm64\n            rust_arch: aarch64\n          - arch: amd64\n            rust_arch: x86_64\n\n    runs-on: ${{ matrix.os }}\n    outputs:\n      draft_release_id: ${{ steps.release.outputs.id }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          submodules: true\n\n      - name: Install Rust\n        run: rustup target add ${{ matrix.rust_arch }}-${{ matrix.rust_abi }}\n        shell: bash\n\n      - name: Install C cross-compilation toolchain\n        if: ${{ matrix.name == 'linux' && matrix.arch != 'amd64' }}\n        run: |\n          sudo apt-get update\n          sudo apt install -f -y gcc-${{ matrix.rust_arch }}-linux-gnu\n          echo CC=${{ matrix.rust_arch }}-linux-gnu-gcc >> $GITHUB_ENV\n          echo RUSTFLAGS='-C linker=${{ matrix.rust_arch }}-linux-gnu-gcc' >> $GITHUB_ENV\n\n      - name: Extract tag name\n        uses: olegtarasov/get-tag@v2.1.2\n        id: tagName\n\n      - name: Build\n        run: |\n          cargo build --release --workspace --locked --target=${{ matrix.rust_arch }}-${{ matrix.rust_abi }}\n\n      - name: Strip symbols (linux)\n        if: ${{ matrix.name == 'linux' }}\n        run: |\n          ${{ matrix.rust_arch }}-linux-gnu-strip target/${{ matrix.rust_arch }}-${{ matrix.rust_abi }}/release/viceroy${{ matrix.extension }}\n\n      - name: Strip symbols (non-linux)\n        if: ${{ matrix.name != 'linux' }}\n        run: |\n          strip target/${{ matrix.rust_arch }}-${{ matrix.rust_abi }}/release/viceroy${{ matrix.extension }}\n\n      - name: Package\n        run: |\n          cd target/${{ matrix.rust_arch }}-${{ matrix.rust_abi }}/release\n          tar czf viceroy_${{ steps.tagName.outputs.tag }}_${{ matrix.name }}-${{ matrix.arch }}.tar.gz viceroy${{ matrix.extension }}\n\n      - name: Release\n        id: release\n        uses: softprops/action-gh-release@v1\n        with:\n          draft: true\n          files: |\n            target/${{ matrix.rust_arch }}-${{ matrix.rust_abi }}/release/viceroy_${{ steps.tagName.outputs.tag }}_${{ matrix.name }}-${{ matrix.arch }}.tar.gz\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n  publish:\n    needs: build\n    runs-on: ubuntu-24.04\n    steps:\n      - name: publish\n        uses: actions/github-script@v6\n        with:\n          script: |\n            github.rest.repos.updateRelease({\n              release_id: ${{ needs.build.outputs.draft_release_id }},\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              draft: false\n            })\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n  publish-crates:\n    needs: build\n    runs-on: ubuntu-24.04\n    permissions:\n      id-token: write\n      contents: read\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          submodules: true\n      - name: Install Rust\n        run: rustup show\n      - name: Authenticate with crates.io\n        uses: rust-lang/crates-io-auth-action@v1\n        id: auth\n      - name: Publish viceroy-lib\n        run: cargo publish -p viceroy-lib --locked\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}\n      - name: Publish viceroy\n        run: cargo publish -p viceroy --locked\n        env:\n          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}\n"
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Test Rust versions\n\non:\n  schedule:\n    - cron: \"2 13 * * 1\"\n  workflow_dispatch:\n\njobs:\n  test:\n    strategy:\n      matrix:\n        rust: [stable, beta, nightly]\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v5\n      with:\n        submodules: true\n    - name: Install Rust\n      run: rustup default ${{ matrix.rust }}\n    - run: make ci\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non: [pull_request]\n\nenv:\n  CACHE_GENERATION: 0\njobs:\n  test:\n    strategy:\n      matrix:\n        platform: [ubuntu-22.04, ubuntu-24.04, windows-2022, macos-14, macos-15]\n    runs-on: ${{ matrix.platform }}\n    env:\n      SCCACHE_GHA_ENABLED: \"true\"\n      RUSTC_WRAPPER: \"sccache\"\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v3\n      with:\n        submodules: true\n    - name: Cache cargo\n      uses: actions/cache@v3\n      with:\n        path: |\n          ~/.cargo/registry\n        key: ${{ matrix.platform }}-cargo-v${{ env.CACHE_GENERATION }}-${{ hashFiles('**/Cargo.lock') }}\n    - name: Run sccache-cache\n      uses: mozilla-actions/sccache-action@v0.0.9\n    - name: Check formatting\n      run: cargo fmt --all -- --check\n      shell: bash\n    - name: test\n      run: make ci\n      shell: bash\n    - name: adapter\n      run: cd wasm_abi/adapter && cargo build --release -p viceroy-component-adapter --target wasm32-unknown-unknown\n\n  # Run the trap test in an isolated job. It needs different cargo features than the usual build, so\n  # it entails rebuilding the whole workspace if we combine them in a single job. This way, we\n  # achieve some parallelism via Actions jobs.\n  trap-test:\n    strategy:\n      matrix:\n        platform: [ubuntu-22.04, ubuntu-24.04, windows-2022, macos-14, macos-15]\n    runs-on: ${{ matrix.platform }}\n    env:\n      SCCACHE_GHA_ENABLED: \"true\"\n      RUSTC_WRAPPER: \"sccache\"\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v3\n      with:\n        submodules: true\n    - name: Cache cargo\n      uses: actions/cache@v3\n      with:\n        path: |\n          ~/.cargo/registry\n        key: ${{ matrix.platform }}-cargo-trap-v${{ env.CACHE_GENERATION }}-${{ hashFiles('**/Cargo.lock') }}\n    - name: Run sccache-cache\n      uses: mozilla-actions/sccache-action@v0.0.9\n    - name: trap-test\n      run: make trap-test-ci\n      shell: bash\n\n  package-check:\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          submodules: true\n      - name: Check crates can be published\n        run: cargo publish --dry-run --package=viceroy-lib\n        shell: bash\n"
  },
  {
    "path": ".gitignore",
    "content": "**/target/\npublish\nvendor/\nverify-publishable/\n.vscode\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## Unreleased\n\n- Rename 'session' to 'sandbox' since that is the preferred term in public documentation. ([#617](https://github.com/fastly/Viceroy/pull/617))\n- Add support for \"WebSockets passthrough\" ([#621](https://github.com/fastly/Viceroy/pull/621))\n- Provide extra context in error messages for backend connection failures ([#613](https://github.com/fastly/Viceroy/pull/613))\n\n## 0.17.0 (2026-04-27)\n\n- Add stub implementations for resvpnproxy hostcalls. ([#596](https://github.com/fastly/Viceroy/pull/596))\n- Add `fake_valid_fastly_keys` config parameter to allow testing `fastly_key_is_valid` hostcall with fake valid keys. ([#599](https://github.com/fastly/Viceroy/pull/599))\n- Add `health` config parameter for backends to mock backend health status in testing. ([#605](https://github.com/fastly/Viceroy/pulls/606))\n- Use `cargo clippy` to lint code in CI. ([#603](https://github.com/fastly/Viceroy/pull/603))\n- Rename `Error::InvalidAlpnRepsonse` to correct a typo ([#612](https://github.com/fastly/Viceroy/pull/612))\n- Upgrade to Rust 1.95 ([#604](https://github.com/fastly/Viceroy/pull/604))\n- Add options for experimenting with wasm gc and exceptions. ([#601](https://github.com/fastly/Viceroy/pull/601))\n- Improve TLS certificate loading, handling and validation ([#478](https://github.com/fastly/Viceroy/pull/478))\n\n## 0.16.5 (2026-03-23)\n\n- Remove most adapter-only interfaces from the Viceroy component adapter. ([#583](https://github.com/fastly/Viceroy/pull/583))\n- Add support for \"library\" components which don't export `http_incoming`. ([#529](https://github.com/fastly/Viceroy/pull/529))\n- Add non-shift adapter when user module is using wit-bindgen. ([#582](https://github.com/fastly/Viceroy/pull/582))\n- Update to Wasmtime 39 ([#584](https://github.com/fastly/Viceroy/pull/584))\n- Remove unused WIT files from the source tree. ([#580](https://github.com/fastly/Viceroy/pull/580))\n- Refactor the CLI, with each subcommand in its own file. ([#581](https://github.com/fastly/Viceroy/pull/581))\n- Make \"viceroy adapt\" add \"produced-by\" metadata to its output. ([#586](https://github.com/fastly/Viceroy/pull/586))\n- Update to Rust 2024 Edition. ([#588](https://github.com/fastly/Viceroy/pull/588))\n- Add stub implementations for bot detection hostcalls. ([#592](https://github.com/fastly/Viceroy/pull/592))\n- Add no-op implementations for stale-if-error hostcalls ([#591](https://github.com/fastly/Viceroy/pull/591))\n- Guest profiling support added for components. ([#593](https://github.com/fastly/Viceroy/pull/593))\n- Add `manifest_version` validation to fastly.toml parsing. ([#590](https://github.com/fastly/Viceroy/pull/590))\n\n## 0.16.4 (2026-01-26)\n\n- Update to the latest WITs (0.1.0—no more \"prerelease\") and adapter. ([#574](https://github.com/fastly/Viceroy/pull/574))\n\n## 0.16.3 (2026-01-20)\n\n- Implement `fastly_compute_runtime::get_heap_mib` hostcall ([#572](https://github.com/fastly/Viceroy/pull/572))\n- Update to latest `moka` release to fix use-after-free bug ([#569](https://github.com/fastly/Viceroy/pull/569))\n- Fix manual framing headers logic to avoid falling back to automatic framing headers ([#571](https://github.com/fastly/Viceroy/pull/571))\n\n## 0.16.2 (2025-12-10)\n\n- Update to the latest WITs and adapter. ([#564](https://github.com/fastly/Viceroy/pull/564))\n\n  This applies version \"0.0.0-prerelease.0\" to wasm_abi/wit/deps/fastly/compute.wit.\n  As a prerelease, this version is not guaranteed to be supported long-term, however\n  from this point forward, changes will be described by version number bumps.\n\n- fix: use original static backend host ([#549](https://github.com/fastly/Viceroy/pull/549))\n- Return InvalidArgument for bad arguments to register_dynamic_backend ([#563](https://github.com/fastly/Viceroy/pull/563))\n\n## 0.16.1 (2025-11-25)\n\n- Fix off-by-one error in reusable sandboxes limit ([#561](https://github.com/fastly/Viceroy/pull/561))\n- Add stubs for new dynamic backend config options ([#560](https://github.com/fastly/Viceroy/pull/560))\n- Fix yaml syntax in rust.yml ([#559](https://github.com/fastly/Viceroy/pull/559))\n- Add a CI script to test different Rust versions ([#556](https://github.com/fastly/Viceroy/pull/556))\n- Return `InvalidArgument` for non-103 1xx status codes ([#557](https://github.com/fastly/Viceroy/pull/557))\n- Fix test for 103 Early Hints ([#558](https://github.com/fastly/Viceroy/pull/558))\n\n## 0.16.0 (2025-11-10)\n\n- Add rudimentary support for 103 responses ([#550](https://github.com/fastly/Viceroy/pull/550))\n- Add support for manual HTTP framing headers ([#551](https://github.com/fastly/Viceroy/pull/551))\n- Core cache: Only return `FOUND` if the object is not expired ([#552](https://github.com/fastly/Viceroy/pull/552))\n\n## 0.15.0 (2025-10-27)\n\n- fix: don't throw error when exit code is 0 ([#537](https://github.com/fastly/Viceroy/pull/537))\n- Update adapter with the memory shift fix ([#538](https://github.com/fastly/Viceroy/pull/538))\n- Enable relaxed-simd-deterministic for Viceroy. ([#539](https://github.com/fastly/Viceroy/pull/539))\n- Return 5XX for requests that land in a reused session that crashes ([#540](https://github.com/fastly/Viceroy/pull/540))\n- Update to the latest WITs and adapter. ([#543](https://github.com/fastly/Viceroy/pull/543))\n- Allow components to have multiple memories. ([#545](https://github.com/fastly/Viceroy/pull/545))\n\n## 0.14.4 (2025-10-01)\n\n- Enable loading Secret Store configuration through environment variables ([#527](https://github.com/fastly/Viceroy/pull/527))\n- Remove deprecated `macos-13` runners from CI matrix ([#528](https://github.com/fastly/Viceroy/pull/528))\n- Remove now-unneeded adapter profiles from the top-level Cargo.toml. ([#530](https://github.com/fastly/Viceroy/pull/530))\n- Fix double borrows in `get_with_by_ref()` calls ([#532](https://github.com/fastly/Viceroy/pull/532))\n- Fix warnings due to SDK deprecations ([#532](https://github.com/fastly/Viceroy/pull/532))\n- Update references of `wasm32-wasi` to `wasm32-wasip1` in README ([#533](https://github.com/fastly/Viceroy/pull/533))\n\n## 0.14.3 (2025-09-17)\n\n- Upgrade to wasmtime v35 ([#513](https://github.com/fastly/Viceroy/pull/513))\n- Fix implementation of `downstream_compliance_region` hostcall ([#519](https://github.com/fastly/Viceroy/pull/519))\n- Fix warnings due to `mismatched_lifetime_syntaxes` lint in Rust 1.89 ([#522](https://github.com/fastly/Viceroy/pull/522))\n- Enable tracing in WIT bindings ([#521](https://github.com/fastly/Viceroy/pull/521))\n- Support overriding client IP with `inspect` hostcall ([#523](https://github.com/fastly/Viceroy/pull/523))\n- Add stub for `downstream_client_tls_servername` hostcall ([#524](https://github.com/fastly/Viceroy/pull/524))\n\n## 0.14.2 (2025-08-12)\n\n- Upgrade to wasmtime v28\n\n## 0.14.1 (2025-08-08)\n\n- Fix Cargo.lock file to allow publication on crates.io\n\n## 0.14.0 (2025-08-08)\n\n- Fix for shielding support ([#503](https://github.com/fastly/Viceroy/pull/503))\n\n  Shielding support in 0.13.0 was, alas, slightly broken: the `toml` settings were\n  not passed through to the main library.\n\n- Add support for integrating with a local Pushpin instance ([#497](https://github.com/fastly/Viceroy/pull/497))\n\n  Viceroy can be invoked with `--local-pushpin-proxy-port=<port>` to cause\n  requests that invoke the `fastly_http_req.redirect_to_grip_proxy` and `_v2`\n  hostcalls to be proxied through a local Pushpin instance running on the\n  specified port. This enables developers to locally test Fanout (real-time\n  messaging) features like HTTP streaming or WebSockets-over-HTTP.\n\n  Requests forwarded this way will be replayed:\n   - Method, headers, path, and query of the forwarded request will be based on\n     the request whose handle is passed to `redirect_to_grip_proxy_v2` (or from\n     the downstream request if `redirect_to_grip_proxy` was called).\n   - The request header `pushpin-route` will be added, its value set to the backend name.\n   - Request body of the forwarded request will contain the full body of the\n     downstream request.\n\n  This mechanism expects Pushpin to be configured with `accept_pushpin_route`\n  to be set to `true` and for its routes to be set up properly. This is\n  configured automatically if Viceroy is called through the Fastly CLI's\n  `fastly compute serve` command.\n\n- Support the simple cache API ([#487](https://github.com/fastly/Viceroy/issues/487)) and core cache API\n\n  The [simple cache API](https://docs.rs/fastly/latest/fastly/cache/simple/index.html) is fully supported in Viceroy.\n  The [core cache API](https://docs.rs/fastly/latest/fastly/cache/core/index.html) is also supported, with the exception of the \"replace\" family of calls ([#495](https://github.com/fastly/Viceroy/issues/495)).\n\n  Both of these use an in-memory store for cached data; restarting Viceroy flushes the cache.\n\n  The HTTP cache layer (readthrough cache) is not supported at this time ([#496](https://github.com/fastly/Viceroy/issues/496)).\n\n- Experimental support for reusable sessions\n\n  The default behavior for Viceroy (and Fastly Compute) is to launch a new WASM instance\n  for each inbound HTTP request.\n\n  This default behavior remains unchanged. However, this release adds experimental\n  hostcalls that allow a WASM instance to receive and respond to additional HTTP requests\n  after the first.\n\n  This is an experimental feature (not yet available through any SDK),\n  and requires an opt-in (using the new hostcalls).\n  Fastly may modify or remove this feature in the future- don't rely on it (yet)!\n\n- Make `SecretStore` public ([#486](https://github.com/fastly/Viceroy/pull/486))\n\n  Make the `SecretStore` type public, so it can be configured in integration tests that use `viceroy-lib`.\n\n## 0.13.0 (2025-04-25)\n\n- Add support for shielding primitives in Viceroy ([#455](https://github.com/fastly/Viceroy/pull/455))\n\n  Shielding definitions can be creating inside `fastly.toml` using the\n  `local-server.shielding_sites` key. These definitions are just a normal TOML\n  dictionary mapping shield sites (e.g., \"pdx-or-us\", \"bfi-wa-us\", etc.) to\n  either the string \"Local\" (meaning that Viceroy should pretend to be operating\n  in that POP), or a dictionary mapping \"unencrypted\" and \"encrypted\" to target\n  URLs (to mimic operating off the shield POP). For example:\n\n  ```\n  [local_server.shielding_sites]\n  \"pdx-or-us\" = \"Local\"\n  \"bfi-wa-us\".unencrypted = \"http://localhost\"\n  \"bfi-wa-us\".encrypted = \"https://localhost\"\n  ```\n\n  This snippet defines two shield POPs: \"pdx-or-us\", which Viceroy should\n  pretend to be running on, and \"bfi-wa-us\", which is remote. If the guest\n  program asks for a backend to \"bfi-wa-us\", Viceroy will use `localhost`\n  as the target server, using HTTPS for encrypted traffic and HTTP for\n  unencrypted traffic.\n\n## 0.12.4 (2025-04-07)\n\n- Add support for `file` entries in KV Stores defined using \"file\"/\"format\" ([#463](https://github.com/fastly/Viceroy/pull/463))\n\n## 0.12.3 (2025-03-28)\n\n- Add `downstream_client_ddos_detected` hostcall stub ([#460](https://github.com/fastly/Viceroy/pull/460))\n- Add support for metadata in local_server.kv_stores ([#459](https://github.com/fastly/Viceroy/pull/459))\n- Add support for the Image Optimizer hostcalls ([#458](https://github.com/fastly/Viceroy/pull/458))\n- Guest Profile sample period configuration support ([#456](https://github.com/fastly/Viceroy/pull/456))\n- Fix cache key type in component interface ([#453](https://github.com/fastly/Viceroy/pull/453))\n- Remove ubuntu-20.04 from CI ([#451](https://github.com/fastly/Viceroy/pull/451))\n- Allow environment variable tests to pass based on validity, rather than their value ([#450](https://github.com/fastly/Viceroy/pull/450))\n- Add `FASTLY_IS_STAGING` environment variable ([#449](https://github.com/fastly/Viceroy/pull/449))\n- Update KV Store key naming restrictions ([#447](https://github.com/fastly/Viceroy/pull/447))\n- Fixes to CI ([#448](https://github.com/fastly/Viceroy/pull/448))\n- Update to 0.11.1 of the Fastly Rust SDK ([#445](https://github.com/fastly/Viceroy/pull/445))\n- Update CI to remove tests for macOS 12 and add tests for macOS 15 ([#436](https://github.com/fastly/Viceroy/pull/436))\n\n## 0.12.2 (2024-12-02)\n\n- Add support for the `on_behalf_of` hostcalls ([#440](https://github.com/fastly/Viceroy/pull/440))\n- Add new `lookup_wait_v2` to fix generation parsing bug ([#439](https://github.com/fastly/Viceroy/pull/439))\n- Add `fastly_acl` hostcalls ([#438](https://github.com/fastly/Viceroy/pull/438))\n\n## 0.12.1 (2024-10-04)\n\n- Stub new HTTP cache hostcalls ([#433](https://github.com/fastly/Viceroy/pull/433))\n- Add support for reading secrets from a JSON file ([#428](https://github.com/fastly/Viceroy/pull/428))\n- Added hostcalls for the new builder api hostcalls ([#427](https://github.com/fastly/Viceroy/pull/427))\n\n## 0.12.0 (2024-09-03)\n\n- Add ReplaceHandle hostcall stubs for upcoming SDK release ([#424](https://github.com/fastly/Viceroy/pull/424))\n- Add keepalive options for dynamic backends ([#423](https://github.com/fastly/Viceroy/pull/423))\n- Fix bug in `inspect` implementation and add a test ([#422](https://github.com/fastly/Viceroy/pull/422))\n- Add the missing adapter calls for new cache operations ([#419](https://github.com/fastly/Viceroy/pull/419))\n- Implement component traits on ComponentCtx ([#421](https://github.com/fastly/Viceroy/pull/421))\n- Split info spans when logging request IDs ([#420](https://github.com/fastly/Viceroy/pull/420))\n- Rename the kv-store interface to object-store in compute.wit ([#415](https://github.com/fastly/Viceroy/pull/415))\n\n## 0.11.0 (2024-08-20)\n\n- Add support for JSON files in `local_server.kv_stores` ([#365](https://github.com/fastly/Viceroy/pull/365))\n- Add `get_vcpu_ms` hostcall ([#412](https://github.com/fastly/Viceroy/pull/412))\n- Add `inspect` hostcall ([#417](https://github.com/fastly/Viceroy/pull/417))\n- Add `downstream_compliance_region` hostcall ([#403](https://github.com/fastly/Viceroy/pull/403))\n- Emit the status code for responses, in addition to other stats ([#416](https://github.com/fastly/Viceroy/pull/416))\n- Update `compute.wit` and the adapter for some api fixes ([#414](https://github.com/fastly/Viceroy/pull/414))\n- Use `mozilla-actions/sccache-action` for caching builds ([#411](https://github.com/fastly/Viceroy/pull/411))\n\n## 0.10.2 (2024-07-22)\n\n- Add support for supplying client certificates in fastly.toml, through the use of the\n  `client_cert_info` table, which must have one of a \"certificate\" or \"certificate_file\"\n  key, as well as one of a \"key\" and \"key_file\" key. The \"_file\" variants can be used to\n  point to certificate/key files on disk, whereas the non-\"_file\" variants should be\n  multi-line string constants in the toml. In all cases, they should be in PEM format.\n- Restore compatibility with older glibc versions in release artifacts\n\n## 0.10.1 (2024-07-11)\n\n- Revert a CI configuration change that inadvertently prevented builds being created for amd64 macOS endpoints ([#405](https://github.com/fastly/Viceroy/pull/405))\n\n## 0.10.0 (2024-07-09)\n\n- Add `get_addr_dest_{ip,port}` hostcalls ([#402](https://github.com/fastly/Viceroy/pull/402))\n- Add `downstream_server_ip_addr` hostcall ([#401](https://github.com/fastly/Viceroy/pull/401))\n- Support `wat` files when adapting core wasm ([#399](https://github.com/fastly/Viceroy/pull/399))\n- Add support for environment variables in the adapter ([#400](https://github.com/fastly/Viceroy/pull/400))\n- Run tests as components ([#396](https://github.com/fastly/Viceroy/pull/396))\n- Remove some unused memory management code in the adapter ([#398](https://github.com/fastly/Viceroy/pull/398))\n- Allow capturing logging endpoint messages ([#397](https://github.com/fastly/Viceroy/pull/397))\n- Support cli args in the adapter ([#394](https://github.com/fastly/Viceroy/pull/394))\n- Rework component testing support to make test updates easier ([#395](https://github.com/fastly/Viceroy/pull/395))\n- Populate the guest cli args ([#393](https://github.com/fastly/Viceroy/pull/393))\n- Update to wasmtime 22.0.0 ([#392](https://github.com/fastly/Viceroy/pull/392))\n- Populate `nwritten_out` when errors occur in config-store::get or dictionary::get ([#389](https://github.com/fastly/Viceroy/pull/389))\n- Switch to using the on-demand allocator, instead of the pooling allocator ([#391](https://github.com/fastly/Viceroy/pull/391))\n- Explicitly test the dictionary host calls in the dictionary fixture ([#390](https://github.com/fastly/Viceroy/pull/390))\n- Enable the config-store-lookup tests ([#387](https://github.com/fastly/Viceroy/pull/387))\n- Run the `request` tests as a component ([#386](https://github.com/fastly/Viceroy/pull/386))\n- Update Ubuntu and macOS runners to latest (and non-EOL) versions ([#388](https://github.com/fastly/Viceroy/pull/388))\n- Fix trap handling when running components ([#382](https://github.com/fastly/Viceroy/pull/382))\n- fix(wiggle_abi): write the result's length, not the guest buffer's ([#385](https://github.com/fastly/Viceroy/pull/385))\n- Add adaptive buffer support for geo + device detection lookups ([#383](https://github.com/fastly/Viceroy/pull/383))\n- Fix buffer-len handling in the component adapter ([#381](https://github.com/fastly/Viceroy/pull/381))\n- Switch to reading dictionaries during the `fastly_dictionary_open` call ([#379](https://github.com/fastly/Viceroy/pull/379))\n- Support adapting core wasm to components ([#374](https://github.com/fastly/Viceroy/pull/374))\n\n## 0.9.7 (2024-05-24)\n\n- Update to wasmtime-21.0.0 ([#369](https://github.com/fastly/Viceroy/pull/369))\n- Initial WebAssembly component support ([#367](https://github.com/fastly/Viceroy/pull/367))\n- Add stubs for new busy-handle hostcalls ([#373](https://github.com/fastly/Viceroy/pull/373))\n\n## 0.9.6 (2024-04-08)\n\n- Return a ValueAbsent for all the downstream-tls related functions instead of a NotAvailable ([#315](https://github.com/fastly/Viceroy/pull/315))\n\n## 0.9.5 (2024-03-15)\n\n- Bug fix: Honor CA certificates when they are supplied, either as part of a dynamic backend\n  definition or as part of a backend defined in fastly.toml. (In the latter case, CA certificates\n  can be added using the \"ca_certificate\" key.) ([#305](https://github.com/fastly/Viceroy/pull/305))\n\n- Consistently use Error::NotAvailable instead of Unsupported ([#349](https://github.com/fastly/Viceroy/pull/349))\n\n## 0.9.4 (2024-02-22)\n\n- Added `delete_async` hostcall for KV stores ([#332](https://github.com/fastly/Viceroy/pull/332))\n- Added `known_length` hostcall for body handles ([#344](https://github.com/fastly/Viceroy/pull/344))\n- Added stubs for new functionality available in production Compute ([#333](https://github.com/fastly/Viceroy/pull/333), [#337](https://github.com/fastly/Viceroy/pull/337), [#344](https://github.com/fastly/Viceroy/pull/344))\n- Fixed inconsistent behavior for not-found geolocation lookups compared to production Compute ([#341](https://github.com/fastly/Viceroy/pull/341))\n\n## 0.9.3 (2023-11-09)\n\n- Renamed Compute@Edge to Compute. ([#328](https://github.com/fastly/Viceroy/pull/328))\n- Added asynchronous versions of the KV store `lookup` and `insert` operations. ([#329](https://github.com/fastly/Viceroy/pull/329))\n- Added support for device detection. ([#330](https://github.com/fastly/Viceroy/pull/330))\n\n## 0.9.2 (2023-10-23)\n\n- Warn instead of fail when certificates can't be loaded ([#325](https://github.com/fastly/Viceroy/pull/325))\n\n- Add support for trailers. Trailer modification calls should be considered experimental,\n  as we finalize interfaces ([#327](https://github.com/fastly/Viceroy/pull/327))\n\n## 0.9.1 (2023-10-09)\n\n- Match the number of memories to the number of core instances ([#322](https://github.com/fastly/Viceroy/pull/322))\n\n## 0.9.0 (2023-10-09)\n\n- Add options to customize behavior of unknown Wasm imports ([#313](https://github.com/fastly/Viceroy/pull/313))\n- Lower Hostcall error log level to DEBUG ([#314](https://github.com/fastly/Viceroy/pull/314))\n- Add perfmap profiling strategy ([#316](https://github.com/fastly/Viceroy/pull/316))\n- Update to wasmtime-13.0.0 ([#317](https://github.com/fastly/Viceroy/pull/317))\n- Revamp profile handling CLI flags ([#318](https://github.com/fastly/Viceroy/pull/318))\n\n## 0.8.1 (2023-09-18)\n\n- Fix a bug in which static backends were marked as GRPC by default ([#311](https://github.com/fastly/Viceroy/pull/311))\n\n## 0.8.0 (2023-09-15)\n\n- Make `viceroy_lib::Error` non-exhaustive\n- Support the gRPC flag for dynamic backends ([#308](https://github.com/fastly/Viceroy/pull/308))\n- Update ABI definitions and stub out some hostcalls ([#307](https://github.com/fastly/Viceroy/pull/307))\n\n## 0.7.0 (2023-08-14)\n\n- Add --profile-guest support to serve mode. ([#301](https://github.com/fastly/Viceroy/pull/301))\n- Use a ResourceLimiter for tracking allocations. ([#300](https://github.com/fastly/Viceroy/pull/300))\n- Support the new mTLS features for dynamic backends, allowing two-way authentication for backend connections. ([#297](https://github.com/fastly/Viceroy/pull/297))\n\n## 0.6.1 (2023-08-03)\n\n- Support the new config store hostcalls. ([#296](https://github.com/fastly/Viceroy/pull/296))\n- Bump to wasmtime-11.0.1 ([#295](https://github.com/fastly/Viceroy/pull/295))\n- Unblock Secret::from_bytes test by upgrading the fastly crate dependency. ([#294](https://github.com/fastly/Viceroy/pull/294))\n- Map Error::UnknownBackend to FastlyStatus::Inval ([#293](https://github.com/fastly/Viceroy/pull/293))\n- When an upstream body is unexpectedly closed, return Httpincomplete ([#290](https://github.com/fastly/Viceroy/pull/290))\n- Error::ValueAbsent should map to FastlyStatus::None, not Inval ([#291](https://github.com/fastly/Viceroy/pull/280))\n- Switch default log level to \"error\", add -v to run ([#288](https://github.com/fastly/Viceroy/pull/288))\n- Update rustls and various dependencies ([#278](https://github.com/fastly/Viceroy/pull/278))\n- Change default port from 7878 to 7676, which is what the Fastly CLI defaults to ([#287](https://github.com/fastly/Viceroy/pull/287))\n\n## 0.6.0 (2023-07-12)\n\n- ⏱️ Add cross-platform ability to profile guest code in run mode ([#280](https://github.com/fastly/Viceroy/pull/280))\n- pin to hyper 0.14.26 for the time being ([#285](https://github.com/fastly/Viceroy/pull/285))\n- 😯 Add support for the new secret from_bytes extension. ([#283](https://github.com/fastly/Viceroy/pull/283))\n- feat: Add a stub for downstream_client_h2_fingerprint ([#277](https://github.com/fastly/Viceroy/pull/277))\n- Fill downstream_client_request_id in ([#282](https://github.com/fastly/Viceroy/pull/282))\n- Bump to wasmtime-10.0.0 ([#279](https://github.com/fastly/Viceroy/pull/279))\n- Add a stub for downstream_client_request_id ([#276](https://github.com/fastly/Viceroy/pull/276))\n-  Fix various warnings ([#271](https://github.com/fastly/Viceroy/pull/271))\n- ⛽ -> ⏲️ Switch from fuel to epoch interruptions. ([#273](https://github.com/fastly/Viceroy/pull/273))\n- Bump wasmtime dependencies to 9.0.1 ([#272](https://github.com/fastly/Viceroy/pull/272))\n- ⏩ none should not be defined in cache_override_tag witx ([#269](https://github.com/fastly/Viceroy/pull/269))\n- in single run mode, keep the response receiver alive during execution ([#270](https://github.com/fastly/Viceroy/pull/270))\n- Return appropriate exit code in run-mode, rather than just 0 or 1 ([#224](https://github.com/fastly/Viceroy/pull/224))\n\n## 0.5.1 (2023-05-17)\n\n-  Update crates and add http_keepalive_mode_set ([#266](https://github.com/fastly/Viceroy/pull/266))\n\n## 0.5.0 (2023-05-11)\n\n- 🚧 Add stubs for Cache API primitives ([#260](https://github.com/fastly/Viceroy/pull/260))\n- Make is_healthy always return Unknown instead of an unsupporte…\n- 🕷️ Rework integration tests to allow parallel test execution ([#257](https://github.com/fastly/Viceroy/pull/257))\n- Add KVStore async lookup ([#253](https://github.com/fastly/Viceroy/pull/253))\n- Update to Wasmtime 8 ([#251](https://github.com/fastly/Viceroy/pull/251))\n- Add documentation explaining how to run rust unit tests w/ viceroy ([#242](https://github.com/fastly/Viceroy/pull/242))\n\n## 0.4.5 (2023-04-13)\n-  Remove validation on config store and dictionary names ([#248](https://github.com/fastly/Viceroy/pull/248))\n\n## 0.4.4 (2023-04-11)\n- feat: Allow local KV Stores to be defined using `[local_server.kv_stores]` ([#245](https://github.com/fastly/Viceroy/pull/245))\n\n## 0.4.3 (2023-04-04)\n- Add the `fastly_backend` module to the wiggle abi ([#243](https://github.com/fastly/Viceroy/pull/243))\n\n## 0.4.2 (2023-03-30)\n- Allow config-stores to be defined using `[local_server.config_stores]` ([#240](https://github.com/fastly/Viceroy/pull/240))\n\n## 0.4.1 (2023-03-23)\n- Add `fastly_backend` interfaces for backend introspection ([#236](https://github.com/fastly/Viceroy/pull/236))\n\n## 0.4.0 (2023-03-17)\n- Add a run-mode that executes the input program once and then exits ([#211](https://github.com/fastly/Viceroy/pull/211))\n- Update to Wasmtime 6.0.0 ([#226](https://github.com/fastly/Viceroy/pull/226))\n- Make object and secret store config names consistent ([#206](https://github.com/fastly/Viceroy/pull/206))\n- Remove dictionary count limit ([#227](https://github.com/fastly/Viceroy/pull/227))\n- Split out run-mode and serve mode into subcommands ([#229](https://github.com/fastly/Viceroy/pull/229))\n\n## 0.3.5 (2023-01-20)\n- Add support for Secret Store ([#210](https://github.com/fastly/Viceroy/pull/210))\n\n## 0.3.4 (2023-01-19)\n- Update to Wasmtime 4.0.0\n  ([#217](https://github.com/fastly/Viceroy/pull/217))\n- Set fixed release build images to improve compatibility of precompiled release artifacts\n  ([#216](https://github.com/fastly/Viceroy/pull/216))\n\n## 0.3.3 (2023-01-18)\n- Support the streaming body `finish()` method introduced in version 0.9.0 of the Rust SDK\n  ([#203](https://github.com/fastly/Viceroy/pull/203))\n- Update to wasmtime 3.0.0 and enable experimental wasi-nn interface\n  ([#209](https://github.com/fastly/Viceroy/pull/209))\n\n## 0.3.2 (2022-11-17)\n- Add geolocation implementation to Viceroy\n  ([#165](https://github.com/fastly/Viceroy/pull/165))\n- Implement async select hostcalls for Viceroy\n  ([#188](https://github.com/fastly/Viceroy/pull/188))\n- Update wasmtime dependency to 2.0\n  ([#194](https://github.com/fastly/Viceroy/pull/194))\n- Return a FastlyStatus::Inval when opening a non-existant object-store\n  ([#196](https://github.com/fastly/Viceroy/pull/196))\n- Add limit exceeded variant to fastly_status witx definition\n  ([#199](https://github.com/fastly/Viceroy/pull/199))\n\n## 0.3.1 (2022-10-11)\n\n- Add stubs for fastly purge\n  ([#184](https://github.com/fastly/Viceroy/pull/184))\n- Add stubs for mTLS information\n  ([#186](https://github.com/fastly/Viceroy/pull/186))\n- Allow to enable wasmtime's profiling support\n  ([#181](https://github.com/fastly/Viceroy/pull/181))\n- Add stubs for `redirect_to_`\n  ([#187](https://github.com/fastly/Viceroy/pull/187))\n\n## 0.3.0 (2022-10-11)\n- Tagged but not released due to invalid metadata added in\n  [#173](https://github.com/fastly/Viceroy/pull/189). See\n  [#189](https://github.com/fastly/Viceroy/pull/189) for more details\n\n## 0.2.15 (2022-08-19)\n\n- Add support for `ObjectStore`\n  ([#167](https://github.com/fastly/Viceroy/pull/167))\n- Add support for dynamic backends\n  ([#163](https://github.com/fastly/Viceroy/pull/163))\n- Extend backend TLS configuration with cert host and SNI\n  ([#168](https://github.com/fastly/Viceroy/pull/168))\n\n## 0.2.14 (2022-05-23)\n\n- Add support for inline TOML dictionaries ([#150](https://github.com/fastly/Viceroy/pull/150))\n\n## 0.2.13 (2022-05-03)\n\n- Add stubs for JA3 hashes and WebSocket upgrades ([#153](https://github.com/fastly/Viceroy/pull/153))\n\n## 0.2.12 (2022-03-08)\n\n- Add stubs for framing header controls, now available on Compute ([#139](https://github.com/fastly/Viceroy/pull/139))\n\n## 0.2.11 (2022-02-15)\n\n- Implement automatic decompression of gzip backend responses ([#125](https://github.com/fastly/Viceroy/pull/125))\n- Remove excess logging for programs that exit with a zero exit code ([#128](https://github.com/fastly/Viceroy/pull/128))\n\n## 0.2.10 (2022-02-08)\n\n- Add telemetry for wall-clock duration ([#121](https://github.com/fastly/Viceroy/pull/121))\n- Bump various runtime limits ([#123](https://github.com/fastly/Viceroy/pull/123))\n\n## 0.2.9 (2022-01-11)\n\n- Do not panic when `auto_decompress_response_set` is called ([#116](https://github.com/fastly/Viceroy/pull/116))\n\n## 0.2.8 (2022-01-07)\n\n- Allow partial CA store to be loaded ([#104](https://github.com/fastly/Viceroy/pull/104))\n- Update ABI and stub out new function ([#113](https://github.com/fastly/Viceroy/pull/104))\n\n## 0.2.7 (2021-12-01)\n\n- Disable ALPN by using rustls more directly ([#100](https://github.com/fastly/Viceroy/pull/100))\n\n## 0.2.6 (2021-11-15)\n\n- Catch interrupt signals ([#85](https://github.com/fastly/Viceroy/pull/85))\n- Include aarch64 tarballs for Linux and macOS ([#88](https://github.com/fastly/Viceroy/pull/88))\n- Align URI and Host header semantics with production Compute ([#90](https://github.com/fastly/Viceroy/pull/90))\n\n## 0.2.5 (2021-10-21)\n\n- Replaced `hyper-tls` with `hyper-rustls`. ([#75](https://github.com/fastly/Viceroy/pull/75))\n- Unknown dictionary items are now logged at debug level. ([#80](https://github.com/fastly/Viceroy/pull/80))\n- Windows releases are now built in CI. ([#82](https://github.com/fastly/Viceroy/pull/82))\n\n## 0.2.4 (2021-09-08)\n\n- Improved error messages when a file could not be read. ([#70](https://github.com/fastly/Viceroy/pull/70))\n- Fixed a bug for dictionary lookups that returned and error rather than `None`. ([#69](https://github.com/fastly/Viceroy/pull/69))\n\n## 0.2.3 (2021-08-23)\n\n### Additions\n- Added the close functionality for `RequestHandle`, `ResponseHandle`,\n  `BodyHandle`, and `StreamingBodyHandle` in the upcoming Rust Compute `0.8.0` SDK\n  release ([#65](https://github.com/fastly/Viceroy/pull/65))\n- Added local dictionary support so that Compute programs that need dictionaries can work in Viceroy ([#61](https://github.com/fastly/Viceroy/pull/61))\n- Added the ability to do host overrides from the TOML configuration ([#48](https://github.com/fastly/Viceroy/pull/48))\n\n### Changes\n- Viceroy now tracks the latest stable Rust which as of this release is 1.54.0\n\n## 0.2.2 (2021-07-15)\n\n### Enhancements\n\n- Renamed `viceroy-cli` package to `viceroy`, in preparation for `cargo install viceroy` ([#41](https://github.com/fastly/Viceroy/pull/41)).\n- Improved UI for traces and errors ([#37](https://github.com/fastly/Viceroy/pull/37)).\n- Increase limit on functions per wasm module ([#33](https://github.com/fastly/Viceroy/pull/33)).\n- Be more flexible with wasm module input, allowing for WAT input as well ([#32](https://github.com/fastly/Viceroy/pull/32)).\n\n### Fixes\n\n- Correctly pull in wasi tokio bindings ([#44](https://github.com/fastly/Viceroy/pull/44)).\n- Correct `--help` output for `--addr` ([#34](https://github.com/fastly/Viceroy/pull/34)).\n\n## 0.2.1 (2021-07-12)\n\n### Fixes\n\n- Changed release artifacts naming format.\n\n## 0.2.0 (2021-07-09)\n\n- Initial release.\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "\n# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n[oss@fastly.com](mailto:oss@fastly.com).\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\n[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].\n\nCommunity Impact Guidelines were inspired by \n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available \nat [https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Viceroy\n\nFirst off thank you for wanting to contribute to making Viceroy better! We\nappreciate you taking time to improve the Compute experience for developers\neverywhere. There are many ways you can contribute that include but aren't\nlimited to documentation, opening issues, issue triage, and code contributions.\nWe'll cover some of the ways you can contribute below, but if you don't see\ninstructions for what you want to do, open up an issue and ask us!\n\n## Table of Contents\n1. Documentation\n1. Feature Requests\n1. Bugs\n1. Issue Triage\n1. Code Contributions\n\n## Documentation\n\nWas something in our documentation unclear? Does something have no documentation,\nbut should? This is a perfect way to make an easy contribution to Viceroy. If\nyou're not sure what the documentation should contain, please open up an issue!\nWe're happy to guide you with the correct information needed or if you already\nknow what needs to be done, open up a PR and we'll review it for you before\nmerging your changes.\n\n## Feature Requests\n\nDo you think there's something Viceroy should have that would make the\nexperience better? Feature requests are a great way to let us know. Before\nopening up a feature request on the issue tracker first make sure that there is\nno currently open issue asking for the same thing. If there's not, open up an\nissue asking for what you want and the motivation behind the change.\n\n## Bugs\n\nSometimes you run into issues and the code is not working properly. If you do\nrun into a bug and you can't figure it out or if you do figure out the bug, open\nup an issue on the issue tracker. Just make sure it's not already an issue that\nhas been filed yet. If you do open up an issue let us know what you expected to\nhappen, what actually happened, what your operating system is, as well as a case\nwe can use to reproduce the issue if you have one!\n\n## Issue Triage\n\nSometimes issues get stale and are no longer an issue, need to be updated, or\nhave been fixed by a PR and were never closed. While we try to stay on top of\nissues and keep the backlog groomed, we are only human and can miss out on\nthings. If you find that an issue can be closed,\n\n## Code Contributions\n\nIf you want to contribute code to Viceroy thank you! A few things before you do\nget started adding a change and open up a PR\n\n1. Make sure there's a tracking issue for your code change. We don't want you to\n   do a lot of work only for us to reject the PR because it's a feature change\n   we won't accept for instance.\n1. Before opening your PR make sure you have tests and things working locally.\n   You can run `make ci` in order to run the tests we run on CI which includes\n   the test suite, `clippy`, and a `cargo fmt` check\n\nIt also helps to understand how we structure Viceroy. Under `cli` is the code\nrelated to setting up and running the Viceroy CLI tool. This includes things\nlike argument parsing, setting up logging, and reading in options. It's fairly\nsmall on purpose as most of the actual logic that runs Viceroy is found under\n`lib`. This contains all the logic for how Viceroy works. You'll most likely\nmake changes here to fix an issue or add functionality!\n\nThanks again for contributing to Viceroy. We really do appreciate you wanting to\nhelp out and make it better!\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"viceroy-lib\"\nversion = \"0.17.1\"\ndescription = \"Viceroy implementation details.\"\nauthors = [\"Fastly\"]\nedition = \"2024\"\nlicense = \"Apache-2.0 WITH LLVM-exception\"\ndocumentation = \"https://docs.rs/viceroy-lib\"\nhomepage = \"https://github.com/fastly/Viceroy\"\nrepository = \"https://github.com/fastly/Viceroy\"\nkeywords = [\"wasm\", \"fastly\"]\ncategories = [\n    \"development-tools\",\n    \"network-programming\",\n    \"simulation\",\n    \"wasm\"\n]\ninclude = [\n    \"CHANGELOG.md\",\n    \"SECURITY.md\",\n    \"src/**/*\",\n    \"wasm_abi/wit/**/*\",\n    \"wasm_abi/compute-at-edge-abi/**/*.witx\",\n    \"wasm_abi/data/*.wasm\",\n]\nrust-version = \"1.95\"\n\n[dependencies]\nanyhow = { workspace = true }\nasync-trait = \"0.1.59\"\nbytes = \"^1.2.1\"\nbytesize = \"^1.1.0\"\ncfg-if = \"^1.0\"\nclap = { workspace = true }\ncranelift-entity = \"^0.123.0\"\nfastly-shared = \"^0.11.5\"\nflate2 = \"^1.0.24\"\nfutures = { workspace = true }\nhttp = \"^0.2.8\"\nhttp-body = \"^0.4.5\"\nhyper = { workspace = true }\nitertools = { workspace = true }\nlazy_static = \"^1.4.0\"\npin-project = { workspace = true }\nregex = \"^1.3.9\"\nrustls = \"^0.21.1\"\nrustls-native-certs = \"^0.6.3\"\nrustls-pemfile = \"^1.0.3\"\nserde = \"^1.0.145\"\nserde_derive = \"^1.0.114\"\nserde_json = { workspace = true }\nthiserror = \"^1.0.37\"\ntokio = { workspace = true }\ntokio-rustls = \"^0.24.1\"\ntoml = \"^0.5.9\"\ntracing = { workspace = true }\ntracing-futures = { workspace = true }\nurl = { workspace = true }\nwasmparser = { workspace = true }\nwasm-encoder = { workspace = true }\n# Disable the \"serde\" feature which we don't need.\nwasm-metadata = { version = \"0.244.0\", default-features = false }\nwit-component = { workspace = true }\nwasmtime = { workspace = true }\nwasmtime-wasi = { workspace = true }\nwasmtime-wasi-io = { workspace = true }\nwasmtime-wasi-nn = { workspace = true }\nwat = { workspace = true }\nwiggle = { workspace = true }\nbase64 = { workspace = true }\nmoka = { version = \"0.12.12\", features = [\"future\"] }\nwalrus = \"0.23.3\"\n\n[dev-dependencies]\nproptest = \"1.6.0\"\nproptest-derive = \"0.5.1\"\ntempfile = \"3.6.0\"\n\n[features]\ndefault = []\ntest-fatalerror-config = []\n\n[workspace]\nmembers = [\n    \"cli\",\n]\nresolver = \"3\"\n\n# Exclude our integration test fixtures, which need to be compiled to wasm\n# (managed by the Makefile)\nexclude = [\n    \"test-fixtures\",\n]\n\n# Specify `cli` as the default workspace member to operate on. This means that\n# commands like `cargo run` will run the CLI binary by default.\n# See: https://doc.rust-lang.org/cargo/reference/workspaces.html#package-selection\ndefault-members = [ \"cli\" ]\n\n[profile.dev]\n# Since some of the integration tests involve compiling Wasm, a little optimization goes a long way\n# toward making the test suite not take forever\nopt-level = 1\n\n[workspace.dependencies]\nanyhow = \"1.0.31\"\nbase64 = \"0.21.2\"\nclap = { version = \"^4.0.18\", features = [\"derive\"] }\nhyper = { version = \"=0.14.26\", features = [\"full\"] }\nitertools = \"0.10.5\"\npin-project = \"1.0.8\"\nrustls = { version = \"0.21.5\", features = [\"dangerous_configuration\"] }\nrustls-pemfile = \"1.0.3\"\nserde_json = \"1.0.59\"\ntokio = { version = \"1.49.0\", features = [\"full\"] }\ntokio-rustls = \"0.24.1\"\ntracing = \"0.1.37\"\ntracing-futures = \"0.2.5\"\nfutures = \"0.3.24\"\nurl = \"2.3.1\"\n\n# Wasmtime dependencies\nwasmtime = { version =  \"39.0.2\", features = [\"call-hook\"] }\nwasmtime-wasi = \"39.0.2\"\nwasmtime-wasi-io = \"39.0.2\"\nwasmtime-wasi-nn = \"39.0.2\"\nwiggle = \"39.0.2\"\nwat = \"1.240.0\"\nwasmparser = \"0.240.0\"\nwasm-encoder = { version = \"0.240.0\", features = [\"wasmparser\"] }\nwit-component = \"0.240.0\"\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM ubuntu:bionic\n\nARG VICEROY_SRC=/Viceroy\n\nENV DEBIAN_FRONTEND=noninteractive\n\nRUN apt-get update \\\n\t&& apt-get install -y --no-install-recommends \\\n\tbuild-essential \\\n\tcurl \\\n\tgit \\\n\tca-certificates \\\n\tpkg-config \\\n\tlibssl-dev\n\n# Setting a consistent LD_LIBRARY_PATH across the entire environment prevents\n# unnecessary Cargo rebuilds.\nENV LD_LIBRARY_PATH=/usr/local/lib\n\n# Install Rust, rustfmt, and the wasm32-wasi cross-compilation target\nRUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y\nENV PATH=/root/.cargo/bin:$PATH\n\nWORKDIR $VICEROY_SRC\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n\n--- LLVM Exceptions to the Apache 2.0 License ----\n\nAs an exception, if, as a result of your compiling your source code, portions\nof this Software are embedded into an Object form of such source code, you\nmay redistribute such embedded portions in such Object form without complying\nwith the conditions of Sections 4(a), 4(b) and 4(d) of the License.\n\nIn addition, if you combine or link compiled forms of this Software with\nsoftware that is licensed under the GPLv2 (\"Combined Software\") and if a\ncourt of competent jurisdiction determines that the patent provision (Section\n3), the indemnity provision (Section 9) or other Section of the License\nconflicts with the conditions of the GPLv2, you may retroactively and\nprospectively choose to deem waived or otherwise exclude such Section(s) of\nthe License, but only in their entirety and only with respect to the Combined\nSoftware.\n\n"
  },
  {
    "path": "Makefile",
    "content": "# Default to using regular `cargo`. CI targets may override this.\nVICEROY_CARGO=cargo\n\n.PHONY: format\nformat:  ## Apply standard formatting with `cargo fmt`.\n\t$(VICEROY_CARGO) fmt\n\tcd wasm_abi/adapter && $(VICEROY_CARGO) fmt\n\n.PHONY: format-check\nformat-check:  ## Check formatting, without updating.\n\t$(VICEROY_CARGO) fmt -- --check\n\n.PHONY: clippy\nclippy:  ## Ask for Clippy lints.\n\t$(VICEROY_CARGO) clippy --all-targets --all-features -- -D warnings\n\n.PHONY: test\ntest: test-crates trap-test  ## Run all tests.\n\n.PHONY: test-crates\ntest-crates: fix-build\n\tRUST_BACKTRACE=1 $(VICEROY_CARGO) test --all\n\n.PHONY: fix-build\nfix-build:\n\tcd test-fixtures && $(VICEROY_CARGO) build --target=wasm32-wasip1\n\n.PHONY: trap-test\ntrap-test: fix-build\n\tcd cli/tests/trap-test && RUST_BACKTRACE=1 $(VICEROY_CARGO) test fatal_error_traps -- --nocapture\n\n# The `trap-test` is its own top-level target for CI in order to achieve better build parallelism.\n.PHONY: trap-test-ci\ntrap-test-ci: VICEROY_CARGO=cargo --locked\ntrap-test-ci: trap-test\n\n.PHONY: ci\nci: VICEROY_CARGO=cargo --locked\nci: format-check test-crates  ## The main CI target; runs all tests except `trap-test`.\n\n.PHONY: clean\nclean:  ## Clean up Cargo outputs and cache.\n\t$(VICEROY_CARGO) clean\n\tcd cli/tests/trap-test/ && $(VICEROY_CARGO) clean\n\n.PHONY: doc\ndoc: ## Open the documentation for the workspace in a browser.\n\t$(VICEROY_CARGO) doc --workspace --open\n\n.PHONY: doc-dev\ndoc-dev: ## Open the documentation for the workspace in a browser, including private items. Useful for development.\n\t$(VICEROY_CARGO) doc --no-deps --document-private-items --workspace --open\n\n\n.PHONY: generate-lockfile\ngenerate-lockfile: ## Run `cargo generate-lockfile` for all of the crates in the project, updating dependencies.\n\t$(VICEROY_CARGO) generate-lockfile\n\t$(VICEROY_CARGO) generate-lockfile --manifest-path=test-fixtures/Cargo.toml\n\t$(VICEROY_CARGO) generate-lockfile --manifest-path=cli/tests/trap-test/Cargo.toml\n\n# Regenerate the adapter, and move it into `wasm_abi/data`\n.PHONY: build-adapter\nbuild-adapter:\n\t# Build the component adapter for adapting the host-call abi to the\n\t# component model. This version uses `--no-default-features` to disable\n\t# the default \"exports\" feature, to build the imports-only \"library\"\n\t# version of the adapter.\n\t( \\\n\t\tcd wasm_abi/adapter && \\\n\t\tcargo build \\\n\t\t\t--package viceroy-component-adapter \\\n\t\t\t--target wasm32-unknown-unknown \\\n\t\t\t--no-default-features \\\n\t\t\t--profile release-library \\\n\t)\n\t# Build the non-shift \"library\" version of the adapter.\n\t( \\\n\t\tcd wasm_abi/adapter && \\\n\t\tcargo build \\\n\t\t\t--package viceroy-component-adapter \\\n\t\t\t--target wasm32-unknown-unknown \\\n\t\t\t--no-default-features \\\n\t\t\t--profile release-library-noshift \\\n\t\t\t--features noshift \\\n\t)\n\n\t# Build the component adapter for adapting the host-call abi to the\n\t# component model. This is the normal version that includes the exports.\n\t( \\\n\t\tcd wasm_abi/adapter && \\\n\t\tcargo build \\\n\t\t\t--package viceroy-component-adapter \\\n\t\t\t--target wasm32-unknown-unknown \\\n\t\t\t--release \\\n\t)\n\n\t# Build the non-shift normal version of the adapter.\n\t( \\\n\t\tcd wasm_abi/adapter && \\\n\t\tcargo build \\\n\t\t\t--package viceroy-component-adapter \\\n\t\t\t--target wasm32-unknown-unknown \\\n\t\t\t--profile release-noshift \\\n\t\t\t--features noshift \\\n\t)\n\n\tcp wasm_abi/adapter/target/wasm32-unknown-unknown/release/viceroy_component_adapter.wasm \\\n\t\twasm_abi/data/viceroy-component-adapter.wasm\n\tcp wasm_abi/adapter/target/wasm32-unknown-unknown/release-noshift/viceroy_component_adapter.wasm \\\n\t\twasm_abi/data/viceroy-component-adapter.noshift.wasm\n\tcp wasm_abi/adapter/target/wasm32-unknown-unknown/release-library/viceroy_component_adapter.wasm \\\n\t\twasm_abi/data/viceroy-component-adapter.library.wasm\n\tcp wasm_abi/adapter/target/wasm32-unknown-unknown/release-library-noshift/viceroy_component_adapter.wasm \\\n\t\twasm_abi/data/viceroy-component-adapter.library.noshift.wasm\n\n\n.PHONY: help\nhelp:  ## Print help text for all documented commands. (Document with a ## comment.)\n\t@grep -E '^[a-zA-Z_-]+:[^#]*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = \":[^#]*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n# Note that we don't sort; targets appear in the order they are in the file.\n# So, put more important targets first (or is it last?)\n\n"
  },
  {
    "path": "README.md",
    "content": "# Viceroy\n\nViceroy provides local testing for developers working with Fastly Compute. It\nallows you to run services written against the Compute APIs on your local\ndevelopment machine, and allows you to configure testing backends for your\nservice to communicate with.\n\nViceroy is normally used through the [Fastly CLI's `fastly compute serve`\ncommand][cli], where it is fully integrated into Compute workflows.\nHowever, it is also a standalone open source tool with its own CLI and a\nRust library that can be embedded into your own testing infrastructure.\n\n[cli]: https://developer.fastly.com/learning/compute/testing/#running-a-local-testing-server\n\n## Installation\n\n### Via the Fastly CLI\n\nAs mentioned above, most users of Compute should do local testing via the\nFastly CLI, rather than working with Viceroy directly. Any [CLI release] of\nversion 0.34 or above supports local testing, and the workflow is documented\n[here][cli].\n\n[CLI release]: https://github.com/fastly/cli/releases\n\n### As a standalone tool from crates.io\n\nTo install Viceroy as a standalone tool, you'll need to first\n[install Rust](https://www.rust-lang.org/tools/install) if you haven't already.\nThen run `cargo install --locked viceroy`, which will download and build the latest\nViceroy release.\n\n## Usage as a library\n\nViceroy can be used as a [Rust library](https://docs.rs/viceroy-lib/). This is useful if you want to run integration tests in the same codebase. We provide a helper method [`handle_request`](https://docs.rs/viceroy-lib/0.2.6/viceroy_lib/struct.ExecuteCtx.html#method.handle_request). Before you build or test your code, we recommend to set the release flag e.g. `cargo test --release` otherwise, the execution will be very slow. This has to do with the Cranelift compiler, which is extremely slow when compiled in debug mode. Besides that, if you use GitHub Actions don't forget to set up a build [cache](https://github.com/actions/cache/blob/main/examples.md#rust---cargo) for Rust. This will speed up your build times a lot.\n\n## Usage as a standalone tool\n\n**NOTE**: the Viceroy standalone CLI has a somewhat different interface from that\nof [the Fastly CLI][cli]. Command-line options below describe the standalone\nViceroy interface.\n\nAfter installation, the `viceroy` command should be available on your path. The\nonly required argument is the path to a compiled `.wasm` blob, which can be\nbuilt by `fastly compute build`. The Fastly CLI should put the blob at\n`bin/main.wasm`. To test the service, you can run:\n\n```\nviceroy bin/main.wasm\n```\n\nThis will start a local server (by default at: `http://127.0.0.1:7676`), which can\nbe used to make requests to your Compute service locally. You can make requests\nby using [curl](https://curl.se/), or you can send a simple GET request by visiting\nthe URL in your web browser.\n\n## Usage as a test runner\nViceroy can also be used as a test runner for running Rust unit tests for Compute applications in the following way:\n\n1. Ensure the `viceroy` command is available in your path\n2. Add the following to your project's `.cargo/config.toml`:\n```\n[build]\ntarget = \"wasm32-wasip1\"\n\n[target.wasm32-wasip1]\nrunner = \"viceroy run -C fastly.toml -- \"\n```\n3. Install [cargo-nextest](https://nexte.st/book/installation.html)\n4. Write your tests that use the fastly crate. For example:\n```Rust\n#[test]\nfn test_using_client_request() {\n    let client_req = fastly::Request::from_client();\n    assert_eq!(client_req.get_method(), Method::GET);\n    assert_eq!(client_req.get_path(), \"/\");\n}\n\n#[test]\nfn test_using_bodies() {\n    let mut body1 = fastly::Body::new();\n    body1.write_str(\"hello, \");\n    let mut body2 = fastly::Body::new();\n    body2.write_str(\"Viceroy!\");\n    body1.append(body2);\n    let appended_str = body1.into_string();\n    assert_eq!(appended_str, \"hello, Viceroy!\");\n}\n\n#[test]\nfn test_a_handler_with_fastly_types() {\n    let req = fastly::Request::get(\"http://example.com/Viceroy\");\n    let resp = some_handler(req).expect(\"request succeeds\");\n    assert_eq!(resp.get_content_type(), Some(TEXT_PLAIN_UTF_8));\n    assert_eq!(resp.into_body_str(), \"hello, /Viceroy!\");\n}\n```\n5. Run your tests with `cargo nextest run`:\n```\n % cargo nextest run\n   Compiling unit-tests-test v0.1.0\n    Finished test [unoptimized + debuginfo] target(s) in 1.16s\n    Starting 3 tests across 1 binaries\n        PASS [   2.106s] unit-tests-test::bin/unit-tests-test tests::test_a_handler_with_fastly_types\n        PASS [   2.225s] unit-tests-test::bin/unit-tests-test tests::test_using_bodies\n        PASS [   2.223s] unit-tests-test::bin/unit-tests-test tests::test_using_client_request\n------------\n     Summary [   2.230s] 3 tests run: 3 passed, 0 skipped\n```\n\nThe reason that `cargo-nextest` is needed rather than just `cargo test` is to allow tests to keep executing if any other test fails. There is no way to recover from a panic in wasm, so test execution would halt as soon as the first test failure occurs. Because of this, we need each test to be executed in its own wasm instance and have the results aggregated to report overall success/failure. cargo-nextest [handles that orchestration for us](https://nexte.st/book/how-it-works.html#the-nextest-model).\n\n## Documentation\n\nSince the Fastly CLI uses Viceroy under the hood, the two share documentation for\neverything other than CLI differences. You can find general documentation for\nlocal testing [here][cli], and documentation about configuring local testing\n[here][toml-docs]. Documentation for Viceroy's CLI can be found via `--help`.\n\n[toml-docs]: https://developer.fastly.com/reference/fastly-toml/#local-server\n\n## Colophon\n\n![Viceroy](doc/logo.png)\n\nThe viceroy is a butterfly whose color and pattern mimics that of the monarch\nbutterfly but is smaller in size.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "## Report a security issue\n\nThe fastly/Viceroy project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [Fastly’s security issue reporting process](https://www.fastly.com/security/report-security-issue).\n\n## Security advisories\n\nRemediation of security vulnerabilities is prioritized by the project team. The project team endeavors to coordinate remediation with third-party stakeholders, and is committed to transparency in the disclosure process. The fastly/Viceroy team announces security issues in release notes as well as GitHub Security Advisories on a best-effort basis.\n\nNote that communications related to security issues in Fastly-maintained OSS as described here are distinct from [Fastly Security Advisories](https://www.fastly.com/security-advisories).\n\n"
  },
  {
    "path": "cli/Cargo.toml",
    "content": "[package]\nname = \"viceroy\"\ndescription = \"Viceroy is a local testing daemon for Fastly Compute.\"\nversion = \"0.17.1\"\nauthors = [\"Fastly\"]\nreadme = \"../README.md\"\nedition = \"2024\"\nlicense = \"Apache-2.0 WITH LLVM-exception\"\ndocumentation = \"https://developer.fastly.com/learning/compute/testing/#running-a-local-testing-server\"\nhomepage = \"https://developer.fastly.com/learning/compute/\"\nrepository = \"https://github.com/fastly/Viceroy\"\nkeywords = [\"wasm\", \"fastly\"]\ncategories = [\n  \"command-line-utilities\",\n  \"development-tools\",\n  \"network-programming\",\n  \"simulation\",\n  \"wasm\"\n]\ninclude = [\n    \"../README.md\",\n    \"../CHANGELOG.md\",\n    \"../SECURITY.md\",\n    \"../doc/logo.png\",\n    \"src/**/*\"\n]\n\n[[bin]]\nname = \"viceroy\"\npath = \"src/main.rs\"\n\n[dependencies]\nanyhow = { workspace = true }\nbase64 = { workspace = true }\nhyper = { workspace = true }\nitertools = { workspace = true }\nserde_json = { workspace = true }\nclap = { workspace = true }\nrustls = { workspace = true }\nrustls-pemfile = { workspace = true }\ntokio = { workspace = true }\ntokio-rustls = { workspace = true }\ntracing = { workspace = true }\ntracing-subscriber = { version = \"^0.3.16\", features = [\"env-filter\", \"fmt\"] }\nviceroy-lib = { path = \"..\", version = \"=0.17.1\" }\nwat = \"^1.0.38\"\nwasmtime = { workspace = true }\nwasmtime-wasi = { workspace = true }\nlibc = \"^0.2.139\"\n\n[dev-dependencies]\nanyhow = { workspace = true }\nfutures = { workspace = true }\nurl = { workspace = true }\ntls-listener = { version = \"^0.7.0\", features = [\"rustls\", \"hyper-h1\", \"tokio-net\", \"rt\"] }\ntempfile = \"3\"\nserde_json = { workspace = true }\n"
  },
  {
    "path": "cli/src/execute_ctx.rs",
    "content": "use crate::opts::SharedArgs;\nuse hyper::{Body, Request, client::Client};\nuse std::io::{self, Stderr, Stdout};\nuse std::sync::Arc;\nuse std::time::Duration;\nuse tokio::time::timeout;\nuse tracing::{Level, Metadata, event};\nuse tracing_subscriber::fmt::writer::MakeWriter;\nuse viceroy_lib::{BackendConnector, ExecuteCtx, GuestProfileConfig, config::FastlyConfig};\n\npub(crate) enum Stdio {\n    Stdout(Stdout),\n    Stderr(Stderr),\n}\n\nimpl io::Write for Stdio {\n    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {\n        match self {\n            Self::Stdout(out) => out.write(buf),\n            Self::Stderr(err) => err.write(buf),\n        }\n    }\n\n    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {\n        match self {\n            Self::Stdout(out) => out.write_all(buf),\n            Self::Stderr(err) => err.write_all(buf),\n        }\n    }\n\n    fn flush(&mut self) -> io::Result<()> {\n        match self {\n            Self::Stdout(out) => out.flush(),\n            Self::Stderr(err) => err.flush(),\n        }\n    }\n}\n\npub(crate) struct StdWriter;\n\nimpl StdWriter {\n    pub(crate) fn new() -> Self {\n        Self {}\n    }\n}\n\nimpl<'a> MakeWriter<'a> for StdWriter {\n    type Writer = Stdio;\n\n    // We need to implement a default behavior so we'll use stdout\n    fn make_writer(&self) -> Self::Writer {\n        Stdio::Stdout(io::stdout())\n    }\n\n    // This is where we can set where we want to send data actually based off\n    // the log level. In this case we want errors to go to stderr as if we used\n    // eprintln and to stdout for everything else.\n    fn make_writer_for(&self, meta: &Metadata<'_>) -> Self::Writer {\n        if meta.level() == &Level::ERROR {\n            Stdio::Stderr(io::stderr())\n        } else {\n            Stdio::Stdout(io::stdout())\n        }\n    }\n}\n\npub(crate) async fn create_execution_context(\n    args: &SharedArgs,\n    check_backends: bool,\n    guest_profile_config: Option<GuestProfileConfig>,\n) -> Result<Arc<ExecuteCtx>, anyhow::Error> {\n    let input = args.input();\n    let ctx = ExecuteCtx::build(\n        input,\n        args.profiling_strategy(),\n        args.wasi_modules(),\n        guest_profile_config,\n        args.unknown_import_behavior(),\n        args.adapt(),\n        args.wasm_features(),\n    )?\n    .with_log_stderr(args.log_stderr())\n    .with_log_stdout(args.log_stdout())\n    .with_local_pushpin_proxy_port(args.local_pushpin_proxy_port());\n\n    let Some(config_path) = args.config_path() else {\n        event!(\n            Level::WARN,\n            \"no configuration provided, invoke with `-C <TOML_FILE>` to provide a configuration\"\n        );\n\n        return Ok(ctx.finish()?);\n    };\n\n    let config = FastlyConfig::from_file(config_path)?;\n    let acls = config.acls();\n    let backends = config.backends();\n    let device_detection = config.device_detection();\n    let geolocation = config.geolocation();\n    let dictionaries = config.dictionaries();\n    let object_stores = config.object_stores();\n    let secret_stores = config.secret_stores();\n    let shielding_sites = config.shielding_sites();\n    let fake_valid_fastly_keys = config.fake_valid_fastly_keys();\n    let backend_names = itertools::join(backends.keys(), \", \");\n\n    let ctx = ctx\n        .with_acls(acls.clone())\n        .with_backends(backends.clone())\n        .with_device_detection(device_detection.clone())\n        .with_geolocation(geolocation.clone())\n        .with_dictionaries(dictionaries.clone())\n        .with_object_stores(object_stores.clone())\n        .with_secret_stores(secret_stores.clone())\n        .with_shielding_sites(shielding_sites.clone())\n        .with_fake_valid_fastly_keys(fake_valid_fastly_keys.clone())\n        .with_config_path(config_path.into())\n        .finish()?;\n\n    if backend_names.is_empty() {\n        event!(\n            Level::WARN,\n            \"no backend definitions found in {}\",\n            config_path.display()\n        );\n    }\n\n    if check_backends {\n        for (name, backend) in backends.iter() {\n            let client = Client::builder().build(BackendConnector::new(\n                backend.clone(),\n                ctx.tls_config().clone(),\n            ));\n            let req = Request::get(&backend.uri).body(Body::empty()).unwrap();\n\n            event!(Level::INFO, \"checking if backend '{}' is up\", name);\n            match timeout(Duration::from_secs(5), client.request(req)).await {\n                // In the case that we don't time out but we have an error, we\n                // check that it's specifically a connection error as this is\n                // the only one that happens if the server is not up.\n                //\n                // We can't combine this with the case above due to needing the\n                // inner error to check if it's a connection error. The type\n                // checker complains about it.\n                Ok(Err(ref e)) if e.is_connect() => event!(\n                    Level::WARN,\n                    \"backend '{}' on '{}' is not up right now\",\n                    name,\n                    backend.uri\n                ),\n                // In the case we timeout we assume the backend is not up as 5\n                // seconds to do a simple get should be enough for a healthy\n                // service\n                Err(_) => event!(\n                    Level::WARN,\n                    \"backend '{}' on '{}' is not up right now\",\n                    name,\n                    backend.uri\n                ),\n                Ok(_) => event!(Level::INFO, \"backend '{}' is up\", name),\n            }\n        }\n    }\n\n    Ok(ctx)\n}\n"
  },
  {
    "path": "cli/src/main.rs",
    "content": "//! Fastly's local testing daemon for Compute.\n\n// When building the project in release mode:\n//   (1): Promote warnings into errors.\n//   (2): Deny broken documentation links.\n//   (3): Deny invalid codeblock attributes in documentation.\n//   (4): Promote warnings in examples into errors, except for unused variables.\n#![cfg_attr(not(debug_assertions), deny(warnings))]\n#![cfg_attr(not(debug_assertions), deny(clippy::all))]\n#![cfg_attr(not(debug_assertions), deny(broken_intra_doc_links))]\n#![cfg_attr(not(debug_assertions), deny(invalid_codeblock_attributes))]\n#![cfg_attr(not(debug_assertions), doc(test(attr(deny(warnings)))))]\n#![cfg_attr(not(debug_assertions), doc(test(attr(allow(dead_code)))))]\n#![cfg_attr(not(debug_assertions), doc(test(attr(allow(unused_variables)))))]\n\nmod execute_ctx;\nmod opts;\nmod subcommands;\n\nuse {\n    crate::execute_ctx::*,\n    crate::opts::*,\n    clap::Parser,\n    std::env,\n    std::process::ExitCode,\n    tracing::{Level, event},\n    tracing_subscriber::{FmtSubscriber, filter::EnvFilter},\n};\n\n#[tokio::main]\nasync fn main() -> ExitCode {\n    // Parse the command-line options, exiting if there are any errors\n    let opts = Opts::parse();\n    let cmd = opts.command.unwrap_or(Commands::Serve(opts.serve));\n    match cmd {\n        Commands::Run(run_args) => subcommands::run::exec(run_args).await,\n        Commands::Serve(serve_args) => subcommands::serve::exec(serve_args).await,\n        Commands::Adapt(adapt_args) => subcommands::adapt::exec(adapt_args),\n    }\n}\n\nfn install_tracing_subscriber(verbosity: u8) {\n    // Default to whatever a user provides, but if not set logging to work for\n    // viceroy and viceroy-lib so that they can have output in the terminal\n    if env::var(\"RUST_LOG\").ok().is_none() {\n        // SAFETY: We are called early in `main` when there are no other\n        // threads created yet.\n        unsafe {\n            match verbosity {\n                0 => env::set_var(\"RUST_LOG\", \"viceroy=error,viceroy-lib=error\"),\n                1 => env::set_var(\"RUST_LOG\", \"viceroy=info,viceroy-lib=info\"),\n                2 => env::set_var(\"RUST_LOG\", \"viceroy=debug,viceroy-lib=debug\"),\n                _ => env::set_var(\"RUST_LOG\", \"viceroy=trace,viceroy-lib=trace\"),\n            }\n        }\n    }\n\n    // Build a subscriber, using the default `RUST_LOG` environment variable for our filter.\n    let builder = FmtSubscriber::builder()\n        .with_writer(StdWriter::new())\n        .with_env_filter(EnvFilter::from_default_env())\n        .with_target(false);\n\n    match env::var(\"RUST_LOG_PRETTY\") {\n        // If the `RUST_LOG_PRETTY` environment variable is set to \"true\", we should emit logs in a\n        // pretty, human-readable output format.\n        Ok(s) if s == \"true\" => builder\n            .pretty()\n            // Show levels, because ANSI escape sequences are normally used to indicate this.\n            .with_level(true)\n            .init(),\n        // Otherwise, we should install the subscriber without any further additions.\n        _ => builder.with_ansi(false).init(),\n    }\n    event!(\n        Level::DEBUG,\n        \"RUST_LOG set to '{}'\",\n        env::var(\"RUST_LOG\").unwrap_or_else(|_| String::from(\"<Could not get env>\"))\n    );\n}\n"
  },
  {
    "path": "cli/src/opts.rs",
    "content": "//! Command line arguments.\n\nuse std::time::Duration;\n\nuse viceroy_lib::{GuestProfileConfig, config::UnknownImportBehavior};\n\nuse {\n    clap::{Args, Parser, Subcommand, ValueEnum},\n    std::net::{IpAddr, Ipv4Addr},\n    std::{\n        collections::HashSet,\n        net::SocketAddr,\n        path::{Path, PathBuf},\n    },\n    viceroy_lib::{Error, ProfilingStrategy, config::ExperimentalModule},\n    wasmtime::WasmFeatures,\n};\n\n// Command-line arguments for the Viceroy CLI.\n//\n// This struct is used to derive a command-line argument parser. See the\n// [clap](https://docs.rs/clap/latest/clap/) documentation for more information.\n//\n// Note that the doc comment below is used as descriptive text in the `--help` output.\n/// Viceroy is a local testing daemon for Compute.\n#[derive(Parser, Debug)]\n#[command(name = \"viceroy\", author, version, about)]\n#[command(propagate_version = true)]\n#[command(args_conflicts_with_subcommands = true)]\npub struct Opts {\n    #[command(subcommand)]\n    pub command: Option<Commands>,\n\n    #[command(flatten)]\n    pub serve: ServeArgs,\n}\n\n#[derive(Subcommand, Debug, Clone)]\npub enum Commands {\n    /// Run the wasm in a Viceroy server. This is the default if no subcommand\n    /// is given.\n    Serve(ServeArgs),\n\n    /// Run the input wasm once and then exit.\n    Run(RunArgs),\n\n    /// Adapt core wasm to a component.\n    Adapt(AdaptArgs),\n}\n\n#[derive(Debug, Args, Clone)]\npub struct ServeArgs {\n    /// The IP address that the service should be bound to.\n    #[arg(long = \"addr\")]\n    socket_addr: Option<SocketAddr>,\n\n    #[command(flatten)]\n    shared: SharedArgs,\n}\n\n#[derive(Args, Debug, Clone)]\npub struct RunArgs {\n    #[command(flatten)]\n    shared: SharedArgs,\n\n    /// Args to pass along to the binary being executed.\n    #[arg(trailing_var_arg = true, allow_hyphen_values = true)]\n    wasm_args: Vec<String>,\n}\n\n#[derive(Args, Debug, Clone)]\npub struct SharedArgs {\n    /// The path to the service's Wasm module.\n    #[arg(value_parser = check_module, required=true)]\n    input: Option<PathBuf>,\n    /// The path to a TOML file containing `local_server` configuration.\n    #[arg(short = 'C', long = \"config\")]\n    config_path: Option<PathBuf>,\n    /// Whether to treat stdout as a logging endpoint\n    #[arg(long = \"log-stdout\", default_value = \"false\")]\n    log_stdout: bool,\n    /// Whether to treat stderr as a logging endpoint\n    #[arg(long = \"log-stderr\", default_value = \"false\")]\n    log_stderr: bool,\n    /// Profiling strategy (valid options are: perfmap, jitdump, vtune, guest)\n    ///\n    /// The perfmap, jitdump, and vtune profiling strategies integrate Viceroy\n    /// with external profilers such as `perf`.\n    ///\n    /// The guest profiling strategy enables in-process sampling. By default,\n    /// when Viceroy is running as a server it will write the captured\n    /// per-request profiles to the `guest-profiles` directory, and as a test\n    /// runner it will write the captured profile to the `guest-profile.json`\n    /// file. These profiles can be viewed at https://profiler.firefox.com/.\n    ///\n    /// The `guest` option can be additionally configured as:\n    ///\n    ///     --profile=guest[,path[,sample]]\n    ///\n    /// where `path` is the directory or filename to write the profile(s) to and\n    /// `sample` is the duration between profiler samples (default 50μs). Time\n    /// units supported are \"s\" (seconds), \"ms\" (milliseconds\"), \"us\"/\"μs\"\n    /// (microseconds), and \"ns\" (nanoseconds).\n    #[arg(long = \"profile\", value_name = \"STRATEGY\", value_parser = check_wasmtime_profiler_mode)]\n    profile: Option<Profile>,\n    /// Port running local Pushpin proxy. If not provided, Pushpin functionality\n    /// is disabled.\n    #[arg(long = \"local-pushpin-proxy-port\")]\n    local_pushpin_proxy_port: Option<u16>,\n    /// Set of experimental WASI modules to link against.\n    #[arg(value_enum, long = \"experimental_modules\", required = false)]\n    experimental_modules: Vec<ExperimentalModuleArg>,\n    /// Set the behavior for unknown imports.\n    ///\n    /// Note that if a program only works with a non-default setting for this flag, it is unlikely\n    /// to be publishable to Fastly.\n    #[arg(long = \"unknown-import-behavior\", value_enum, default_value_t = UnknownImportBehavior::LinkError)]\n    unknown_import_behavior: UnknownImportBehavior,\n    /// Verbosity of logs for Viceroy. `-v` sets the log level to INFO,\n    /// `-vv` to DEBUG, and `-vvv` to TRACE. This option will not take\n    /// effect if you set RUST_LOG to a value before starting Viceroy\n    #[arg(short = 'v', action = clap::ArgAction::Count)]\n    verbosity: u8,\n    /// Whether or not to automatically adapt core-wasm modules to\n    /// components before running them.\n    #[arg(long = \"adapt\")]\n    adapt: bool,\n    /// Enable the Wasm Exception Handling feature.\n    #[arg(long)]\n    wasm_exceptions: bool,\n    /// Enable the Wasm GC feature.\n    #[arg(long)]\n    wasm_gc: bool,\n    /// Enable component-model GC integration.\n    #[arg(long)]\n    wasm_cm_gc: bool,\n}\n\n#[derive(Debug, Clone)]\nenum Profile {\n    Native(ProfilingStrategy),\n    Guest {\n        path: Option<String>,\n        sample_period: Option<Duration>,\n    },\n}\n\nimpl ServeArgs {\n    /// The address that the service should be bound to.\n    pub fn addr(&self) -> SocketAddr {\n        self.socket_addr\n            .unwrap_or_else(|| SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7676))\n    }\n\n    pub fn shared(&self) -> &SharedArgs {\n        &self.shared\n    }\n}\n\nimpl RunArgs {\n    /// The arguments to pass to the underlying binary when run_mode=true\n    pub fn wasm_args(&self) -> &Vec<String> {\n        &self.wasm_args\n    }\n\n    pub fn shared(&self) -> &SharedArgs {\n        &self.shared\n    }\n}\n\nimpl SharedArgs {\n    /// The path to the service's Wasm binary.\n    pub fn input(&self) -> PathBuf {\n        self.input.as_ref().unwrap().clone()\n    }\n\n    /// The path to a `local_server` configuration file.\n    pub fn config_path(&self) -> Option<&Path> {\n        self.config_path.as_deref()\n    }\n\n    /// Whether to treat stdout as a logging endpoint\n    pub fn log_stdout(&self) -> bool {\n        self.log_stdout\n    }\n\n    /// Whether to treat stderr as a logging endpoint\n    pub fn log_stderr(&self) -> bool {\n        self.log_stderr\n    }\n\n    /// Whether to enable wasmtime's builtin profiler.\n    pub fn profiling_strategy(&self) -> ProfilingStrategy {\n        match self.profile {\n            Some(Profile::Native(s)) => s,\n            _ => ProfilingStrategy::None,\n        }\n    }\n\n    /// Port running local Pushpin proxy.\n    pub fn local_pushpin_proxy_port(&self) -> Option<u16> {\n        self.local_pushpin_proxy_port\n    }\n\n    /// Configuration for guest profiling if enabled\n    pub fn guest_profile_config(&self) -> Option<GuestProfileConfig> {\n        if let Some(Profile::Guest {\n            path,\n            sample_period,\n        }) = &self.profile\n        {\n            Some(GuestProfileConfig {\n                path: PathBuf::from(\n                    path.as_ref()\n                        .map(|p| p.as_str())\n                        .unwrap_or(\"guest-profiles\"),\n                ),\n                sample_period: sample_period.unwrap_or_else(|| Duration::from_micros(50)),\n            })\n        } else {\n            None\n        }\n    }\n\n    /// Set of experimental wasi modules to link against.\n    pub fn wasi_modules(&self) -> HashSet<ExperimentalModule> {\n        self.experimental_modules.iter().map(|x| x.into()).collect()\n    }\n\n    /// Unknown import behavior\n    pub fn unknown_import_behavior(&self) -> UnknownImportBehavior {\n        self.unknown_import_behavior\n    }\n\n    /// Verbosity of logs for Viceroy. `-v` sets the log level to DEBUG and\n    /// `-vv` to TRACE. This option will not take effect if you set RUST_LOG\n    /// to a value before starting Viceroy\n    pub fn verbosity(&self) -> u8 {\n        self.verbosity\n    }\n\n    pub fn adapt(&self) -> bool {\n        self.adapt\n    }\n\n    pub fn wasm_features(&self) -> WasmFeatures {\n        let mut wasm_features = WasmFeatures::default();\n        if self.wasm_exceptions {\n            wasm_features.insert(WasmFeatures::EXCEPTIONS);\n        }\n        if self.wasm_gc {\n            wasm_features.insert(WasmFeatures::GC);\n        }\n        if self.wasm_cm_gc {\n            wasm_features.insert(WasmFeatures::CM_GC);\n        }\n        wasm_features\n    }\n}\n\n#[derive(Args, Debug, Clone)]\npub struct AdaptArgs {\n    /// The path to the Wasm module to adapt.\n    #[arg(value_parser = check_module, required=true)]\n    input: PathBuf,\n\n    /// The output name\n    #[arg(short = 'o', long = \"output\")]\n    output: Option<PathBuf>,\n\n    /// Verbosity of logs for Viceroy. `-v` sets the log level to INFO,\n    /// `-vv` to DEBUG, and `-vvv` to TRACE. This option will not take\n    /// effect if you set RUST_LOG to a value before starting Viceroy\n    #[arg(short = 'v', action = clap::ArgAction::Count)]\n    verbosity: u8,\n}\n\nimpl AdaptArgs {\n    pub(crate) fn input(&self) -> PathBuf {\n        self.input.clone()\n    }\n\n    pub(crate) fn output(&self) -> PathBuf {\n        if let Some(output) = self.output.as_ref() {\n            return output.clone();\n        }\n\n        let mut output = PathBuf::from(self.input.file_name().expect(\"input filename\"));\n        output.set_extension(\"component.wasm\");\n        output\n    }\n\n    /// Verbosity of logs for Viceroy. `-v` sets the log level to DEBUG and\n    /// `-vv` to TRACE. This option will not take effect if you set RUST_LOG\n    /// to a value before starting Viceroy\n    pub fn verbosity(&self) -> u8 {\n        self.verbosity\n    }\n}\n\n/// Enum of available (experimental) wasi modules\n#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Hash)]\npub enum ExperimentalModuleArg {\n    WasiNn,\n}\n\nimpl From<ExperimentalModuleArg> for ExperimentalModule {\n    fn from(arg: ExperimentalModuleArg) -> ExperimentalModule {\n        match arg {\n            ExperimentalModuleArg::WasiNn => ExperimentalModule::WasiNn,\n        }\n    }\n}\n\nimpl From<&ExperimentalModuleArg> for ExperimentalModule {\n    fn from(arg: &ExperimentalModuleArg) -> ExperimentalModule {\n        match arg {\n            ExperimentalModuleArg::WasiNn => ExperimentalModule::WasiNn,\n        }\n    }\n}\n\nimpl From<ExperimentalModule> for ExperimentalModuleArg {\n    fn from(module: ExperimentalModule) -> ExperimentalModuleArg {\n        match module {\n            ExperimentalModule::WasiNn => ExperimentalModuleArg::WasiNn,\n        }\n    }\n}\n\nimpl From<&ExperimentalModule> for ExperimentalModuleArg {\n    fn from(module: &ExperimentalModule) -> ExperimentalModuleArg {\n        match module {\n            ExperimentalModule::WasiNn => ExperimentalModuleArg::WasiNn,\n        }\n    }\n}\n\n/// A parsing function used by [`Opts`][opts] to check that the input is a valid Wasm module in\n/// binary or text format.\n///\n/// [opts]: struct.Opts.html\nfn check_module(s: &str) -> Result<PathBuf, Error> {\n    let path = PathBuf::from(s);\n    let contents = std::fs::read(&path)?;\n    match wat::parse_bytes(&contents) {\n        Ok(_) => Ok(path),\n        _ => Err(Error::FileFormat),\n    }\n}\n\n/// Parse a string as a duration\n///\n/// This implementation is mostly borrowed from the wasmtime cli\nfn parse_profile_sample_duration(s: &str) -> Result<Duration, Error> {\n    // assume an integer without a unit specified is a number of seconds ...\n    if let Ok(val) = s.parse() {\n        return Ok(Duration::from_secs(val));\n    }\n\n    if let Some(num) = s.strip_suffix(\"s\")\n        && let Ok(val) = num.parse()\n    {\n        return Ok(Duration::from_secs(val));\n    }\n    if let Some(num) = s.strip_suffix(\"ms\")\n        && let Ok(val) = num.parse()\n    {\n        return Ok(Duration::from_millis(val));\n    }\n    if let Some(num) = s.strip_suffix(\"us\").or(s.strip_suffix(\"μs\"))\n        && let Ok(val) = num.parse()\n    {\n        return Ok(Duration::from_micros(val));\n    }\n    if let Some(num) = s.strip_suffix(\"ns\")\n        && let Ok(val) = num.parse()\n    {\n        return Ok(Duration::from_nanos(val));\n    }\n\n    Err(Error::ProfilingStrategy)\n}\n\n/// A parsing function used by [`Opts`][opts] to check that the input is valid wasmtime's profiling strategy.\n///\n/// [opts]: struct.Opts.html\nfn check_wasmtime_profiler_mode(s: &str) -> Result<Profile, Error> {\n    let parts = s.split(',').collect::<Vec<_>>();\n    match &parts[..] {\n        [\"jitdump\"] => Ok(Profile::Native(ProfilingStrategy::JitDump)),\n        [\"perfmap\"] => Ok(Profile::Native(ProfilingStrategy::PerfMap)),\n        [\"vtune\"] => Ok(Profile::Native(ProfilingStrategy::VTune)),\n        [\"guest\"] => Ok(Profile::Guest {\n            path: None,\n            sample_period: None,\n        }),\n        [\"guest\", path] => Ok(Profile::Guest {\n            path: Some(path.to_string()),\n            sample_period: None,\n        }),\n        [\"guest\", path, sample_period] => Ok(Profile::Guest {\n            path: path.to_string().into(),\n            sample_period: Some(parse_profile_sample_duration(sample_period)?),\n        }),\n        _ => Err(Error::ProfilingStrategy),\n    }\n}\n\n/// A collection of unit tests for our CLI argument parsing.\n///\n/// Note: When using [`Clap::try_parse_from`][from] to test how command line arguments are\n/// parsed, note that the first argument will be parsed as the binary name. `dummy-program-name` is\n/// used to highlight that this argument is ignored.\n///\n/// [from]: https://docs.rs/clap/latest/clap/trait.Parser.html#method.try_parse_from\n#[cfg(test)]\nmod opts_tests {\n    use {\n        super::{Commands, Opts},\n        clap::{Parser, error::ErrorKind},\n        std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},\n        std::path::PathBuf,\n    };\n\n    fn test_file(name: &str) -> String {\n        let mut path = PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"))\n            .join(\"tests\")\n            .join(\"wasm\");\n        path.push(name);\n        assert!(path.exists(), \"test file does not exist\");\n        path.into_os_string().into_string().unwrap()\n    }\n\n    /// A small type alias for test results, with a boxed error type.\n    type TestResult = Result<(), anyhow::Error>;\n\n    /// Test that the default address works as expected.\n    #[test]\n    fn default_addr_works() -> TestResult {\n        let empty_args = &[\"dummy-program-name\", &test_file(\"minimal.wat\")];\n        let opts = Opts::try_parse_from(empty_args)?;\n        let cmd = opts.command.unwrap_or(Commands::Serve(opts.serve));\n        let expected = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7676);\n        if let Commands::Serve(serve_args) = cmd {\n            assert_eq!(serve_args.addr(), expected);\n        }\n        Ok(())\n    }\n\n    /// Test that an `--addr` value with an invalid IPv4 address is rejected.\n    #[test]\n    fn invalid_addrs_are_rejected() -> TestResult {\n        let args_with_bad_addr = &[\n            \"dummy-program-name\",\n            \"--addr\",\n            \"999.0.0.1:7676\",\n            &test_file(\"minimal.wat\"),\n        ];\n        match Opts::try_parse_from(args_with_bad_addr) {\n            Err(err)\n                if err.kind() == ErrorKind::ValueValidation\n                    && (err.to_string().contains(\"invalid socket address syntax\")\n                        || err.to_string().contains(\"invalid IP address syntax\")) =>\n            {\n                Ok(())\n            }\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// IPv6 addresses are supported. Test that they are accepted.\n    #[test]\n    fn ipv6_addrs_are_accepted() -> TestResult {\n        let args_with_ipv6_addr = &[\n            \"dummy-program-name\",\n            \"--addr\",\n            \"[::1]:7676\",\n            &test_file(\"minimal.wat\"),\n        ];\n        let opts = Opts::try_parse_from(args_with_ipv6_addr)?;\n        let cmd = opts.command.unwrap_or(Commands::Serve(opts.serve));\n        let addr_v6 = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));\n        let expected = SocketAddr::new(addr_v6, 7676);\n        if let Commands::Serve(serve_args) = cmd {\n            assert_eq!(serve_args.addr(), expected);\n        }\n        Ok(())\n    }\n\n    /// Test that a nonexistent file is rejected properly.\n    #[test]\n    fn nonexistent_file_is_rejected() -> TestResult {\n        let args_with_nonexistent_file = &[\"dummy-program-name\", \"path/to/a/nonexistent/file\"];\n        match Opts::try_parse_from(args_with_nonexistent_file) {\n            Err(err)\n                if err.kind() == ErrorKind::ValueValidation\n                    && (err.to_string().contains(\"No such file or directory\")\n                        || err.to_string().contains(\"cannot find the path specified\")) =>\n            {\n                Ok(())\n            }\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that an invalid file is rejected.\n    #[test]\n    fn invalid_file_is_rejected() -> TestResult {\n        let args_with_invalid_file = &[\"dummy-program-name\", &test_file(\"invalid.wat\")];\n        let expected_msg = format!(\"{}\", viceroy_lib::Error::FileFormat);\n        match Opts::try_parse_from(args_with_invalid_file) {\n            Err(err)\n                if err.kind() == ErrorKind::ValueValidation\n                    && err.to_string().contains(&expected_msg) =>\n            {\n                Ok(())\n            }\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that a Wasm module in text format is accepted.\n    #[test]\n    fn text_format_is_accepted() -> TestResult {\n        let args = &[\"dummy-program-name\", &test_file(\"minimal.wat\")];\n        match Opts::try_parse_from(args) {\n            Ok(_) => Ok(()),\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that a Wasm module in binary format is accepted.\n    #[test]\n    fn binary_format_is_accepted() -> TestResult {\n        let args = &[\"dummy-program-name\", &test_file(\"minimal.wasm\")];\n        match Opts::try_parse_from(args) {\n            Ok(_) => Ok(()),\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that wasmtime's jitdump profiling strategy is accepted.\n    #[test]\n    fn wasmtime_profiling_strategy_jitdump_is_accepted() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"--profile\",\n            \"jitdump\",\n            &test_file(\"minimal.wat\"),\n        ];\n        match Opts::try_parse_from(args) {\n            Ok(_) => Ok(()),\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that wasmtime's VTune profiling strategy is accepted.\n    #[test]\n    fn wasmtime_profiling_strategy_vtune_is_accepted() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"--profile\",\n            \"vtune\",\n            &test_file(\"minimal.wat\"),\n        ];\n        match Opts::try_parse_from(args) {\n            Ok(_) => Ok(()),\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that wasmtime's PerfMap profiling strategy is accepted.\n    #[test]\n    fn wasmtime_profiling_strategy_perfmap_is_accepted() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"--profile\",\n            \"perfmap\",\n            &test_file(\"minimal.wat\"),\n        ];\n        match Opts::try_parse_from(args) {\n            Ok(_) => Ok(()),\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that wasmtime's guest profiling strategy without path is accepted.\n    #[test]\n    fn wasmtime_profiling_strategy_guest_without_path_is_accepted() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"--profile\",\n            \"guest\",\n            &test_file(\"minimal.wat\"),\n        ];\n        match Opts::try_parse_from(args) {\n            Ok(_) => Ok(()),\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that wasmtime's guest profiling strategy with path is accepted.\n    #[test]\n    fn wasmtime_profiling_strategy_guest_with_path_is_accepted() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"--profile\",\n            \"guest,/some/path\",\n            &test_file(\"minimal.wat\"),\n        ];\n        match Opts::try_parse_from(args) {\n            Ok(_) => Ok(()),\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    #[test]\n    fn wasmtime_profiling_strategy_guest_with_path_and_period_is_accepted() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"--profile\",\n            \"guest,/some/path,250ns\",\n            &test_file(\"minimal.wat\"),\n        ];\n        match Opts::try_parse_from(args) {\n            Ok(_) => Ok(()),\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Test that an invalid wasmtime's profiling strategy rejected.\n    #[test]\n    fn invalid_wasmtime_profiling_strategy_is_rejected() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"--profile\",\n            \"invalid_profiling_strategy\",\n            &test_file(\"minimal.wat\"),\n        ];\n        match Opts::try_parse_from(args) {\n            Ok(_) => panic!(\"unexpected result\"),\n            Err(_) => Ok(()),\n        }\n    }\n\n    /// Test that trailing arguments are collected successfully\n    #[test]\n    fn trailing_args_are_collected_in_run_mode() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"run\",\n            &test_file(\"minimal.wat\"),\n            \"--\",\n            \"--trailing-arg\",\n            \"--trailing-arg-2\",\n        ];\n        let opts = Opts::try_parse_from(args)?;\n        let cmd = opts.command.unwrap_or(Commands::Serve(opts.serve));\n        if let Commands::Run(run_args) = cmd {\n            assert_eq!(\n                run_args.wasm_args(),\n                &[\"--trailing-arg\", \"--trailing-arg-2\"]\n            );\n        }\n        Ok(())\n    }\n\n    /// Input is still accepted after double-dash. This is how the input will be\n    /// passed by cargo nextest if using Viceroy in run-mode to run tests\n    #[test]\n    fn input_accepted_after_double_dash() -> TestResult {\n        let args = &[\n            \"dummy-program-name\",\n            \"run\",\n            \"--\",\n            &test_file(\"minimal.wat\"),\n            \"--trailing-arg\",\n            \"--trailing-arg-2\",\n        ];\n        let opts = match Opts::try_parse_from(args) {\n            Ok(opts) => opts,\n            res => panic!(\"unexpected result: {:?}\", res),\n        };\n        let cmd = opts.command.unwrap_or(Commands::Serve(opts.serve));\n        if let Commands::Run(run_args) = cmd {\n            assert_eq!(\n                run_args.shared.input().to_str().unwrap(),\n                &test_file(\"minimal.wat\")\n            );\n            assert_eq!(\n                run_args.wasm_args(),\n                &[\"--trailing-arg\", \"--trailing-arg-2\"]\n            );\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "cli/src/subcommands/adapt.rs",
    "content": "use crate::install_tracing_subscriber;\nuse crate::opts::AdaptArgs;\nuse std::process::ExitCode;\nuse tracing::{Level, event};\n\npub(crate) fn exec(adapt_args: AdaptArgs) -> ExitCode {\n    install_tracing_subscriber(adapt_args.verbosity());\n    let input = adapt_args.input();\n    let output = adapt_args.output();\n    let bytes = match std::fs::read(&input) {\n        Ok(bytes) => bytes,\n        Err(_) => {\n            event!(\n                Level::ERROR,\n                \"Failed to read module from: {}\",\n                input.display()\n            );\n            return ExitCode::FAILURE;\n        }\n    };\n\n    if viceroy_lib::adapt::is_component(&bytes) {\n        event!(\n            Level::ERROR,\n            \"File is already a component: {}\",\n            input.display()\n        );\n        return ExitCode::FAILURE;\n    }\n\n    let is_wat = input.extension().map(|str| str == \"wat\").unwrap_or(false);\n\n    let module = if is_wat {\n        let text = match String::from_utf8(bytes) {\n            Ok(module) => module,\n            Err(e) => {\n                event!(Level::ERROR, \"Failed to parse wat: {e:?}\");\n                return ExitCode::FAILURE;\n            }\n        };\n\n        match viceroy_lib::adapt::adapt_wat(&text) {\n            Ok(module) => module,\n            Err(e) => {\n                event!(Level::ERROR, \"Failed to adapt wat: {e:?}\");\n                return ExitCode::FAILURE;\n            }\n        }\n    } else {\n        match viceroy_lib::adapt::adapt_bytes(&bytes) {\n            Ok(module) => module,\n            Err(e) => {\n                event!(Level::ERROR, \"Failed to adapt module: {e:?}\");\n                return ExitCode::FAILURE;\n            }\n        }\n    };\n\n    event!(Level::INFO, \"Writing component to: {}\", output.display());\n    match std::fs::write(output, module) {\n        Ok(_) => ExitCode::SUCCESS,\n        Err(e) => {\n            event!(Level::ERROR, \"Failed to write component: {e:?}\");\n            ExitCode::FAILURE\n        }\n    }\n}\n"
  },
  {
    "path": "cli/src/subcommands/run.rs",
    "content": "use crate::execute_ctx::create_execution_context;\nuse crate::install_tracing_subscriber;\nuse crate::opts::RunArgs;\nuse std::process::ExitCode;\nuse tracing::{Level, event};\nuse wasmtime_wasi::I32Exit;\n\npub(crate) async fn exec(run_args: RunArgs) -> ExitCode {\n    install_tracing_subscriber(run_args.shared().verbosity());\n    match run_wasm_main(run_args).await {\n        Ok(_) => ExitCode::SUCCESS,\n        Err(e) => {\n            // Suppress stack trace if the error is due to a\n            // normal call to proc_exit, leading to a process\n            // exit.\n            if !e.is::<I32Exit>() {\n                event!(Level::ERROR, \"{}\", e);\n            }\n            get_exit_code(e)\n        }\n    }\n}\n\n/// Execute a Wasm program in the Viceroy environment.\nasync fn run_wasm_main(run_args: RunArgs) -> Result<(), anyhow::Error> {\n    // Load the wasm module into an execution context\n    let ctx = create_execution_context(\n        run_args.shared(),\n        false,\n        run_args.shared().guest_profile_config(),\n    )\n    .await?;\n    let input = run_args.shared().input();\n    let program_name = match input.file_stem() {\n        Some(stem) => stem.to_string_lossy(),\n        None => panic!(\"program cannot be a directory\"),\n    };\n    ctx.run_main(&program_name, run_args.wasm_args()).await\n}\n\n// This function is based on similar exit code logic in the wasmtime cli:\n// https://github.com/bytecodealliance/wasmtime/blob/cc768f/src/commands/run.rs#L214-L246\nfn get_exit_code(e: anyhow::Error) -> ExitCode {\n    // If we exited with a specific WASI exit code, forward that to\n    // the process\n    if let Some(exit) = e.downcast_ref::<I32Exit>() {\n        // On Windows, exit status 3 indicates an abort (see below),\n        // so return 1 indicating a non-zero status to avoid ambiguity.\n        if cfg!(windows) && exit.0 >= 3 {\n            return ExitCode::FAILURE;\n        }\n        return ExitCode::from(exit.0 as u8);\n    }\n\n    // If the program exited because of a trap, return an error code\n    // to the outside environment indicating a more severe problem\n    // than a simple failure.\n    if e.is::<wasmtime::Trap>() {\n        if cfg!(unix) {\n            // On Unix, return the error code of an abort.\n            return ExitCode::from(128u8 + libc::SIGABRT as u8);\n        } else if cfg!(windows) {\n            // On Windows, return 3.\n            // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019\n            return ExitCode::from(3u8);\n        }\n    }\n    // Otherwise just return 1\n    ExitCode::FAILURE\n}\n"
  },
  {
    "path": "cli/src/subcommands/serve.rs",
    "content": "use crate::opts::ServeArgs;\nuse crate::{create_execution_context, install_tracing_subscriber};\nuse std::process::ExitCode;\nuse tracing::{Level, event};\nuse viceroy_lib::{Error, ViceroyService};\n\npub(crate) async fn exec(serve_args: ServeArgs) -> ExitCode {\n    install_tracing_subscriber(serve_args.shared().verbosity());\n    match {\n        tokio::select! {\n            _ = tokio::signal::ctrl_c() => {\n                Ok(())\n            }\n            res = serve(serve_args) => {\n                if let Err(ref e) = res {\n                    event!(Level::ERROR, \"{}\", e);\n                }\n                res\n            }\n        }\n    } {\n        Ok(_) => ExitCode::SUCCESS,\n        Err(_) => ExitCode::FAILURE,\n    }\n}\n\n/// Starts up a Viceroy server.\n///\n/// Create a new server, bind it to an address, and serve responses until an error occurs.\nasync fn serve(serve_args: ServeArgs) -> Result<(), Error> {\n    // Load the wasm module into an execution context\n    let ctx = create_execution_context(\n        serve_args.shared(),\n        true,\n        serve_args.shared().guest_profile_config(),\n    )\n    .await?;\n\n    if let Some(guest_profile_config) = serve_args.shared().guest_profile_config() {\n        std::fs::create_dir_all(guest_profile_config.path)?;\n    }\n\n    let addr = serve_args.addr();\n    ViceroyService::new(ctx).serve(addr).await?;\n\n    unreachable!()\n}\n"
  },
  {
    "path": "cli/src/subcommands.rs",
    "content": "//! Module for `viceroy` CLI commands.\n\npub mod adapt;\npub mod run;\npub mod serve;\n"
  },
  {
    "path": "cli/tests/integration/acl.rs",
    "content": "use crate::{common::Test, common::TestResult, viceroy_test};\nuse hyper::{StatusCode, body::to_bytes};\nuse viceroy_lib::config::FastlyConfig;\nuse viceroy_lib::error::{AclConfigError, FastlyConfigError};\n\nviceroy_test!(acl_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"acl\"\n        description = \"acl test\"\n        authors = [\"Test User <test_user@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        acls.my-acl-1 = \"../test-fixtures/data/my-acl-1.json\"\n        acls.my-acl-2 = {file = \"../test-fixtures/data/my-acl-2.json\"}\n    \"#;\n\n    let resp = Test::using_fixture(\"acl.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .log_stderr()\n        .log_stdout()\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nfn bad_config_test(local_server_fragment: &str) -> Result<FastlyConfig, FastlyConfigError> {\n    let toml = format!(\n        r#\"\n        name = \"acl\"\n        description = \"acl test\"\n        authors = [\"Test User <test_user@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        {}\n    \"#,\n        local_server_fragment\n    );\n\n    toml.parse::<FastlyConfig>()\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_invalid_path() -> TestResult {\n    const TOML_FRAGMENT: &str = \"acls.bad = 1\";\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidAclDefinition {\n            err: AclConfigError::InvalidType,\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"expected a FastlyConfigError::InvalidAclDefinition with AclConfigError::InvalidType\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_missing_key() -> TestResult {\n    const TOML_FRAGMENT: &str = \"acls.bad = { \\\"other\\\" = true }\";\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidAclDefinition {\n            err: AclConfigError::MissingFile,\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"expected a FastlyConfigError::InvalidAclDefinition with AclConfigError::MissingFile\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_missing_file() -> TestResult {\n    const TOML_FRAGMENT: &str = \"acls.bad = \\\"/does/not/exist\\\"\";\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidAclDefinition {\n            err: AclConfigError::IoError(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"expected a FastlyConfigError::InvalidAclDefinition with AclConfigError::IoError\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_invalid_json() -> TestResult {\n    const TOML_FRAGMENT: &str = \"acls.bad = \\\"../Cargo.toml\\\"\";\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidAclDefinition {\n            err: AclConfigError::JsonError(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"expected a FastlyConfigError::InvalidAclDefinition with AclConfigError::JsonError\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "cli/tests/integration/args.rs",
    "content": "use crate::common::{Test, TestResult};\nuse hyper::{StatusCode, body::to_bytes};\n\n/// Run a program that tests its args. This checks that we're populating the argument list with the\n/// singleton \"compute-app\" value.\n/// Check that an empty response is sent downstream by default.\n///\n/// `args.wasm` is a guest program checks its cli args.\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn empty_ok_response_by_default_after_args() -> TestResult {\n    let resp = Test::using_fixture(\"args.wasm\").against_empty().await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n}\n\n/// Run a program that tests its args. This checks that we're populating the argument list with the\n/// singleton \"compute-app\" value.\n/// Check that an empty response is sent downstream by default.\n///\n/// `args.wasm` is a guest program checks its cli args.\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn empty_ok_response_by_default_after_args_component() {\n    let resp = Test::using_fixture(\"args.wasm\")\n        .adapt_component(true)\n        .against_empty()\n        .await\n        .unwrap();\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n}\n"
  },
  {
    "path": "cli/tests/integration/async_io.rs",
    "content": "// On Windows, streaming body backpressure doesn't seem to work as expected, either\n// due to the Hyper client or server too eagerly clearing the chunk buffer. This issue does\n// not appear related to async I/O hostcalls; the behavior is seen within the streaming body\n// implementation in general. For the time being, this test is unix-only.\n//\n// https://github.com/fastly/Viceroy/issues/207 tracks the broader issue.\n#![cfg(target_family = \"unix\")]\n\nuse crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{Body, Request, Response, StatusCode, body::HttpBody};\nuse std::sync::{\n    Arc,\n    atomic::{AtomicUsize, Ordering},\n};\nuse tokio::sync::Barrier;\n\nviceroy_test!(async_io_methods, |is_component| {\n    let request_count = Arc::new(AtomicUsize::new(0));\n    let req_count_1 = request_count.clone();\n    let req_count_2 = request_count.clone();\n    let req_count_3 = request_count.clone();\n    let req_count_4 = request_count.clone();\n\n    let barrier = Arc::new(Barrier::new(3));\n    let barrier_1 = barrier.clone();\n    let barrier_2 = barrier.clone();\n    let sync_barrier = Arc::new(Barrier::new(2));\n    let sync_barrier_1 = sync_barrier.clone();\n\n    // We set up 4 async backends below, configured to test different\n    // combinations of async behavior from the guest.  The first three backends\n    // are things we are actually testing, and the fourth (\"Semaphore\") is just\n    // used as a synchronization mechanism. Each backend will receive 4 requests\n    // total and will behave differently depending on which request # it is\n    // processing.\n    let test = Test::using_fixture(\"async_io.wasm\")\n        .adapt_component(is_component)\n        .async_backend(\"Simple\", \"/\", None, move |req: Request<Body>| {\n            assert_eq!(req.headers()[\"Host\"], \"simple.org\");\n            let req_count_1 = req_count_1.clone();\n            let barrier_1 = barrier_1.clone();\n            Box::new(async move {\n                match req_count_1.load(Ordering::Relaxed) {\n                    1 => Response::builder()\n                        .status(StatusCode::OK)\n                        .body(Body::empty())\n                        .unwrap(),\n                    0 | 2 | 3 => {\n                        barrier_1.wait().await;\n                        Response::builder()\n                            .status(StatusCode::OK)\n                            .body(Body::empty())\n                            .unwrap()\n                    }\n                    _ => unreachable!(),\n                }\n            })\n        })\n        .await\n        .async_backend(\"ReadBody\", \"/\", None, move |req: Request<Body>| {\n            assert_eq!(req.headers()[\"Host\"], \"readbody.org\");\n            let req_count_2 = req_count_2.clone();\n            Box::new(async move {\n                match req_count_2.load(Ordering::Relaxed) {\n                    2 => Response::builder()\n                        .status(StatusCode::OK)\n                        .body(Body::empty())\n                        .unwrap(),\n                    0 | 1 | 3 => Response::builder()\n                        .header(\"Transfer-Encoding\", \"chunked\")\n                        .status(StatusCode::OK)\n                        .body(Body::empty())\n                        .unwrap(),\n                    _ => unreachable!(),\n                }\n            })\n        })\n        .await\n        .async_backend(\"WriteBody\", \"/\", None, move |req: Request<Body>| {\n            assert_eq!(req.headers()[\"Host\"], \"writebody.org\");\n            let req_count_3 = req_count_3.clone();\n            let barrier_2 = barrier_2.clone();\n            let sync_barrier = sync_barrier.clone();\n            Box::new(async move {\n                match req_count_3.load(Ordering::Relaxed) {\n                    3 => {\n                        // Read at least 4MB and one 8K chunk from the request\n                        // to relieve back-pressure for the guest. These numbers\n                        // come from the amount of data that the guest writes to\n                        // the request body in test-fixtures/src/bin/async_io.rs\n                        let mut bod = req.into_body();\n                        let mut bytes_read = 0;\n                        while bytes_read < (4 * 1024 * 1024) + (8 * 1024) {\n                            if let Some(Ok(bytes)) = bod.data().await {\n                                bytes_read += bytes.len();\n                            }\n                        }\n\n                        // The guest will have another outstanding request to\n                        // the Semaphore backend below. Awaiting on the barrier\n                        // here will cause that request to return indicating to\n                        // the guest that we have read from the request body\n                        // and the write handle should be ready again.\n                        sync_barrier.wait().await;\n                        let _body = hyper::body::to_bytes(bod);\n                        Response::builder()\n                            .status(StatusCode::OK)\n                            .body(Body::empty())\n                            .unwrap()\n                    }\n                    0..=2 => {\n                        barrier_2.wait().await;\n                        Response::builder()\n                            .status(StatusCode::OK)\n                            .body(Body::empty())\n                            .unwrap()\n                    }\n                    _ => unreachable!(),\n                }\n            })\n        })\n        .await\n        .async_backend(\"Semaphore\", \"/\", None, move |req: Request<Body>| {\n            assert_eq!(req.headers()[\"Host\"], \"writebody.org\");\n            let req_count_4 = req_count_4.clone();\n            let sync_barrier_1 = sync_barrier_1.clone();\n            Box::new(async move {\n                match req_count_4.load(Ordering::Relaxed) {\n                    3 => {\n                        sync_barrier_1.wait().await;\n                        Response::builder()\n                            .status(StatusCode::OK)\n                            .body(Body::empty())\n                            .unwrap()\n                    }\n                    0..=2 => Response::builder()\n                        .status(StatusCode::OK)\n                        .body(Body::empty())\n                        .unwrap(),\n                    _ => unreachable!(),\n                }\n            })\n        })\n        .await;\n\n    // request_count is 0 here\n    let resp = test.against_empty().await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(resp.headers()[\"Simple-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Read-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Write-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Ready-Index\"], \"timeout\");\n\n    barrier.wait().await;\n\n    request_count.store(1, Ordering::Relaxed);\n    let resp = test.against_empty().await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(resp.headers()[\"Simple-Ready\"], \"true\");\n    assert_eq!(resp.headers()[\"Read-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Write-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Ready-Index\"], \"0\");\n    let temp_barrier = barrier.clone();\n    let _task = tokio::task::spawn(async move { temp_barrier.wait().await });\n    barrier.wait().await;\n\n    request_count.store(2, Ordering::Relaxed);\n    let resp = test.against_empty().await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(resp.headers()[\"Simple-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Read-Ready\"], \"true\");\n    assert_eq!(resp.headers()[\"Write-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Ready-Index\"], \"1\");\n    barrier.wait().await;\n\n    request_count.store(3, Ordering::Relaxed);\n    let resp = test.against_empty().await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(resp.headers()[\"Simple-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Read-Ready\"], \"false\");\n    assert_eq!(resp.headers()[\"Write-Ready\"], \"true\");\n    assert_eq!(resp.headers()[\"Ready-Index\"], \"2\");\n    let temp_barrier = barrier.clone();\n    let _task = tokio::task::spawn(async move { temp_barrier.wait().await });\n    barrier.wait().await;\n\n    let resp = test\n        .against(\n            Request::get(\"/\")\n                .header(\"Empty-Select-Timeout\", \"0\")\n                .body(Body::empty())\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n\n    let resp = test\n        .against(\n            Request::get(\"/\")\n                .header(\"Empty-Select-Timeout\", \"1\")\n                .body(Body::empty())\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(resp.headers()[\"Ready-Index\"], \"timeout\");\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/body.rs",
    "content": "//! Tests related to HTTP request and response bodies.\n\nuse {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{HeaderMap, Response, StatusCode, body},\n};\n\nviceroy_test!(bodies_can_be_written_and_appended, |is_component| {\n    let resp = Test::using_fixture(\"write-body.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n\n    let body = body::to_bytes(resp.into_body())\n        .await\n        .expect(\"can read body\")\n        .to_vec();\n    let body = String::from_utf8(body)?;\n    assert_eq!(&body, \"Hello, Viceroy!\");\n\n    Ok(())\n});\n\nviceroy_test!(bodies_can_be_written_and_read, |is_component| {\n    let resp = Test::using_fixture(\"write-and-read-body.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n\nviceroy_test!(zero_length_raw_chunks_are_transparent, |is_component| {\n    let resp = Test::using_fixture(\"expects-hello.wasm\")\n        .adapt_component(is_component)\n        .async_backend(\n            \"ReturnsHello\",\n            \"/\",\n            None,\n            move |_req: hyper::Request<hyper::Body>| {\n                Box::new(async move {\n                    // We'll \"trickle back\" our response.\n                    let (mut write, read) = hyper::Body::channel();\n                    // Assume a Tokio runtime for writing the response...\n                    tokio::spawn(async move {\n                        for chunk in [\"\", \"hello\", \"\", \" \", \"\", \"world\", \"\"] {\n                            let Ok(_) = write.send_data(chunk.into()).await else {\n                                return;\n                            };\n                            tokio::task::yield_now().await;\n                        }\n                        let _ = write.send_trailers(HeaderMap::default()).await;\n                    });\n                    Response::builder()\n                        .status(StatusCode::OK)\n                        .body(read)\n                        .unwrap()\n                })\n            },\n        )\n        .await\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n\nviceroy_test!(gzip_breaks_are_ok, |is_component| {\n    let resp = Test::using_fixture(\"expects-hello.wasm\")\n        .adapt_component(is_component)\n        .async_backend(\n            \"ReturnsHello\",\n            \"/\",\n            None,\n            move |req: hyper::Request<hyper::Body>| {\n                Box::new(async move {\n                    let Some(encoding) = req.headers().get(\"accept-encoding\") else {\n                        return Response::builder()\n                            .status(StatusCode::BAD_REQUEST)\n                            .body(hyper::Body::empty())\n                            .unwrap();\n                    };\n                    if !encoding.to_str().unwrap().to_lowercase().contains(\"gzip\") {\n                        return Response::builder()\n                            .status(StatusCode::BAD_REQUEST)\n                            .body(hyper::Body::empty())\n                            .unwrap();\n                    }\n                    // Produced by: echo -n \"hello world\" | gzip >hello_world.gzip\n                    const GZ_BODY: &[u8] = include_bytes!(\"hello_world.gzip\");\n                    // We'll \"trickle back\" our response.\n                    let (mut write, read) = hyper::Body::channel();\n                    // Assume a Tokio runtime for writing the response...\n                    tokio::spawn(async move {\n                        for &byte in GZ_BODY.iter() {\n                            let Ok(_) =\n                                write.send_data(body::Bytes::copy_from_slice(&[byte])).await\n                            else {\n                                return;\n                            };\n                            tokio::task::yield_now().await;\n                        }\n                        let _ = write.send_trailers(HeaderMap::default()).await;\n                    });\n                    Response::builder()\n                        .status(StatusCode::OK)\n                        .header(\"content-encoding\", \"gzip\")\n                        .body(read)\n                        .unwrap()\n                })\n            },\n        )\n        .await\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/cache.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::StatusCode,\n};\n\nviceroy_test!(cache, |is_component| {\n    let resp = Test::using_fixture(\"cache.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/client_certs.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse base64::engine::{Engine, general_purpose};\nuse hyper::http::response;\nuse hyper::server::conn::AddrIncoming;\nuse hyper::service::{make_service_fn, service_fn};\nuse hyper::{Request, Server, StatusCode};\nuse rustls::server::{AllowAnyAuthenticatedClient, ServerConfig};\nuse rustls::{Certificate, PrivateKey, RootCertStore};\nuse std::io::Cursor;\nuse std::net::SocketAddr;\nuse std::sync::Arc;\nuse tls_listener::TlsListener;\nuse tokio_rustls::TlsAcceptor;\nuse tokio_rustls::server::TlsStream;\n\n// So, let's say you want to regenerate some of the keys used in these tests,\n// because they've expired or you want to try different algorithms. To do so,\n// you need to:\n//\n// Create a key for the certificate authority:\n//   > openssl genrsa -des3 -out ca.key 2048\n// You must set a passphrase for this key. In this case, I chose \"Viceroy\"\n//\n// Now we create a root certificate, or a CA certificate that we can use as\n// our \"known good\" authority. This one will last for 10 years. You'll get\n// asked a bunch of questions that don't actually matter:\n//   > openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.pem\n//\n// Now we create a server certificate key:\n//   > openssl genrsa -out server.key 2048\n// Then create a certificate signing request (CSR), which is an annoying middle\n// step:\n//   > openssl req -new -key server.key -out server.csr\n// Which will also ask you a bunch of questions that don't much matter. At this\n// point, it's important to know what you're going to use it for. In this case,\n// we want a server to run on localhost. Must TLS/HTTPS things get very picky\n// about what certificates they'll accept for a server, so we need to mark the\n// certificate appropriately; in this case, as being associated with localhost.\n// So we'll create an extension file called 'server.ext' that contains:\n//\n//      authorityKeyIdentifier=keyid,issuer\n//      basicConstraints=CA:FALSE\n//      keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment\n//      subjectAltName = @alt_names\n//      [alt_names]\n//      DNS.1 = localhost\n//      IP.1 = 127.0.0.1\n//\n// Now we can create a signed certificate to go with our server key, by running:\n//   > openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial \\\n//                  -out server.crt -days 3650 -sha256 -extfile server.ext\n//\n// Repeat this for as many keys as you need. In the case of these tests, we need\n// another one for the client.\n\n// NOTE(ACW): This test setup is much more complicated than it feels like it should\n// be, but this is the only consistent way I can build a server that requires and\n// passes back TLS client certificates.\n\nstruct Watcher {\n    inner: AllowAnyAuthenticatedClient,\n}\n\nimpl rustls::server::ClientCertVerifier for Watcher {\n    fn client_auth_root_subjects(&self) -> &[rustls::DistinguishedName] {\n        tracing::warn!(\"client_auth_root_subjects\");\n        self.inner.client_auth_root_subjects()\n    }\n\n    fn verify_client_cert(\n        &self,\n        end_entity: &Certificate,\n        intermediates: &[Certificate],\n        now: std::time::SystemTime,\n    ) -> Result<rustls::server::ClientCertVerified, rustls::Error> {\n        tracing::warn!(\"varify_client_cert\");\n        self.inner\n            .verify_client_cert(end_entity, intermediates, now)\n    }\n}\n\nfn build_server_tls_config() -> ServerConfig {\n    let mut roots = RootCertStore::empty();\n    let ca_cert_bytes = include_bytes!(concat!(\n        env!(\"CARGO_MANIFEST_DIR\"),\n        \"/../test-fixtures/data/ca.pem\"\n    ));\n\n    let mut ca_cursor = Cursor::new(ca_cert_bytes);\n    let mut root_certs = rustls_pemfile::certs(&mut ca_cursor).expect(\"pem ca certs\");\n    for cert in root_certs.drain(..) {\n        roots.add(&Certificate(cert)).expect(\"can add root certs\");\n    }\n\n    let server_cert_bytes: &[u8] = include_bytes!(concat!(\n        env!(\"CARGO_MANIFEST_DIR\"),\n        \"/../test-fixtures/data/server.crt\"\n    ));\n    let mut server_cursor = Cursor::new(server_cert_bytes);\n    let server_cert_list: Vec<Certificate> = rustls_pemfile::certs(&mut server_cursor)\n        .expect(\"can read server cert\")\n        .into_iter()\n        .map(Certificate)\n        .collect();\n\n    let server_key_bytes: &[u8] = include_bytes!(concat!(\n        env!(\"CARGO_MANIFEST_DIR\"),\n        \"/../test-fixtures/data/server.key\"\n    ));\n    let mut key_cursor = Cursor::new(server_key_bytes);\n    let server_key = rustls_pemfile::rsa_private_keys(&mut key_cursor)\n        .expect(\"have a key\")\n        .into_iter()\n        .map(PrivateKey)\n        .next()\n        .expect(\"have one key\");\n\n    ServerConfig::builder()\n        .with_safe_default_cipher_suites()\n        .with_safe_default_kx_groups()\n        .with_safe_default_protocol_versions()\n        .expect(\"basic tls server config\")\n        .with_client_cert_verifier(Arc::new(Watcher {\n            inner: AllowAnyAuthenticatedClient::new(roots),\n        }))\n        .with_single_cert(server_cert_list, server_key)\n        .expect(\"valid server cert\")\n}\n\nviceroy_test!(custom_ca_works, |is_component| {\n    let test = Test::using_fixture(\"mutual-tls.wasm\").adapt_component(is_component);\n    let server_addr: SocketAddr = \"127.0.0.1:0\".parse().expect(\"localhost parses\");\n    let incoming = AddrIncoming::bind(&server_addr).expect(\"bind\");\n    let bound_port = incoming.local_addr().port();\n\n    let acceptor = TlsAcceptor::from(Arc::new(build_server_tls_config()));\n    let listener = TlsListener::new_hyper(acceptor, incoming);\n\n    let service = make_service_fn(|stream: &TlsStream<_>| {\n        let (_, server_connection) = stream.get_ref();\n        let peer_certs = server_connection.peer_certificates().map(|x| x.to_vec());\n        async move {\n            Ok::<_, std::io::Error>(service_fn(move |_req| {\n                let peer_certs = peer_certs.clone();\n\n                async {\n                    match peer_certs {\n                        None => response::Builder::new()\n                            .status(401)\n                            .body(\"could not identify client certificate\".to_string()),\n                        Some(vec) if vec.len() != 1 => response::Builder::new()\n                            .status(406)\n                            .body(format!(\"can only handle 1 cert, got {}\", vec.len())),\n                        Some(mut cert_vec) => {\n                            let Certificate(cert) = cert_vec.remove(0);\n                            let base64_cert = general_purpose::STANDARD.encode(cert);\n                            response::Builder::new().status(200).body(base64_cert)\n                        }\n                    }\n                }\n            }))\n        }\n    });\n    let server = Server::builder(listener).serve(service);\n    tokio::spawn(server);\n\n    // positive test: setting the CA should allow this\n    let resp = test\n        .against(\n            Request::post(\"/\")\n                .header(\"port\", bound_port)\n                .header(\"set-ca\", \"please\")\n                .body(\"Hello, Viceroy!\")\n                .unwrap(),\n        )\n        .await;\n    let resp = resp.expect(\"got response\");\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        resp.into_body().read_into_string().await?,\n        \"Hello, Viceroy!\"\n    );\n\n    // negative test: if we don't set the CA, we should get a failure\n    let resp = test\n        .against(\n            Request::post(\"/\")\n                .header(\"port\", bound_port)\n                .body(\"Hello, Viceroy!\")\n                .unwrap(),\n        )\n        .await;\n    assert_eq!(\n        resp.expect(\"got response\").status(),\n        StatusCode::SERVICE_UNAVAILABLE\n    );\n    Ok(())\n});\n\nviceroy_test!(client_certs_work, |is_component| {\n    // Set up the test harness\n    // SAFETY: This isn't actually safe, but this is just a test and we haven't\n    // seen problems so far.\n    unsafe {\n        std::env::set_var(\n            \"SSL_CERT_FILE\",\n            concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/../test-fixtures/data/ca.pem\"),\n        )\n    };\n    let test = Test::using_fixture(\"mutual-tls.wasm\").adapt_component(is_component);\n\n    let server_addr: SocketAddr = \"127.0.0.1:0\".parse().expect(\"localhost parses\");\n    let incoming = AddrIncoming::bind(&server_addr).expect(\"bind\");\n    let bound_port = incoming.local_addr().port();\n\n    let acceptor = TlsAcceptor::from(Arc::new(build_server_tls_config()));\n    let listener = TlsListener::new_hyper(acceptor, incoming);\n\n    let service = make_service_fn(|stream: &TlsStream<_>| {\n        let (_, server_connection) = stream.get_ref();\n        let peer_certs = server_connection.peer_certificates().map(|x| x.to_vec());\n        async move {\n            Ok::<_, std::io::Error>(service_fn(move |_req| {\n                let peer_certs = peer_certs.clone();\n\n                async {\n                    match peer_certs {\n                        None => response::Builder::new()\n                            .status(401)\n                            .body(\"could not identify client certificate\".to_string()),\n                        Some(vec) if vec.len() != 1 => response::Builder::new()\n                            .status(406)\n                            .body(format!(\"can only handle 1 cert, got {}\", vec.len())),\n                        Some(mut cert_vec) => {\n                            let Certificate(cert) = cert_vec.remove(0);\n                            let base64_cert = general_purpose::STANDARD.encode(cert);\n                            response::Builder::new().status(200).body(base64_cert)\n                        }\n                    }\n                }\n            }))\n        }\n    });\n    let server = Server::builder(listener).serve(service);\n    tokio::spawn(server);\n\n    let resp = test\n        .against(\n            Request::post(\"/\")\n                .header(\"port\", bound_port)\n                .header(\"set-ca\", \"please\")\n                .body(\"Hello, Viceroy!\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        resp.into_body().read_into_string().await?,\n        \"Hello, Viceroy!\"\n    );\n\n    // SAFETY: See the comment above about `set_var`.\n    unsafe { std::env::remove_var(\"SSL_CERT_FILE\") };\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/common/backends.rs",
    "content": "use std::collections::HashMap;\nuse std::sync::{Arc, Weak};\n\nuse hyper::http::HeaderValue;\nuse hyper::http::uri::PathAndQuery;\nuse hyper::{Body as HyperBody, Request, Response, Uri};\nuse tokio::sync::Mutex;\n\nuse super::{AsyncResp, TestServer, TestService};\n\npub type BackendName = String;\n\n/// A set of test backend definitions and possibly-running test servers for those backends.\n#[derive(Clone, Debug)]\npub struct TestBackends {\n    inner: Arc<Mutex<Inner>>,\n}\n\nimpl TestBackends {\n    /// Create a new, empty set of test backends.\n    pub fn new() -> Self {\n        Self {\n            inner: Arc::new(Mutex::new(Inner::new())),\n        }\n    }\n\n    /// Initialize a set of test backends from [`viceroy_lib::config::Backends`] that probably came\n    /// from a `fastly.toml` file.\n    ///\n    /// Note that this constructor adds backend definitions without an associated test service. A\n    /// test service must be set for each backend with [`TestBackends::set_test_service()`] or\n    /// [`TestBackends::set_async_test_service()`] before starting the test servers.\n    pub fn from_backend_configs(backend_configs: &viceroy_lib::config::Backends) -> Self {\n        let mut inner = Inner::new();\n        for (name, backend_config) in backend_configs {\n            let test_backend = TestBackend {\n                path: backend_config\n                    .uri\n                    .path_and_query()\n                    .cloned()\n                    .unwrap_or_else(|| PathAndQuery::from_static(\"/\")),\n                override_host: backend_config.override_host.clone(),\n                cert_host: backend_config.cert_host.clone(),\n                use_sni: backend_config.use_sni,\n                test_service: None,\n                test_server: None,\n            };\n            inner.map.insert(name.clone(), test_backend);\n        }\n        Self {\n            inner: Arc::new(Mutex::new(inner)),\n        }\n    }\n\n    /// Get [`viceroy_lib::config::Backends`] for the defined test backends and their test servers.\n    ///\n    /// Panics if the test servers have not been started, as the ephemeral ports bound during test\n    /// server startup are required in order to provide complete backend configurations.\n    pub async fn backend_configs(&self) -> viceroy_lib::config::Backends {\n        let inner = self.inner.lock().await;\n        let mut backends = viceroy_lib::config::Backends::new();\n        for (name, backend) in inner.map.iter() {\n            let addr = backend\n                .test_server\n                .as_ref()\n                .expect(\"TestBackend servers must be running to get backend configurations\")\n                .bound_addr;\n            let uri = format!(\"http://{addr}{}\", backend.path)\n                .parse()\n                .expect(\"backend uri must be valid\");\n            let backend_config = viceroy_lib::config::Backend {\n                uri,\n                override_host: backend.override_host.clone(),\n                cert_host: backend.cert_host.clone(),\n                use_sni: backend.use_sni,\n                grpc: false,\n                client_cert: None,\n                ca_certs: vec![],\n                health: viceroy_lib::config::BackendHealth::Unknown,\n            };\n            backends.insert(name.to_string(), Arc::new(backend_config));\n        }\n        backends\n    }\n\n    /// Get a [`TestBackendBuilder`] that will add a backend of the given name when built.\n    pub fn test_backend(&self, name: &str) -> TestBackendBuilder {\n        TestBackendBuilder {\n            inner: Arc::downgrade(&self.inner),\n            name: name.to_string(),\n            path: \"/\".to_string(),\n            override_host: None,\n            use_sni: true,\n            test_service: None,\n        }\n    }\n\n    /// Set the test service for the backend of the given name.\n    ///\n    /// Panics if the test servers have already been started.\n    #[allow(unused)] // It's not used for now, but could be useful for advanced backend chicanery.\n    pub async fn set_test_service<TestServiceFn>(&self, name: &str, test_service: TestServiceFn)\n    where\n        TestServiceFn: Fn(Request<Vec<u8>>) -> Response<Vec<u8>>,\n        TestServiceFn: Send + Sync + 'static,\n    {\n        let mut inner = self.inner.lock().await;\n        assert!(\n            !inner.servers_are_running,\n            \"cannot set a test service once servers are running\"\n        );\n        inner\n            .map\n            .get_mut(name)\n            .unwrap_or_else(|| panic!(\"backend {name:?} not found\"))\n            .test_service = Some(TestService::Sync(Arc::new(test_service)));\n    }\n\n    /// Set the asynchronous test service for the backend of the given name.\n    ///\n    /// Panics if the test servers have already been started.\n    #[allow(unused)] // It's not used for now, but could be useful for advanced backend chicanery.\n    pub async fn set_async_test_service<TestServiceFn>(\n        &self,\n        name: &str,\n        test_service: TestServiceFn,\n    ) where\n        TestServiceFn: Fn(Request<HyperBody>) -> AsyncResp,\n        TestServiceFn: Send + Sync + 'static,\n    {\n        let mut inner = self.inner.lock().await;\n        assert!(\n            !inner.servers_are_running,\n            \"cannot set a test service once servers are running\"\n        );\n        inner\n            .map\n            .get_mut(name)\n            .unwrap_or_else(|| panic!(\"backend {name:?} not found\"))\n            .test_service = Some(TestService::Async(Arc::new(test_service)));\n    }\n\n    /// Are the backend test servers running?\n    pub async fn servers_are_running(&self) -> bool {\n        self.inner.lock().await.servers_are_running\n    }\n\n    /// Start the backend test servers.\n    ///\n    /// Panics if the servers are already running, or if any backend is missing a test service.\n    pub async fn start_servers(&self) {\n        let mut inner = self.inner.lock().await;\n        assert!(\n            !inner.servers_are_running,\n            \"cannot start TestBackend servers more than once\"\n        );\n        for (name, backend) in inner.map.iter_mut() {\n            let Some(service) = backend.test_service.as_ref() else {\n                panic!(\"no service defined for backend {name}\");\n            };\n            backend.test_server = Some(Arc::new(service.spawn()));\n        }\n        inner.servers_are_running = true;\n    }\n\n    /// Get the [`Uri`] suitable for sending a request to a running backend test server.\n    ///\n    /// Specifically, this `Uri` will include the ephemeral port assigned when the test server was\n    /// started, which must be known in advance to properly test fixtures using dynamic backends.\n    ///\n    /// Panics if no backend by this name is defined, or if the backend test servers have not yet been\n    /// started.\n    pub async fn uri_for_backend_server(&self, name: &str) -> Uri {\n        let inner = self.inner.lock().await;\n        let backend = inner.map.get(name).expect(\"backend not found\");\n        let addr = backend\n            .test_server\n            .as_ref()\n            .expect(\"TestBackend servers must be running to get backend configurations\")\n            .bound_addr;\n        format!(\"http://{addr}{}\", backend.path)\n            .parse()\n            .expect(\"backend uri must be valid\")\n    }\n}\n\n#[derive(Debug)]\nstruct Inner {\n    map: HashMap<BackendName, TestBackend>,\n    servers_are_running: bool,\n}\n\nimpl Inner {\n    pub fn new() -> Self {\n        Self {\n            map: HashMap::new(),\n            servers_are_running: false,\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\npub struct TestBackend {\n    path: PathAndQuery,\n    override_host: Option<HeaderValue>,\n    cert_host: Option<String>,\n    use_sni: bool,\n    test_service: Option<TestService>,\n    test_server: Option<Arc<TestServer>>,\n}\n\n#[derive(Debug)]\npub struct TestBackendBuilder {\n    inner: Weak<Mutex<Inner>>,\n    name: String,\n    path: String,\n    override_host: Option<String>,\n    use_sni: bool,\n    test_service: Option<TestService>,\n}\n\nimpl TestBackendBuilder {\n    /// Set the path to be prepended to requests sent to this backend (`/` by default).\n    pub fn path(mut self, path: &str) -> Self {\n        self.path = path.to_owned();\n        self\n    }\n\n    /// Set the `override_host` parameter on this backend (not set by default).\n    pub fn override_host(mut self, override_host: &str) -> Self {\n        self.override_host = Some(override_host.to_string());\n        self\n    }\n\n    /// Set the `use_sni` parameter on this backend (`true` by default).\n    pub fn use_sni(mut self, use_sni: bool) -> Self {\n        self.use_sni = use_sni;\n        self\n    }\n\n    /// Set the synchronous service function to use for this backend.\n    ///\n    /// The service function takes a request argument with a byte vector body, and returns a\n    /// response with a byte vector body. It will be called for each request sent to this backend's\n    /// test server.\n    ///\n    /// A test service (sync or async) must be set before starting the test servers.\n    pub fn test_service<TestServiceFn>(mut self, test_service: TestServiceFn) -> Self\n    where\n        TestServiceFn: Fn(Request<Vec<u8>>) -> Response<Vec<u8>>,\n        TestServiceFn: Send + Sync + 'static,\n    {\n        self.test_service = Some(TestService::Sync(Arc::new(test_service)));\n        self\n    }\n\n    /// Set the asynchronous service function to use for this backend.\n    ///\n    /// The service function takes a request argument with a `hyper::Body`, and returns a response\n    /// with a `hyper::Body`. It will be called for each request sent to this backend's test server.\n    ///\n    /// A test service (sync or async) must be set before starting the test servers.\n    pub fn async_test_service<TestServiceFn>(mut self, test_service: TestServiceFn) -> Self\n    where\n        TestServiceFn: Fn(Request<HyperBody>) -> AsyncResp,\n        TestServiceFn: Send + Sync + 'static,\n    {\n        self.test_service = Some(TestService::Async(Arc::new(test_service)));\n        self\n    }\n\n    /// Finish building this backend and add it to the [`TestBackends`] that created this builder.\n    ///\n    /// Panics if:\n    ///\n    ///   * The `TestBackends` that created this builder no longer exists, or its test servers have\n    ///     already been started\n    ///   * The `path` does not parse as a valid `PathAndQuery`\n    ///   * The `override_host` does not parse as a valid `HeaderValue`\n    pub async fn build(self) {\n        let inner_arc = self.inner.upgrade().expect(\"TestBackends dropped\");\n        let path = self.path.parse().expect(\"invalid backend path\");\n        let override_host = self\n            .override_host\n            .map(|s| s.parse().expect(\"can parse override_host\"));\n        let mut inner = inner_arc.lock().await;\n        if inner.servers_are_running {\n            panic!(\"cannot add test backends after starting servers\");\n        }\n        inner.map.insert(\n            self.name,\n            TestBackend {\n                path,\n                override_host,\n                cert_host: None,\n                use_sni: self.use_sni,\n                test_service: self.test_service,\n                test_server: None,\n            },\n        );\n    }\n}\n"
  },
  {
    "path": "cli/tests/integration/common.rs",
    "content": "//! Common values and types used by test fixtures\n\nuse futures::stream::StreamExt;\nuse hyper::{Body as HyperBody, Request, Response, Server, Uri, service};\nuse std::{\n    collections::HashSet,\n    convert::Infallible,\n    future::Future,\n    io::Write,\n    net::{Ipv4Addr, SocketAddr},\n    path::PathBuf,\n    sync::{Arc, Mutex},\n};\nuse tracing_subscriber::filter::EnvFilter;\nuse viceroy_lib::config::UnknownImportBehavior;\nuse viceroy_lib::{\n    ExecuteCtx, ProfilingStrategy, ViceroyService,\n    body::Body,\n    config::{\n        Acls, DeviceDetection, Dictionaries, FakeValidFastlyKeys, FastlyConfig, Geolocation,\n        ObjectStores, SecretStores, ShieldingSites,\n    },\n};\nuse wasmtime::WasmFeatures;\n\npub use self::backends::TestBackends;\n\nmod backends;\n\n#[macro_export]\nmacro_rules! viceroy_test {\n    ($name:ident, |$is_component:ident| $body:block) => {\n        mod $name {\n            use super::*;\n\n            async fn test_impl($is_component: bool) -> TestResult {\n                $body\n            }\n\n            #[tokio::test(flavor = \"multi_thread\")]\n            async fn core_wasm() -> TestResult {\n                test_impl(false).await\n            }\n\n            #[tokio::test(flavor = \"multi_thread\")]\n            async fn component() -> TestResult {\n                test_impl(true).await\n            }\n        }\n    };\n}\n\n/// A shorthand for the path to our test fixtures' build artifacts for Rust tests.\n///\n/// This value can be appended with the name of a fixture's `.wasm` in a test program, using the\n/// [`format!`][fmt] macro. For example:\n///\n/// ```\n/// let module_path = format!(\"{}/guest.wasm\", RUST_FIXTURE_PATH);\n/// ```\npub static RUST_FIXTURE_PATH: &str = \"../test-fixtures/target/wasm32-wasip1/debug/\";\n\n/// A shorthand for the path to our test fixtures' build artifacts for WAT tests.\n///\n/// This value can be appended with the name of a fixture's `.wat` in a test program, using the\n/// [`format!`][fmt] macro. For example:\n///\n/// ```\n/// let module_path = format!(\"{}/guest.wat\", WAT_FIXTURE_PATH);\n/// ```\npub static WAT_FIXTURE_PATH: &str = \"../test-fixtures/\";\n\n/// A catch-all error, so we can easily use `?` in test cases.\npub type Error = Box<dyn std::error::Error + Send + Sync>;\n\n/// Handy alias for the return type of async Tokio tests\npub type TestResult = Result<(), Error>;\n\n/// A builder for running individual requests through a wasm fixture.\npub struct Test {\n    module_path: PathBuf,\n    acls: Acls,\n    backends: TestBackends,\n    device_detection: DeviceDetection,\n    dictionaries: Dictionaries,\n    geolocation: Geolocation,\n    object_stores: ObjectStores,\n    secret_stores: SecretStores,\n    shielding_sites: ShieldingSites,\n    fake_valid_fastly_keys: FakeValidFastlyKeys,\n    capture_logs: Arc<Mutex<dyn Write + Send>>,\n    log_stdout: bool,\n    log_stderr: bool,\n    via_hyper: bool,\n    unknown_import_behavior: UnknownImportBehavior,\n    adapt_component: bool,\n    profiling_strategy: ProfilingStrategy,\n    guest_profile_config: Option<viceroy_lib::GuestProfileConfig>,\n}\n\nimpl Test {\n    /// Create a new test given the file name for its wasm fixture.\n    pub fn using_fixture(fixture: &str) -> Self {\n        let mut module_path = PathBuf::from(RUST_FIXTURE_PATH);\n        module_path.push(fixture);\n\n        Self {\n            module_path,\n            acls: Acls::new(),\n            backends: TestBackends::new(),\n            device_detection: DeviceDetection::new(),\n            dictionaries: Dictionaries::new(),\n            geolocation: Geolocation::new(),\n            object_stores: ObjectStores::new(),\n            secret_stores: SecretStores::new(),\n            shielding_sites: ShieldingSites::new(),\n            fake_valid_fastly_keys: FakeValidFastlyKeys::new(),\n            capture_logs: Arc::new(Mutex::new(std::io::stdout())),\n            log_stdout: false,\n            log_stderr: false,\n            via_hyper: false,\n            unknown_import_behavior: Default::default(),\n            adapt_component: false,\n            profiling_strategy: ProfilingStrategy::None,\n            guest_profile_config: None,\n        }\n    }\n\n    /// Create a new test given the file name for its wasm fixture.\n    pub fn using_wat_fixture(fixture: &str) -> Self {\n        let mut module_path = PathBuf::from(WAT_FIXTURE_PATH);\n        module_path.push(fixture);\n\n        Self {\n            module_path,\n            acls: Acls::new(),\n            backends: TestBackends::new(),\n            device_detection: DeviceDetection::new(),\n            dictionaries: Dictionaries::new(),\n            geolocation: Geolocation::new(),\n            object_stores: ObjectStores::new(),\n            secret_stores: SecretStores::new(),\n            shielding_sites: ShieldingSites::new(),\n            fake_valid_fastly_keys: FakeValidFastlyKeys::new(),\n            capture_logs: Arc::new(Mutex::new(std::io::stdout())),\n            log_stdout: false,\n            log_stderr: false,\n            via_hyper: false,\n            unknown_import_behavior: Default::default(),\n            adapt_component: false,\n            profiling_strategy: ProfilingStrategy::None,\n            guest_profile_config: None,\n        }\n    }\n\n    /// Use backend and dictionary settings provided in a `fastly.toml` file.\n    pub fn using_fastly_toml(self, fastly_toml: &str) -> Result<Self, Error> {\n        let config = fastly_toml.parse::<FastlyConfig>()?;\n        Ok(Self {\n            acls: config.acls().to_owned(),\n            backends: TestBackends::from_backend_configs(config.backends()),\n            device_detection: config.device_detection().to_owned(),\n            dictionaries: config.dictionaries().to_owned(),\n            geolocation: config.geolocation().to_owned(),\n            object_stores: config.object_stores().to_owned(),\n            secret_stores: config.secret_stores().to_owned(),\n            shielding_sites: config.shielding_sites().to_owned(),\n            fake_valid_fastly_keys: config.fake_valid_fastly_keys().to_owned(),\n            ..self\n        })\n    }\n\n    /// Use existing [`TestBackends`] for this test, replacing any previously existing backends.\n    #[allow(unused)] // It's not used for now, but could be useful for advanced backend chicanery.\n    pub fn using_test_backends(mut self, test_backends: &TestBackends) -> Self {\n        self.backends = test_backends.clone();\n        self\n    }\n\n    /// Use the specified [`UnknownImportBehavior`] for this test.\n    pub fn using_unknown_import_behavior(\n        mut self,\n        unknown_import_behavior: UnknownImportBehavior,\n    ) -> Self {\n        self.unknown_import_behavior = unknown_import_behavior;\n        self\n    }\n\n    /// Add a backend definition to this test.\n    ///\n    /// The `name` is the static backend name that can be passed as, for example, the argument to\n    /// `Request::send()`.\n    ///\n    /// The `path` is the path that will be prepended to the URLs of requests sent to this\n    /// backend. Note that the host and port used to send requests to this backend will be\n    /// automatically determined when the test servers are started.\n    ///\n    /// `override_host` optionally sets the corresponding parameter in the backend definition.\n    ///\n    /// `service` is the synchronous function that the test server will run on each request this\n    /// backend receives in order to determine what response to send.\n    pub async fn backend<ServiceFn>(\n        self,\n        name: &str,\n        path: &str,\n        override_host: Option<&str>,\n        service: ServiceFn,\n    ) -> Self\n    where\n        ServiceFn: Fn(Request<Vec<u8>>) -> Response<Vec<u8>>,\n        ServiceFn: Send + Sync + 'static,\n    {\n        let uri: Uri = path.parse().expect(\"invalid backend URL\");\n        let mut builder = self\n            .backends\n            .test_backend(name)\n            .path(uri.path())\n            .use_sni(true)\n            .test_service(service);\n        if let Some(override_host) = override_host {\n            builder = builder.override_host(override_host);\n        }\n        builder.build().await;\n        self\n    }\n\n    /// Add a backend definition to this test with an asynchronous test server function.\n    ///\n    /// The `name` is the static backend name that can be passed as, for example, the argument to\n    /// `Request::send()`.\n    ///\n    /// The `path` is the path that will be prepended to the URLs of requests sent to this\n    /// backend. Note that the host and port used to send requests to this backend will be\n    /// automatically determined when the test servers are started.\n    ///\n    /// `override_host` optionally sets the corresponding parameter in the backend definition.\n    ///\n    /// `service` is the asynchronous function that the test server will run on each request this\n    /// backend receives in order to determine what response to send.\n    pub async fn async_backend<ServiceFn>(\n        self,\n        name: &str,\n        url: &str,\n        override_host: Option<&str>,\n        service: ServiceFn,\n    ) -> Self\n    where\n        ServiceFn: Fn(Request<HyperBody>) -> AsyncResp,\n        ServiceFn: Send + Sync + 'static,\n    {\n        let uri: Uri = url.parse().expect(\"invalid backend URL\");\n        let mut builder = self\n            .backends\n            .test_backend(name)\n            .path(uri.path())\n            .use_sni(true)\n            .async_test_service(service);\n        if let Some(override_host) = override_host {\n            builder = builder.override_host(override_host);\n        }\n        builder.build().await;\n        self\n    }\n\n    pub fn capture_logs(mut self, capture_logs: Arc<Mutex<dyn Write + Send>>) -> Self {\n        self.capture_logs = capture_logs;\n        self\n    }\n\n    /// Treat stderr as a logging endpoint for this test.\n    pub fn log_stderr(self) -> Self {\n        Self {\n            log_stderr: true,\n            ..self\n        }\n    }\n\n    /// Treat stdout as a logging endpoint for this test.\n    pub fn log_stdout(self) -> Self {\n        Self {\n            log_stdout: true,\n            ..self\n        }\n    }\n\n    /// Actually spin up a hyper server and client for this test, rather than just\n    /// passing the request through the guest code.\n    pub fn via_hyper(self) -> Self {\n        Self {\n            via_hyper: true,\n            ..self\n        }\n    }\n\n    /// Automatically adapt the wasm to a component before running.\n    pub fn adapt_component(mut self, adapt: bool) -> Self {\n        self.adapt_component = adapt;\n        self\n    }\n\n    /// Enable guest profiling with the specified configuration.\n    pub fn with_guest_profiling(mut self, config: viceroy_lib::GuestProfileConfig) -> Self {\n        self.guest_profile_config = Some(config);\n        self\n    }\n\n    /// Pass the given requests through this test, returning the associated responses.\n    ///\n    /// A `Test` can be used repeatedly against different requests, either individually (as with\n    /// `against()`) or in batches (as with `against_many()`).\n    ///\n    /// The difference between calling this function with many requests, rather than calling\n    /// `against()` multiple times, is that the requests shared in an `against_many()` call will\n    /// share the same Wasm execution context. This can be useful when validating interactions\n    /// across shared state in the context. Subsequent calls to `against_many()` (or `against()`)\n    /// will use a fresh context.\n    ///\n    /// When this function is called, the test servers for its defined backends will be started, if\n    /// they have not been already. Those test servers will remain running for the lifetime of this\n    /// [`Test`] object, and are therefore potentially reused for multiple `against*()` invocations.\n    pub async fn against_many(\n        &self,\n        mut reqs: Vec<Request<impl Into<HyperBody>>>,\n    ) -> Result<Vec<Response<Body>>, Error> {\n        let mut responses = Vec::with_capacity(reqs.len());\n\n        // Install a tracing subscriber. We use a human-readable event formatter in tests, using a\n        // writer that supports input capturing for `cargo test`. This subscribes to all events in\n        // the `viceroy-lib` library.\n        tracing_subscriber::fmt()\n            .with_env_filter(\n                EnvFilter::from_default_env().add_directive(\"viceroy_lib=trace\".parse().unwrap()),\n            )\n            .pretty()\n            .with_test_writer()\n            // `try_init` returns an `Err` if the initialization was unsuccessful, likely because a\n            // global subscriber was already installed. we will ignore this error if it happens.\n            .try_init()\n            .ok();\n\n        // Start the backend test servers so that we can ask `TestBackends` for the final backend\n        // configurations, including the ephemeral ports we'll need for requests to actually land on\n        // the right servers. The test servers will remain running until after this [`Test`] is\n        // dropped.\n        if !self.backends.servers_are_running().await {\n            self.backends.start_servers().await;\n        }\n\n        let ctx = ExecuteCtx::build(\n            &self.module_path,\n            self.profiling_strategy,\n            HashSet::new(),\n            self.guest_profile_config.clone(),\n            self.unknown_import_behavior,\n            self.adapt_component,\n            WasmFeatures::default(),\n        )?\n        .with_acls(self.acls.clone())\n        .with_backends(self.backends.backend_configs().await)\n        .with_dictionaries(self.dictionaries.clone())\n        .with_device_detection(self.device_detection.clone())\n        .with_geolocation(self.geolocation.clone())\n        .with_object_stores(self.object_stores.clone())\n        .with_secret_stores(self.secret_stores.clone())\n        .with_shielding_sites(self.shielding_sites.clone())\n        .with_fake_valid_fastly_keys(self.fake_valid_fastly_keys.clone())\n        .with_capture_logs(self.capture_logs.clone())\n        .with_log_stderr(self.log_stderr)\n        .with_log_stdout(self.log_stdout)\n        .finish()?;\n\n        if self.via_hyper {\n            let svc = ViceroyService::new(ctx);\n            // We use the \"graceful shutdown\" capability of Hyper, with a oneshot channel signaling\n            // completion:\n            let (tx, rx) = tokio::sync::oneshot::channel();\n            // NB the server is spawned onto a dedicated async task; we are going to use the\n            // _current_ task to act as the client.\n            let (server_handle, server_addr) = {\n                // Bind the server to an ephemeral port to allow for parallel test execution.\n                let server = hyper::Server::bind(&([127, 0, 0, 1], 0).into()).serve(svc);\n                let server_addr = server.local_addr();\n                let server_handle = tokio::spawn(server.with_graceful_shutdown(async {\n                    rx.await\n                        .expect(\"receiver error while shutting down hyper server\")\n                }));\n                (server_handle, server_addr)\n            };\n\n            for mut req in reqs.drain(..) {\n                // Fix up the request URI to include the ephemeral port assignment. The `http::Uri`\n                // interface makes this unfortunately verbose.\n                let new_uri = Uri::builder()\n                    .scheme(\"http\")\n                    .authority(server_addr.to_string())\n                    .path_and_query(\n                        req.uri()\n                            .path_and_query()\n                            .map(|p_and_q| p_and_q.as_str())\n                            .unwrap_or(\"\"),\n                    )\n                    .build()\n                    .unwrap();\n                *req.uri_mut() = new_uri;\n\n                // Pass the request to the server via a Hyper client on the _current_ task:\n                let resp = hyper::Client::new().request(req.map(Into::into)).await?;\n                responses.push(resp.map(Into::into));\n            }\n\n            // We're done with these test requests, so shut down the server.\n            tx.send(())\n                .expect(\"sender error while shutting down hyper server\");\n            // Reap the task handle to ensure that the server did indeed shut down.\n            let _ = server_handle.await?;\n        } else {\n            for mut req in reqs.drain(..) {\n                // We do not have to worry about an ephemeral port in the non-hyper scenario, but we\n                // still normalize the request URI for consistency.\n                let new_uri = Uri::builder()\n                    .scheme(\"http\")\n                    .authority(\"localhost\")\n                    .path_and_query(\n                        req.uri()\n                            .path_and_query()\n                            .map(|p_and_q| p_and_q.as_str())\n                            .unwrap_or(\"\"),\n                    )\n                    .build()\n                    .unwrap();\n                *req.uri_mut() = new_uri;\n                let local = (Ipv4Addr::LOCALHOST, 80).into();\n                let remote = (Ipv4Addr::LOCALHOST, 0).into();\n                let resp = ctx\n                    .clone()\n                    .handle_request(req.map(Into::into), local, remote)\n                    .await\n                    .map(|result| {\n                        match result {\n                            (resp, None) => resp,\n                            (_, Some(err)) => {\n                                // Splat the string representation of the runtime error into a synthetic\n                                // 500. This is a bit of a hack, but good enough to check for expected error\n                                // strings.\n                                let body = err.to_string();\n                                Response::builder()\n                                    .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)\n                                    .body(Body::from(body.as_bytes()))\n                                    .unwrap()\n                            }\n                        }\n                    })?;\n                responses.push(resp);\n            }\n        }\n\n        Ok(responses)\n    }\n\n    /// Pass the given request to a Viceroy execution context defined by this test.\n    ///\n    /// Only the path, query, and fragment of the request URI will be used; the host and port will\n    /// be rewritten as appropriate to connect to the Viceroy context.\n    ///\n    /// A `Test` can be used repeatedly against different requests. Note, however, that\n    /// a fresh execution context is set up each time.\n    pub async fn against(\n        &self,\n        req: Request<impl Into<HyperBody>>,\n    ) -> Result<Response<Body>, Error> {\n        Ok(self\n            .against_many(vec![req])\n            .await?\n            .pop()\n            .expect(\"singleton back from against_many\"))\n    }\n\n    /// Pass an empty `GET /` request through this test.\n    pub async fn against_empty(&self) -> Result<Response<Body>, Error> {\n        self.against(Request::get(\"/\").body(\"\").unwrap()).await\n    }\n\n    /// Start the test servers for this test's [`TestBackends`].\n    ///\n    /// Panics if a test service has not been set for all configured backends. This is unlikely to\n    /// occur unless using [`Test::using_fastly_toml()] or [`Test::using_backends()`], as the\n    /// convenience methods for defining backends require a test service.\n    pub async fn start_backend_servers(&self) {\n        self.backends.start_servers().await;\n    }\n\n    /// Get the [`Uri`] suitable for sending a request to a running backend test server.\n    ///\n    /// Specifically, this `Uri` will include the ephemeral port assigned when the test server was\n    /// started, which must be known in advance to properly test fixtures using dynamic backends.\n    ///\n    /// Panics if no backend by this name is defined, or if the backend test servers have not yet\n    /// been started.\n    pub async fn uri_for_backend_server(&self, name: &str) -> Uri {\n        self.backends.uri_for_backend_server(name).await\n    }\n}\n\n/// A handle to a running test server, used to keep track of its assigned ephemeral port and to\n/// gracefully shut down the server when it's no longer needed.\n#[derive(Debug)]\nstruct TestServer {\n    bound_addr: SocketAddr,\n    terminate_signal: Option<tokio::sync::oneshot::Sender<()>>,\n    task_handle: tokio::task::JoinHandle<()>,\n}\n\nimpl Drop for TestServer {\n    fn drop(&mut self) {\n        self.terminate_signal\n            .take()\n            .unwrap()\n            .send(())\n            .expect(\"could not send terminate signal to test server\");\n        if !self.task_handle.is_finished() {\n            self.task_handle.abort();\n        }\n    }\n}\ntype SyncService = dyn Fn(Request<Vec<u8>>) -> Response<Vec<u8>> + Send + Sync;\n\ntype AsyncResp = Box<dyn Future<Output = Response<HyperBody>> + Send + Sync>;\ntype AsyncService = dyn Fn(Request<HyperBody>) -> AsyncResp + Send + Sync;\n\n#[derive(Clone)]\nenum TestService {\n    Sync(Arc<SyncService>),\n    Async(Arc<AsyncService>),\n}\n\nimpl std::fmt::Debug for TestService {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Self::Sync(_) => f.debug_tuple(\"Sync\").finish(),\n            Self::Async(_) => f.debug_tuple(\"Async\").finish(),\n        }\n    }\n}\n\nimpl TestService {\n    /// Spawn a test server onto a dedicated Tokio task, returning a handle to allow for graceful\n    /// termination of the server when it is no longer needed.\n    fn spawn(&self) -> TestServer {\n        let service = self.clone();\n\n        // we transform `service` into an async function that consumes Hyper bodies. that requires a bit\n        // of `Arc` and `move` operations because each invocation needs to produce a distinct `Future`\n        let async_service = Arc::new(move |req: Request<HyperBody>| {\n            let service = service.clone();\n\n            async move {\n                let resp = match service {\n                    TestService::Sync(s) => {\n                        let (parts, body) = req.into_parts();\n                        let mut body = Box::new(body); // for pinning\n                        // read out all of the bytes from the body into a vector, then re-assemble the request\n                        let mut body_bytes = Vec::new();\n                        while let Some(chunk) = body.next().await {\n                            body_bytes.extend_from_slice(&chunk.unwrap());\n                        }\n                        let req = Request::from_parts(parts, body_bytes);\n\n                        // pass the request through the service function, then convert its body into\n                        // the form that Hyper wants\n                        s(req).map(HyperBody::from)\n                    }\n                    TestService::Async(s) => Box::into_pin(s(req)).await.map(HyperBody::from),\n                };\n\n                let res: Result<_, hyper::Error> = Ok(resp);\n                res\n            }\n        });\n\n        // now we go through Tower's service layers, wrapping `async_host`\n        let make_service = service::make_service_fn(move |_conn| {\n            let async_host = async_service.clone();\n            async move { Ok::<_, Infallible>(service::service_fn(move |req| async_host(req))) }\n        });\n\n        // we set up a \"graceful shutdown\" for the server, with a oneshot channel signaling completion.\n        let (terminate_signal, rx) = tokio::sync::oneshot::channel();\n        // Bind the test server to an ephemeral port to avoid conflicts between\n        // concurrently-executing tests.\n        let server = Server::bind(&([127, 0, 0, 1], 0).into()).serve(make_service);\n        let bound_addr = server.local_addr();\n        let graceful_server = server.with_graceful_shutdown(async {\n            rx.await\n                .expect(\"receiver error while shutting down mock host\")\n        });\n        let task_handle = tokio::spawn(async {\n            graceful_server\n                .await\n                .expect(\"mock host shut down with hyper error\")\n        });\n        TestServer {\n            bound_addr,\n            terminate_signal: Some(terminate_signal),\n            task_handle,\n        }\n    }\n}\n"
  },
  {
    "path": "cli/tests/integration/config_store_lookup.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{StatusCode, body::to_bytes};\n\nviceroy_test!(json_config_store_lookup_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"json-config_store-lookup\"\n        description = \"json config_store lookup test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server.config_stores.animals]\n        file = \"../test-fixtures/data/json-config_store.json\"\n        format = \"json\"\n    \"#;\n\n    let resp = Test::using_fixture(\"config-store-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(inline_toml_config_store_lookup_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"inline-toml-config_store-lookup\"\n        description = \"inline toml config_store lookup test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server.config_stores.animals]\n        format = \"inline-toml\"\n        [local_server.config_stores.animals.contents]\n        dog = \"woof\"\n        cat = \"meow\"\n    \"#;\n\n    let resp = Test::using_fixture(\"config-store-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(missing_config_store_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"missing-config_store-config\"\n        description = \"missing config_store test\"\n        language = \"rust\"\n    \"#;\n\n    let resp = Test::using_fixture(\"config-store-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/device_detection_lookup.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{StatusCode, body::to_bytes};\n\nviceroy_test!(json_device_detection_lookup_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"json-device-detection-lookup\"\n        description = \"json device detection lookup test\"\n        authors = [\"Test User <test_user@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        [local_server.device_detection]\n        file = \"../test-fixtures/data/device-detection-mapping.json\"\n        format = \"json\"\n    \"#;\n\n    let resp = Test::using_fixture(\"device-detection-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(inline_toml_device_detection_lookup_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"inline-toml-device-detection-lookup\"\n        description = \"inline toml device detection lookup test\"\n        authors = [\"Test User <test_user@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        [local_server.device_detection]\n        format = \"inline-toml\"\n        [local_server.device_detection.user_agents]\n        [local_server.device_detection.user_agents.\"Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 [FBAN/FBIOS;FBAV/8.0.0.28.18;FBBV/1665515;FBDV/iPhone4,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/7.0.4;FBSS/2; FBCR/Telekom.de;FBID/phone;FBLC/de_DE;FBOP/5]\"]\n        user_agent = {}\n        os = {}\n        device = {name = \"iPhone\", brand = \"Apple\", model = \"iPhone4,1\", hwtype = \"Mobile Phone\", is_ereader = false, is_gameconsole = false, is_mediaplayer = false, is_mobile = true, is_smarttv = false, is_tablet = false, is_tvplayer = false, is_desktop = false, is_touchscreen = true }\n        [local_server.device_detection.user_agents.\"ghosts-app/1.0.2.1 (ASUSTeK COMPUTER INC.; X550CC; Windows 8 (X86); en)\"]\n        user_agent = {}\n        os = {}\n        device = {name = \"Asus TeK\", brand = \"Asus\", model = \"TeK\", is_desktop = false }\n        \"#;\n\n    let resp = Test::using_fixture(\"device-detection-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/dictionary_lookup.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{StatusCode, body::to_bytes};\n\nviceroy_test!(json_dictionary_lookup_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"json-dictionary-lookup\"\n        description = \"json dictionary lookup test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        [local_server.dictionaries]\n        [local_server.dictionaries.animals]\n        file = \"../test-fixtures/data/json-dictionary.json\"\n        format = \"json\"\n    \"#;\n\n    let resp = Test::using_fixture(\"dictionary-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(inline_toml_dictionary_lookup_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"inline-toml-dictionary-lookup\"\n        description = \"inline toml dictionary lookup test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        [local_server.dictionaries]\n        [local_server.dictionaries.animals]\n        format = \"inline-toml\"\n        [local_server.dictionaries.animals.contents]\n        dog = \"woof\"\n        cat = \"meow\"\n    \"#;\n\n    let resp = Test::using_fixture(\"dictionary-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(missing_dictionary_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"missing-dictionary-config\"\n        description = \"missing dictionary test\"\n        language = \"rust\"\n    \"#;\n\n    let resp = Test::using_fixture(\"dictionary-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/downstream_req.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{Request, StatusCode},\n};\n\nviceroy_test!(downstream_request_works, |is_component| {\n    let req = Request::get(\"/\")\n        .header(\"Accept\", \"text/html\")\n        .header(\"X-Custom-Test\", \"abcdef\")\n        .body(\"Hello, world!\")?;\n    let resp = Test::using_fixture(\"downstream-req.wasm\")\n        .adapt_component(is_component)\n        .against(req)\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/early_hints.rs",
    "content": "// A test to ensure that early hints (103 responses) don't cause errors in Viceroy.\n\nuse crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\n\nviceroy_test!(early_hints, |is_component| {\n    let resp = Test::using_fixture(\"early-hints.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert!(resp.status().is_success());\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/edge_rate_limiting.rs",
    "content": "//! Tests related to HTTP request and response bodies.\n\nuse {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::StatusCode,\n};\n\nviceroy_test!(check_hostcalls_implemented, |is_component| {\n    let resp = Test::using_fixture(\"edge-rate-limiting.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/env_vars.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\n\nviceroy_test!(env_vars_are_set, |is_component| {\n    let resp = Test::using_fixture(\"env-vars.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert!(resp.status().is_success());\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/fastly_key_is_valid.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{Request, StatusCode},\n};\n\nviceroy_test!(fastly_key_is_valid_with_valid_key, |is_component| {\n    let resp = Test::using_fixture(\"fastly-key-is-valid.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(\n            r#\"\n            manifest_version = 3\n            name = \"fastly-key-is-valid-test\"\n            language = \"rust\"\n\n            [local_server]\n            fake_valid_fastly_keys = [\"test-key-123\", \"another-key\"]\n        \"#,\n        )?\n        .against(\n            Request::get(\"/\")\n                .header(\"fastly-key\", \"test-key-123\")\n                .body(\"\")?,\n        )\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    let body = resp.into_body().read_into_string().await?;\n    assert!(\n        body.contains(\"is_valid=true\"),\n        \"expected valid key, got: {body}\"\n    );\n    Ok(())\n});\n\nviceroy_test!(fastly_key_is_valid_with_invalid_key, |is_component| {\n    let resp = Test::using_fixture(\"fastly-key-is-valid.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(\n            r#\"\n            manifest_version = 3\n            name = \"fastly-key-is-valid-test\"\n            language = \"rust\"\n\n            [local_server]\n            fake_valid_fastly_keys = [\"test-key-123\", \"another-key\"]\n        \"#,\n        )?\n        .against(\n            Request::get(\"/\")\n                .header(\"fastly-key\", \"wrong-key\")\n                .body(\"\")?,\n        )\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    let body = resp.into_body().read_into_string().await?;\n    assert!(\n        body.contains(\"is_valid=false\"),\n        \"expected invalid key, got: {body}\"\n    );\n    Ok(())\n});\n\nviceroy_test!(fastly_key_is_valid_with_no_header, |is_component| {\n    let resp = Test::using_fixture(\"fastly-key-is-valid.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(\n            r#\"\n            manifest_version = 3\n            name = \"fastly-key-is-valid-test\"\n            language = \"rust\"\n\n            [local_server]\n            fake_valid_fastly_keys = [\"test-key-123\"]\n        \"#,\n        )?\n        .against(Request::get(\"/\").body(\"\")?)\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    let body = resp.into_body().read_into_string().await?;\n    assert!(\n        body.contains(\"is_valid=false\"),\n        \"expected invalid without header, got: {body}\"\n    );\n    Ok(())\n});\n\nviceroy_test!(\n    fastly_key_is_valid_with_no_keys_configured,\n    |is_component| {\n        let resp = Test::using_fixture(\"fastly-key-is-valid.wasm\")\n            .adapt_component(is_component)\n            .against(Request::get(\"/\").header(\"fastly-key\", \"any-key\").body(\"\")?)\n            .await?;\n\n        assert_eq!(resp.status(), StatusCode::OK);\n        let body = resp.into_body().read_into_string().await?;\n        assert!(\n            body.contains(\"is_valid=false\"),\n            \"expected invalid when no keys configured, got: {body}\"\n        );\n        Ok(())\n    }\n);\n"
  },
  {
    "path": "cli/tests/integration/geolocation_lookup.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{StatusCode, body::to_bytes};\n\nviceroy_test!(json_geolocation_lookup_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"json-geolocation-lookup\"\n        description = \"json geolocation lookup test\"\n        authors = [\"Test User <test_user@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        [local_server.geolocation]\n        use_default_loopback = false\n        file = \"../test-fixtures/data/geolocation-mapping.json\"\n        format = \"json\"\n    \"#;\n\n    let resp = Test::using_fixture(\"geolocation-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(inline_toml_geolocation_lookup_works, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"inline-toml-geolocation-lookup\"\n        description = \"inline toml geolocation lookup test\"\n        authors = [\"Test User <test_user@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        [local_server.geolocation]\n        use_default_loopback = false\n        format = \"inline-toml\"\n        [local_server.geolocation.addresses]\n        [local_server.geolocation.addresses.\"127.0.0.1\"]\n        as_name = \"Fastly Test\"\n        as_number = 12345\n        area_code = 123\n        city = \"Test City\"\n        conn_speed = \"broadband\"\n        conn_type = \"wired\"\n        continent = \"NA\"\n        country_code = \"CA\"\n        country_code3 = \"CAN\"\n        country_name = \"Canada\"\n        latitude = 12.345\n        longitude = 54.321\n        metro_code = 0\n        postal_code = \"12345\"\n        proxy_description = \"?\"\n        proxy_type = \"?\"\n        region = \"BC\"\n        utc_offset = -700\n        [local_server.geolocation.addresses.\"0000:0000:0000:0000:0000:0000:0000:0001\"]\n        as_name = \"Fastly Test IPv6\"\n        as_number = 12345\n        area_code = 123\n        city = \"Test City IPv6\"\n        conn_speed = \"broadband\"\n        conn_type = \"wired\"\n        continent = \"NA\"\n        country_code = \"CA\"\n        country_code3 = \"CAN\"\n        country_name = \"Canada\"\n        latitude = 12.345\n        longitude = 54.321\n        metro_code = 0\n        postal_code = \"12345\"\n        proxy_description = \"?\"\n        proxy_type = \"?\"\n        region = \"BC\"\n        utc_offset = -700\n    \"#;\n\n    let resp = Test::using_fixture(\"geolocation-lookup.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(\n    default_configuration_geolocation_lookup_works,\n    |is_component| {\n        const FASTLY_TOML: &str = r#\"\n        name = \"default-config-geolocation-lookup\"\n        description = \"default config geolocation lookup test\"\n        authors = [\"Test User <test_user@fastly.com>\"]\n        language = \"rust\"\n    \"#;\n\n        let resp = Test::using_fixture(\"geolocation-lookup-default.wasm\")\n            .adapt_component(is_component)\n            .using_fastly_toml(FASTLY_TOML)?\n            .against_empty()\n            .await?;\n\n        assert_eq!(resp.status(), StatusCode::OK);\n        assert!(\n            to_bytes(resp.into_body())\n                .await\n                .expect(\"can read body\")\n                .to_vec()\n                .is_empty()\n        );\n\n        Ok(())\n    }\n);\n"
  },
  {
    "path": "cli/tests/integration/grpc.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::http::response;\nuse hyper::server::conn::AddrIncoming;\nuse hyper::service::{make_service_fn, service_fn};\nuse hyper::{Request, Server, StatusCode};\nuse std::net::SocketAddr;\n\nviceroy_test!(grpc_creates_h2_connection, |is_component| {\n    let test = Test::using_fixture(\"grpc.wasm\").adapt_component(is_component);\n    let server_addr: SocketAddr = \"127.0.0.1:0\".parse().expect(\"localhost parses\");\n    let incoming = AddrIncoming::bind(&server_addr).expect(\"bind\");\n    let bound_port = incoming.local_addr().port();\n\n    let service = make_service_fn(|_| async move {\n        Ok::<_, std::io::Error>(service_fn(move |_req| async {\n            response::Builder::new()\n                .status(200)\n                .body(\"Hello!\".to_string())\n        }))\n    });\n\n    let server = Server::builder(incoming).http2_only(true).serve(service);\n    tokio::spawn(server);\n\n    let resp = test\n        .against(\n            Request::post(\"/\")\n                .header(\"port\", bound_port)\n                .body(\"Hello, Viceroy!\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    // The test below is not critical -- we've proved our point by returning 200 -- but seems\n    // to trigger an error in Windows; it looks like there's a funny interaction between reading\n    // the body and the stream having been closed, and we get a NO_ERROR error. So I've commented\n    // it out, until there's a clear Hyper solution.\n    // assert_eq!(resp.into_body().read_into_string().await?, \"Hello!\");\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/http_semantics.rs",
    "content": "//! Tests related to HTTP semantics (e.g. framing headers, status codes).\n\nuse {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{Request, Response, StatusCode, header},\n};\n\nviceroy_test!(framing_headers_are_overridden, |is_component| {\n    // Set up the test harness\n    let test = Test::using_fixture(\"bad-framing-headers.wasm\")\n        .adapt_component(is_component)\n        // The \"TheOrigin\" backend checks framing headers on the request and then echos its body.\n        .backend(\"TheOrigin\", \"/\", None, |req| {\n            assert!(!req.headers().contains_key(header::TRANSFER_ENCODING));\n            assert_eq!(\n                req.headers().get(header::CONTENT_LENGTH),\n                Some(&hyper::header::HeaderValue::from(9))\n            );\n            Response::new(Vec::from(&b\"salutations\"[..]))\n        })\n        .await;\n\n    let resp = test\n        .via_hyper()\n        .against(Request::post(\"/\").body(\"greetings\").unwrap())\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n\n    assert!(!resp.headers().contains_key(header::TRANSFER_ENCODING));\n    assert_eq!(\n        resp.headers().get(header::CONTENT_LENGTH),\n        Some(&hyper::header::HeaderValue::from(11))\n    );\n\n    Ok(())\n});\n\nviceroy_test!(manual_framing_preserves_transfer_encoding, |is_component| {\n    let test = Test::using_fixture(\"manual-framing-headers.wasm\")\n        .adapt_component(is_component)\n        .backend(\"TheOrigin\", \"/\", None, |req| {\n            assert!(\n                !req.headers().contains_key(header::CONTENT_LENGTH),\n                \"Content-Length was incorrectly added\"\n            );\n            // Transfer-Encoding should be preserved\n            assert_eq!(\n                req.headers().get(header::TRANSFER_ENCODING),\n                Some(&hyper::header::HeaderValue::from_static(\"chunked\")),\n                \"Transfer-Encoding should have been preserved\"\n            );\n            Response::new(Vec::new())\n        })\n        .await;\n\n    let resp = test.via_hyper().against_empty().await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n\nviceroy_test!(content_length_is_computed_correctly, |is_component| {\n    // Set up the test harness\n    let test = Test::using_fixture(\"content-length.wasm\")\n        .adapt_component(is_component)\n        // The \"TheOrigin\" backend supplies a fixed-size body.\n        .backend(\"TheOrigin\", \"/\", None, |_| {\n            Response::new(Vec::from(&b\"ABCDEFGHIJKLMNOPQRST\"[..]))\n        })\n        .await;\n\n    let resp = test.via_hyper().against_empty().await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n\n    assert!(!resp.headers().contains_key(header::TRANSFER_ENCODING));\n    assert_eq!(\n        resp.headers().get(header::CONTENT_LENGTH),\n        Some(&hyper::header::HeaderValue::from(28))\n    );\n    let resp_body = resp.into_body().read_into_string().await.unwrap();\n    assert_eq!(resp_body, \"ABCD12345xyzEFGHIJKLMNOPQRST\");\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/inspect.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{Request, StatusCode},\n};\n\nviceroy_test!(upstream_sync, |is_component| {\n    // Set up the test harness:\n    let test = Test::using_fixture(\"inspect.wasm\").adapt_component(is_component);\n\n    // And send a request to exercise the hostcall:\n    let resp = test\n        .against(Request::post(\"/\").body(\"Hello, Viceroy!\").unwrap())\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        resp.into_body().read_into_string().await?,\n        \"inspect result: waf_response=200, tags=[], decision_ms=0ms, verdict=Allow\"\n    );\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/invalid_status_code.rs",
    "content": "// A test to ensure that invalid status codes (1xx other than 103 Early Hunts) return errors in Viceroy.\n\nuse crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\n\nuse hyper::StatusCode;\n\nviceroy_test!(invalid_status_code, |is_component| {\n    let resp = Test::using_fixture(\"invalid-status-code.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/kv_store.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{StatusCode, body::to_bytes};\n\nviceroy_test!(kv_store, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = [{key = \"first\", data = \"This is some data\"},{key = \"second\", file = \"../test-fixtures/data/kv-store.txt\"},{key = \"third\", data = \"third\", metadata = \"some metadata\"}]\n    \"#;\n\n    let resp = Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(object_stores_backward_compat, |is_component| {\n    // Previously the \"kv_stores\" key was named \"object_stores\" and\n    // the \"file\" key was named \"path\".  This test ensures that both\n    // still work.\n    const FASTLY_TOML: &str = r#\"\n        name = \"object-store-test\"\n        description = \"object store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        object_stores.empty_store = []\n        object_stores.store_one = [{key = \"first\", data = \"This is some data\"},{key = \"second\", path = \"../test-fixtures/data/kv-store.txt\"},{key = \"third\", data = \"third\", metadata = \"some metadata\"}]\n    \"#;\n\n    let resp = Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(kv_store_allows_fetching_of_key_from_file, |is_component| {\n    // This test checks that we can provide a \"format\" and a \"file\"\n    // with a JSON dictionary inside it for a KV Store\n    // and have the KV Store populated with it.\n    const FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Gustav Wengel <gustav@climatiq.io>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = { file = \"../test-fixtures/data/json-kv_store.json\", format = \"json\" }\n    \"#;\n\n    let resp = Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(object_store_backward_compat, |is_component| {\n    // Previously the \"object_stores\" key was named \"object_store\" and\n    // the \"file\" key was named \"path\".  This test ensures that both\n    // still work.\n    const FASTLY_TOML: &str = r#\"\n        name = \"object-store-test\"\n        description = \"object store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        object_store.empty_store = []\n        object_store.store_one = [{key = \"first\", data = \"This is some data\"},{key = \"second\", path = \"../test-fixtures/data/kv-store.txt\"},{key = \"third\", data = \"third\", metadata = \"some metadata\"}]\n    \"#;\n\n    let resp = Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\nviceroy_test!(kv_store_bad_configs, |is_component| {\n    const BAD_1_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = 3, data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_1_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': The `key` value for an object is not a string.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_2_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"first\", data = 3}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_2_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': The `data` value for the object `first` is not a string.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_3_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"first\", data = \"This is some data\", file = \"../test-fixtures/data/kv-store.txt\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_3_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': The `file` and `data` keys for the object `first` are set. Only one can be used.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_4_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"first\", file = 3}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_4_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': The `file` value for the object `first` is not a string.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_5_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"first\", file = \"../path/does/not/exist\"}]\n    \"#;\n\n    // For CI to pass we need to include the specific message for each platform\n    // we test against\n    #[cfg(target_os = \"macos\")]\n    let invalid_path_message =\n        \"invalid configuration for 'store_one': No such file or directory (os error 2)\";\n    #[cfg(target_os = \"linux\")]\n    let invalid_path_message =\n        \"invalid configuration for 'store_one': No such file or directory (os error 2)\";\n    #[cfg(target_os = \"windows\")]\n    let invalid_path_message = \"invalid configuration for 'store_one': The system cannot find the path specified. (os error 3)\";\n\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_5_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(invalid_path_message, &e.to_string()),\n        _ => panic!(),\n    }\n\n    const BAD_6_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"first\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_6_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': The `file` or `data` key for the object `first` is not set. One must be used.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_7_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_7_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': The `key` key for an object is not set. It must be used.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_8_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = \"lol lmao\"\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_8_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': There is no array of objects for the given store.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_9_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [\"This is some data\"]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_9_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': There is an object in the given store that is not a table of keys.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_10_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Gustav Wengel <gustav@climatiq.io>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = { file = \"../test-fixtures/data/json-kv_store.json\" }\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\").using_fastly_toml(BAD_10_FASTLY_TOML) {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': When using a top-level 'file' to load data, both 'file' and 'format' must be set.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_11_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Gustav Wengel <gustav@climatiq.io>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = { format = \"json\" }\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\").using_fastly_toml(BAD_11_FASTLY_TOML) {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': When using a top-level 'file' to load data, both 'file' and 'format' must be set.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_12_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Gustav Wengel <gustav@climatiq.io>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = { file = \"../test-fixtures/data/json-kv_store.json\", format = \"INVALID\" }\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\").using_fastly_toml(BAD_12_FASTLY_TOML) {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': 'INVALID' is not a valid format for the config store. Supported format(s) are: 'json'.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_13_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Gustav Wengel <gustav@climatiq.io>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = { file = \"../test-fixtures/data/ABSOLUTELY_NOT_A_REAL_PATH\", format = \"json\" }\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\").using_fastly_toml(BAD_13_FASTLY_TOML) {\n        Err(e) => {\n            // We can't assert on the whole error message here as the next part of the string is platform-specific\n            // where it says that it cannot find the file.\n            assert!(\n                e.to_string()\n                    .contains(\"invalid configuration for 'store_one'\")\n            );\n        }\n        _ => panic!(),\n    }\n\n    // Not a valid JSON file\n    const BAD_14_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Gustav Wengel <gustav@climatiq.io>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = { file = \"../test-fixtures/data/kv-store.txt\", format = \"json\" }\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\").using_fastly_toml(BAD_14_FASTLY_TOML) {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': The file is of the wrong format. The file is expected to contain a single JSON Object.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_15_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"first\", data = \"This is some data\", metadata = 5}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_15_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': The `metadata` value for the object `first` is not a string.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    // Invalid format JSON - entry must have data or file (or path)\n    const BAD_16_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Gustav Wengel <gustav@climatiq.io>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = { file = \"../test-fixtures/data/json-kv_store-invalid_1.json\", format = \"json\" }\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\").using_fastly_toml(BAD_16_FASTLY_TOML) {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Item value under key named 'first' is of the wrong format. One of 'data' or 'file' must be present.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    // Invalid format JSON - entry cannot have both data and file\n    const BAD_17_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Gustav Wengel <gustav@climatiq.io>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.empty_store = []\n        kv_stores.store_one = { file = \"../test-fixtures/data/json-kv_store-invalid_2.json\", format = \"json\" }\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\").using_fastly_toml(BAD_17_FASTLY_TOML) {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Item value under key named 'first' is of the wrong format. 'data' and 'file' are mutually exclusive.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    Ok(())\n});\n\nviceroy_test!(kv_store_bad_key_values, |is_component| {\n    const BAD_1_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_1_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot be empty.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_2_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeEEEEEEEEEEEEEEEEEEEEEEEEEEEEEeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_2_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot be over 1024 bytes in size.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_3_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \".well-known/acme-challenge/wheeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_3_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot start with `.well-known/acme-challenge`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_4_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \".\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_4_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot be named `.`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_5_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"..\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_5_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot be named `..`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_6_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"carriage\\rreturn\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_6_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `\\r`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_7_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"newlines\\nin\\nthis\\neconomy?\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_7_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `\\n`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_8_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"howdy#\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_8_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `#`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_9_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"hello;\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_9_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `;`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_10_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"yoohoo?\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_10_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `?`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_11_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"hey^\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_11_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `^`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_12_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \"ello ello|\", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_12_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `|`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    const BAD_13_FASTLY_TOML: &str = r#\"\n        name = \"kv-store-test\"\n        description = \"kv store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        kv_stores.store_one = [{key = \" \", data = \"This is some data\"}]\n    \"#;\n    match Test::using_fixture(\"kv_store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(BAD_13_FASTLY_TOML)\n    {\n        Err(e) => assert_eq!(\n            \"invalid configuration for 'store_one': Invalid `key` value used: Keys for objects cannot contain a `\\\\u{20}`.\",\n            &e.to_string()\n        ),\n        _ => panic!(),\n    }\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/logging.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::StatusCode,\n    std::{\n        io::{self, Write},\n        sync::{Arc, Mutex},\n    },\n};\n\nstruct LogWriter(Vec<Vec<u8>>);\n\nimpl Write for LogWriter {\n    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {\n        self.0.push(buf.to_owned());\n        Ok(buf.len())\n    }\n\n    fn flush(&mut self) -> io::Result<()> {\n        Ok(())\n    }\n}\n\nviceroy_test!(logging_works, |is_component| {\n    let log_writer = Arc::new(Mutex::new(LogWriter(Vec::new())));\n    let resp = Test::using_fixture(\"logging.wasm\")\n        .adapt_component(is_component)\n        .capture_logs(log_writer.clone())\n        .log_stderr()\n        .log_stdout()\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n\n    let mut logs = std::mem::take(&mut log_writer.lock().unwrap().0).into_iter();\n    let mut read_log_line = || String::from_utf8(logs.next().unwrap()).unwrap();\n\n    assert_eq!(read_log_line(), \"inigo :: Who are you?\\n\");\n    assert_eq!(read_log_line(), \"mib :: No one of consequence.\\n\");\n    assert_eq!(read_log_line(), \"inigo :: I must know.\\n\");\n    assert_eq!(read_log_line(), \"mib :: Get used to disappointment.\\n\");\n\n    assert_eq!(\n        read_log_line(),\n        \"mib :: There is something\\\\nI ought to tell you.\\n\"\n    );\n    assert_eq!(read_log_line(), \"inigo :: Tell me.\\\\n\\n\");\n    assert_eq!(read_log_line(), \"mib :: I'm not left-handed either.\\n\");\n    assert_eq!(read_log_line(), \"inigo :: O_O\\n\");\n\n    assert_eq!(read_log_line(), \"stdout :: logging from stdout\\n\");\n    assert_eq!(read_log_line(), \"stderr :: logging from stderr\\n\");\n\n    // showcase line buffering on stdout\n    assert_eq!(read_log_line(), \"stdout :: log line terminates on flush\\n\");\n    assert_eq!(read_log_line(), \"stdout :: newline completes a log line\\n\");\n\n    // showcase no buffering on stderr\n    assert_eq!(read_log_line(), \"stderr :: log line terminates on flush\\n\");\n    assert_eq!(read_log_line(), \"stderr :: log line terminates\\n\");\n    assert_eq!(read_log_line(), \"stderr :: on each write\\n\");\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/main.rs",
    "content": "mod acl;\nmod args;\nmod async_io;\nmod body;\nmod cache;\nmod client_certs;\nmod common;\nmod config_store_lookup;\nmod device_detection_lookup;\nmod dictionary_lookup;\nmod downstream_req;\nmod early_hints;\nmod edge_rate_limiting;\nmod env_vars;\nmod fastly_key_is_valid;\nmod geolocation_lookup;\nmod grpc;\nmod http_semantics;\nmod inspect;\nmod invalid_status_code;\nmod kv_store;\nmod logging;\nmod memory;\nmod profiling;\nmod request;\nmod response;\nmod reusable_sandboxes;\nmod secret_store;\nmod sending_response;\nmod shielding;\nmod sleep;\nmod unknown_import_behavior;\nmod upstream;\nmod upstream_async;\nmod upstream_dynamic;\nmod upstream_streaming;\nmod vcpu_time;\n"
  },
  {
    "path": "cli/tests/integration/memory.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::body::to_bytes;\nuse hyper::{Request, StatusCode};\n\nviceroy_test!(direct_wasm_works, |is_component| {\n    let resp = Test::using_wat_fixture(\"return_ok.wat\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n\nviceroy_test!(heap_limit_test_ok, |is_component| {\n    let resp = Test::using_wat_fixture(\"combined_heap_limits.wat\")\n        .adapt_component(is_component)\n        .against(\n            Request::get(\"/\")\n                .header(\"guest-kb\", \"235\")\n                .header(\"header-kb\", \"1\")\n                .header(\"body-kb\", \"16\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n    println!(\"response: {:?}\", resp);\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(resp.headers().len(), 16);\n    assert!(resp.headers().contains_key(\"x-test-header-3\"));\n    assert_eq!(\n        resp.headers().get(\"x-test-header-12\").unwrap(),\n        \"Lorem ipsum dolor sit amet, consectetur adipiscing elit viverra.\"\n    );\n    let body = resp.into_body();\n    assert_eq!(to_bytes(body).await.unwrap().len(), 16 * 1024);\n    Ok(())\n});\n\nviceroy_test!(heap_limit_test_bad, |is_component| {\n    let resp = Test::using_wat_fixture(\"combined_heap_limits.wat\")\n        .adapt_component(is_component)\n        .against(\n            Request::get(\"/\")\n                .header(\"guest-kb\", \"150000\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/profiling.rs",
    "content": "//! Tests for guest profiling functionality.\n\nuse {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::StatusCode,\n    std::time::Duration,\n    viceroy_lib::GuestProfileConfig,\n};\n\nviceroy_test!(guest_profiling_works, |is_component| {\n    // Create a temporary directory for the profile output\n    let temp_dir = tempfile::tempdir()?;\n    let profile_dir = temp_dir.path().join(\"profiles\");\n    std::fs::create_dir(&profile_dir)?;\n\n    let resp = Test::using_fixture(\"noop.wasm\")\n        .adapt_component(is_component)\n        .with_guest_profiling(GuestProfileConfig {\n            path: profile_dir.clone(),\n            sample_period: Duration::from_micros(50),\n        })\n        .against_empty()\n        .await?;\n\n    // Verify the request succeeded\n    assert_eq!(resp.status(), StatusCode::OK);\n\n    // Verify that a profile file was created in the directory\n    let profile_files: Vec<_> = std::fs::read_dir(&profile_dir)?\n        .filter_map(|e| e.ok())\n        .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some(\"json\"))\n        .collect();\n\n    assert!(\n        !profile_files.is_empty(),\n        \"At least one profile JSON file should be created in {:?}\",\n        profile_dir\n    );\n\n    // Verify the first profile file has content and is valid JSON\n    let first_profile = &profile_files[0];\n    let metadata = std::fs::metadata(first_profile.path())?;\n    assert!(metadata.len() > 0, \"Profile file should not be empty\");\n\n    let profile_content = std::fs::read_to_string(first_profile.path())?;\n    let _: serde_json::Value = serde_json::from_str(&profile_content)?;\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/request.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::StatusCode,\n};\n\nviceroy_test!(request_works, |is_component| {\n    let resp = Test::using_fixture(\"request.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/response.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::StatusCode,\n};\n\nviceroy_test!(response_works, |is_component| {\n    let resp = Test::using_fixture(\"response.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/reusable_sandboxes.rs",
    "content": "//! Tests related to HTTP request and response bodies.\n\nuse crate::common::{Test, TestResult};\nuse crate::viceroy_test;\nuse hyper::{Request, StatusCode};\n\nviceroy_test!(check_hostcalls_implemented, |is_component| {\n    let test = Test::using_fixture(\"reusable-sandboxes.wasm\")\n        .adapt_component(is_component)\n        .via_hyper();\n\n    let reqs = (0..5)\n        .map(|n| Request::post(\"/\").body(n.to_string()).unwrap())\n        .collect();\n\n    let resps = test.against_many(reqs).await?;\n\n    for (n, resp) in resps.into_iter().enumerate() {\n        let exp = format!(\"Response #{}\", n + 1);\n        assert_eq!(resp.status(), StatusCode::OK);\n        assert_eq!(resp.into_body().read_into_string().await?, exp);\n    }\n\n    Ok(())\n});\n\nviceroy_test!(check_crash_causes_5xx, |is_component| {\n    let test = Test::using_fixture(\"reusable-sandboxes.wasm\")\n        .adapt_component(is_component)\n        .via_hyper();\n\n    let reqs = (0..5)\n        .map(|n| {\n            let body = if n == 4 {\n                \"crash!\".to_owned()\n            } else {\n                n.to_string()\n            };\n            Request::post(\"/\").body(body).unwrap()\n        })\n        .collect();\n\n    let mut resps = test.against_many(reqs).await?;\n    let errs = resps.split_off(4);\n\n    assert_eq!(resps.len(), 4);\n    assert_eq!(errs.len(), 1);\n\n    for (n, resp) in resps.into_iter().enumerate() {\n        let exp = format!(\"Response #{}\", n + 1);\n        assert_eq!(resp.status(), StatusCode::OK);\n        assert_eq!(resp.into_body().read_into_string().await?, exp);\n    }\n\n    for resp in errs.into_iter() {\n        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n        let body = resp.into_body().read_into_string().await?;\n        let needle = \"error while executing at wasm backtrace\";\n        assert!(body.contains(needle), \"missing expected string: {body:?}\");\n    }\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/secret_store.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{StatusCode, body::to_bytes};\nuse viceroy_lib::config::FastlyConfig;\nuse viceroy_lib::error::{FastlyConfigError, SecretStoreConfigError};\n\nviceroy_test!(secret_store_works, |is_component| {\n    // SAFETY: This isn't actually safe, but this is just a test and we haven't\n    // seen problems so far.\n    unsafe { std::env::set_var(\"ENV_3D397809_C6DB_446E_BB73_F2B1E87A0F2A\", \"my-env-value\") };\n\n    const FASTLY_TOML: &str = r#\"\n        name = \"secret-store\"\n        description = \"secret store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        secret_stores.store_one = [{key = \"first\", data = \"This is some data\"},{key = \"second\", file = \"../test-fixtures/data/kv-store.txt\"},{key = \"fourth\", env = \"ENV_3D397809_C6DB_446E_BB73_F2B1E87A0F2A\"}]\n        secret_stores.store_two = {file = \"../test-fixtures/data/json-secret_store.json\", format = \"json\"}\n    \"#;\n\n    let resp = Test::using_fixture(\"secret-store.wasm\")\n        .adapt_component(is_component)\n        .using_fastly_toml(FASTLY_TOML)?\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    // SAFETY: See the comment above about `set_var`.\n    unsafe { std::env::remove_var(\"ENV_3D397809_C6DB_446E_BB73_F2B1E87A0F2A\") };\n\n    Ok(())\n});\n\nfn bad_config_test(toml_fragment: &str) -> Result<FastlyConfig, FastlyConfigError> {\n    let toml = format!(\n        r#\"\n        name = \"secret-store\"\n        description = \"secret store test\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n        [local_server]\n        {}\n    \"#,\n        toml_fragment\n    );\n\n    println!(\"TOML: {}\", toml);\n    toml.parse::<FastlyConfig>()\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_store_not_array() -> TestResult {\n    const TOML_FRAGMENT: &str = \"secret_stores.store_one = 1\";\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::NotAnArray,\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::NotAnArray\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_store_not_table() -> TestResult {\n    const TOML_FRAGMENT: &str = \"secret_stores.store_one = [1]\";\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::NotATable,\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::NotATable\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_no_key() -> TestResult {\n    const TOML_FRAGMENT: &str = r#\"secret_stores.store_one = [{data = \"This is some data\"}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::NoKey,\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::NoKey\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_key_not_string() -> TestResult {\n    const TOML_FRAGMENT: &str =\n        r#\"secret_stores.store_one = [{key = 1, data = \"This is some data\"}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::KeyNotAString,\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::KeyNotAString\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_no_store_field() -> TestResult {\n    const TOML_FRAGMENT: &str = r#\"secret_stores.store_one = [{key = \"first\"}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::FileDataEnvNotSet(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::NoFileOrData\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_store_fields_not_exclusive() -> TestResult {\n    use std::fmt::Write as _;\n\n    fn build_toml_fragment(data: bool, file: bool, env: bool) -> String {\n        let mut s = String::from(r#\"secret_stores.store_one = [{ key = \"first\"\"#);\n        if data {\n            write!(s, r#\", data = \"This is some data\"\"#).unwrap();\n        }\n        if file {\n            write!(s, r#\", file = \"file.txt\"\"#).unwrap();\n        }\n        if env {\n            write!(s, r#\", env = \"ENV_5CD829D9_4DAD_406A_8524_A810650E614E\"\"#).unwrap();\n        }\n        s.push_str(\"}]\");\n        s\n    }\n\n    // iterate all combinations of data, file, env that have 2 or more fields set\n    for &data in &[false, true] {\n        for &file in &[false, true] {\n            for &env in &[false, true] {\n                if [data, file, env].into_iter().filter(|b| *b).count() < 2 {\n                    continue;\n                }\n\n                let toml = build_toml_fragment(data, file, env);\n\n                match bad_config_test(&toml) {\n                    Err(FastlyConfigError::InvalidSecretStoreDefinition {\n                        err: SecretStoreConfigError::FileDataEnvExclusive(_),\n                        ..\n                    }) => {}\n                    Err(_) => panic!(\n                        \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::FileDataEnvExclusive\"\n                    ),\n                    Ok(_) => panic!(\"Expected an error, but got Ok\"),\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_data_not_string() -> TestResult {\n    const TOML_FRAGMENT: &str = r#\"secret_stores.store_one = [{key = \"first\", data = 1}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::DataNotAString(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::DataNotAString\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_file_not_string() -> TestResult {\n    const TOML_FRAGMENT: &str = r#\"secret_stores.store_one = [{key = \"first\", file = 1}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::FileNotAString(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::FileNotAString\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_env_not_string() -> TestResult {\n    const TOML_FRAGMENT: &str = r#\"secret_stores.store_one = [{key = \"first\", env = 1}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::EnvNotAString(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::EnvNotAString\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_file_nonexistent() -> TestResult {\n    const TOML_FRAGMENT: &str =\n        r#\"secret_stores.store_one = [{key = \"first\", file = \"nonexistent.txt\"}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::IoError(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::IoError\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_invalid_store_name() -> TestResult {\n    const TOML_FRAGMENT: &str =\n        r#\"secret_stores.store*one = [{key = \"first\", data = \"This is some data\"}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidFastlyToml(_)) => (),\n        Err(_) => panic!(\"Expected a FastlyConfigError::InvalidFastlyToml\"),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_invalid_secret_name() -> TestResult {\n    const TOML_FRAGMENT: &str =\n        r#\"secret_stores.store_one = [{key = \"first*\", data = \"This is some data\"}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::InvalidSecretName(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::InvalidSecretName\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn bad_config_secret_name_too_long() -> TestResult {\n    const TOML_FRAGMENT: &str = r#\"secret_stores.store_one = [{key = \"firstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirst\", data = \"This is some data\"}]\"#;\n    match bad_config_test(TOML_FRAGMENT) {\n        Err(FastlyConfigError::InvalidSecretStoreDefinition {\n            err: SecretStoreConfigError::InvalidSecretName(_),\n            ..\n        }) => (),\n        Err(_) => panic!(\n            \"Expected a FastlyConfigError::InvalidSecretStoreDefinition with SecretStoreConfigError::InvalidSecretName\"\n        ),\n        _ => panic!(\"Expected an error\"),\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "cli/tests/integration/sending_response.rs",
    "content": "//! Tests related to sending HTTP responses downstream.\n\nuse {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{\n        StatusCode,\n        body::{HttpBody, to_bytes},\n    },\n};\n\n// Use the `teapot-status` fixture to check that responses can be sent downstream by the guest.\n//\n// `teapot-status.wasm` will create a [`418 I'm a teapot`][tea] response, per [RFC2324][rfc]. This\n// status code is used to clearly indicate that a response came from the guest program.\n//\n// [rfc]: https://tools.ietf.org/html/rfc2324\n// [tea]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418\nviceroy_test!(responses_can_be_sent_downstream, |is_component| {\n    let resp = Test::using_fixture(\"teapot-status.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::IM_A_TEAPOT);\n    Ok(())\n});\n\n// Run a program that does nothing, to check that an empty response is sent downstream by default.\n//\n// `noop.wasm` is an empty guest program. This exists to show that if no response is sent\n// downstream by the guest, a [`200 OK`][ok] response will be sent.\n//\n// [ok]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200\nviceroy_test!(empty_ok_response_by_default, |is_component| {\n    let resp = Test::using_fixture(\"noop.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n\n// Run a program that panics, to check that a [`500 Internal Server Error`][err] response is sent\n// downstream.\n//\n// [err]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500\nviceroy_test!(five_hundred_when_guest_panics, |is_component| {\n    let resp = Test::using_fixture(\"panic.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n    Ok(())\n});\n\n// Test that gradually writing to a streaming body works.\nviceroy_test!(responses_can_be_streamed_downstream, |is_component| {\n    let mut resp = Test::using_fixture(\"streaming-response.wasm\")\n        .adapt_component(is_component)\n        .via_hyper()\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        resp.headers()\n            .contains_key(hyper::header::TRANSFER_ENCODING)\n    );\n    assert!(!resp.headers().contains_key(hyper::header::CONTENT_LENGTH));\n\n    // accumulate the entire body to a vector\n    let mut body = Vec::new();\n    while let Some(chunk) = resp.data().await {\n        body.extend_from_slice(&chunk.unwrap());\n    }\n\n    // work with the body as a string, breaking it into lines\n    let body_str = String::from_utf8(body).unwrap();\n    let mut i: u32 = 0;\n    for line in body_str.lines() {\n        assert_eq!(line, i.to_string());\n        i += 1;\n    }\n    assert_eq!(i, 1000);\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/shielding.rs",
    "content": "use crate::common::{Test, TestResult};\nuse crate::viceroy_test;\nuse hyper::service::{make_service_fn, service_fn};\nuse hyper::{Body, Request, Response, Server, StatusCode, body};\nuse std::convert::Infallible;\nuse std::net::SocketAddr;\n\nviceroy_test!(shielding_running_on, |is_component| {\n    const FASTLY_TOML: &str = r#\"\n        name = \"shielding_running_on\"\n        description = \"test that running_on works\"\n        authors = [\"Test user <test_user@fastly.com>\"]\n        language = \"rust\"\n        [local_server.shielding_sites]\n        \"pdx-or-us\" = \"Local\"\n        \"bfi-wa-us\".unencrypted = \"http://localhost\"\n        \"bfi-wa-us\".encrypted = \"https://localhost\"\n    \"#;\n    let test = Test::using_fixture(\"shielding.wasm\")\n        .using_fastly_toml(FASTLY_TOML)?\n        .adapt_component(is_component);\n\n    let resp1 = test\n        .against(\n            Request::get(\"/is-shield\")\n                .header(\"shield\", \"waffle-cone\")\n                .body(\"\")?,\n        )\n        .await?;\n    assert_eq!(StatusCode::INTERNAL_SERVER_ERROR, resp1.status());\n\n    let resp2 = test\n        .against(\n            Request::get(\"/is-shield\")\n                .header(\"shield\", \"pdx-or-us\")\n                .body(\"\")?,\n        )\n        .await?;\n    assert_eq!(StatusCode::OK, resp2.status());\n    let body = body::to_bytes(resp2.into_body()).await.unwrap().to_vec();\n    let string = String::from_utf8(body).unwrap();\n    assert_eq!(\"true\", &string);\n\n    let resp3 = test\n        .against(\n            Request::get(\"/is-shield\")\n                .header(\"shield\", \"bfi-wa-us\")\n                .body(\"\")?,\n        )\n        .await?;\n    assert_eq!(StatusCode::OK, resp3.status());\n    let body = body::to_bytes(resp3.into_body()).await.unwrap().to_vec();\n    let string = String::from_utf8(body).unwrap();\n    assert_eq!(\"false\", &string);\n\n    Ok(())\n});\n\nviceroy_test!(shield_backends, |is_component| {\n    let blank_addr = SocketAddr::from(([127, 0, 0, 1], 0));\n\n    let make_service_unenc = make_service_fn(|_conn| async {\n        Ok::<_, Infallible>(service_fn(|_: Request<Body>| async {\n            Ok::<Response<Body>, std::io::Error>(Response::new(Body::from(\"unencrypted land\")))\n        }))\n    });\n\n    let make_service_enc = make_service_fn(|_conn| async {\n        Ok::<_, Infallible>(service_fn(|_: Request<Body>| async {\n            Ok::<Response<Body>, std::io::Error>(Response::new(Body::from(\"encrypted land\")))\n        }))\n    });\n\n    let unenc_server = Server::bind(&blank_addr).serve(make_service_unenc);\n    let enc_server = Server::bind(&blank_addr).serve(make_service_enc);\n\n    let unenc_addr = unenc_server.local_addr();\n    let enc_addr = enc_server.local_addr();\n\n    let mut server_set = tokio::task::JoinSet::new();\n    server_set.spawn(unenc_server);\n    server_set.spawn(enc_server);\n\n    let fastly_toml = format!(\n        r#\"\n        name = \"shielding_running_on\"\n        description = \"test that running_on works\"\n        authors = [\"Test user <test_user@fastly.com>\"]\n        language = \"rust\"\n        [local_server.shielding_sites]\n        \"pdx-or-us\" = \"Local\"\n        \"bfi-wa-us\".unencrypted = \"http://{}\"\n        \"bfi-wa-us\".encrypted = \"http://{}\"\n    \"#,\n        unenc_addr, enc_addr\n    );\n\n    let test = Test::using_fixture(\"shielding.wasm\")\n        .using_fastly_toml(&fastly_toml)?\n        .adapt_component(is_component);\n\n    let resp1 = test\n        .against(\n            Request::get(\"/shield-to\")\n                .header(\"shield\", \"bfi-wa-us\")\n                .header(\"shield-type\", \"unencrypted\")\n                .body(\"\")?,\n        )\n        .await?;\n    assert_eq!(StatusCode::OK, resp1.status());\n    let body = body::to_bytes(resp1.into_body()).await.unwrap().to_vec();\n    let string = String::from_utf8(body).unwrap();\n    assert_eq!(\"unencrypted land\", &string);\n\n    let resp2 = test\n        .against(\n            Request::get(\"/shield-to\")\n                .header(\"shield\", \"bfi-wa-us\")\n                .header(\"shield-type\", \"encrypted\")\n                .body(\"\")?,\n        )\n        .await?;\n    assert_eq!(StatusCode::OK, resp2.status());\n    let body = body::to_bytes(resp2.into_body()).await.unwrap().to_vec();\n    let string = String::from_utf8(body).unwrap();\n    assert_eq!(\"encrypted land\", &string);\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/sleep.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{StatusCode, body::to_bytes};\n\n// Run a program that only sleeps. This exercises async functionality in wasi.\n// Check that an empty response is sent downstream by default.\n//\n// `sleep.wasm` is a guest program which sleeps for 100 milliseconds,then returns.\nviceroy_test!(empty_ok_response_by_default_after_sleep, |is_component| {\n    let resp = Test::using_fixture(\"sleep.wasm\")\n        .adapt_component(is_component)\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert!(\n        to_bytes(resp.into_body())\n            .await\n            .expect(\"can read body\")\n            .to_vec()\n            .is_empty()\n    );\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/unknown_import_behavior.rs",
    "content": "use hyper::{Request, Response, StatusCode};\nuse viceroy_lib::config::UnknownImportBehavior;\n\nuse crate::common::{Test, TestResult};\n\n/// A test using the default behavior, where the unknown import will fail to link.\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn default_behavior_link_failure() -> TestResult {\n    let res = Test::using_fixture(\"unknown-import.wasm\")\n        .against_empty()\n        .await;\n\n    let err = res.expect_err(\"should be a link failure\");\n    assert!(\n        err.to_string()\n            .contains(\"unknown import: `unknown_module::unknown_function` has not been defined\")\n    );\n\n    Ok(())\n}\n\n/// A test using the trap behavior, where calling the unknown import will cause a runtime trap.\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn trap_behavior_function_called() -> TestResult {\n    let resp = Test::using_fixture(\"unknown-import.wasm\")\n        .using_unknown_import_behavior(UnknownImportBehavior::Trap)\n        .against(Request::get(\"/\").header(\"call-it\", \"yes\").body(\"\").unwrap())\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n    let body_bytes = hyper::body::to_bytes(resp.into_body()).await?;\n    let body = std::str::from_utf8(&body_bytes)?;\n    // The backtrace contains things like stack addresses and gensyms, so we just look for a couple\n    // key parts that should be relatively stable across invocations and wasmtime\n    // versions. Fundamentally though, we're still making assertions based on pretty-printed errors,\n    // so beware of trivial breakages.\n    assert!(\n        body.contains(\"error while executing at wasm backtrace\"),\n        \"missing expected string: {body:?}\"\n    );\n    assert!(\n        body.contains(\"unknown_import::main::\"),\n        \"missing expected string: {body:?}\"\n    );\n\n    Ok(())\n}\n\n/// A test using the trap behavior, where not calling the function means execution proceeds normally.\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn trap_behavior_function_not_called() -> TestResult {\n    let resp = Test::using_fixture(\"unknown-import.wasm\")\n        .backend(\"TheOrigin\", \"/\", None, |_req| {\n            Response::builder()\n                .status(StatusCode::OK)\n                .body(vec![])\n                .unwrap()\n        })\n        .await\n        .using_unknown_import_behavior(UnknownImportBehavior::Trap)\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n\n    Ok(())\n}\n"
  },
  {
    "path": "cli/tests/integration/upstream.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{\n        Request, Response, StatusCode,\n        header::{self, HeaderValue},\n    },\n};\n\nviceroy_test!(upstream_sync, |is_component| {\n    ////////////////////////////////////////////////////////////////////////////////////\n    // Setup\n    ////////////////////////////////////////////////////////////////////////////////////\n\n    // Set up the test harness\n    let test = Test::using_fixture(\"upstream.wasm\")\n        .adapt_component(is_component)\n        // The \"origin\" backend simply echos the request body\n        .backend(\"origin\", \"/\", None, |req| {\n            let body = req.into_body();\n            Response::new(body)\n        })\n        .await\n        // The \"prefix-*\" backends return the request URL as the response body\n        .backend(\"prefix-hello\", \"/hello\", None, |req| {\n            let body = req.uri().to_string().into_bytes();\n            Response::new(body)\n        })\n        .await\n        .backend(\"prefix-hello-slash\", \"/hello/\", None, |req| {\n            let body = req.uri().to_string().into_bytes();\n            Response::new(body)\n        })\n        .await;\n\n    ////////////////////////////////////////////////////////////////////////////////////\n    // A simple round-trip echo test to \"origin\"\n    ////////////////////////////////////////////////////////////////////////////////////\n\n    let resp = test\n        .against(\n            Request::post(\"/\")\n                .header(\"Viceroy-Backend\", \"origin\")\n                .body(\"Hello, Viceroy!\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        resp.into_body().read_into_string().await?,\n        \"Hello, Viceroy!\"\n    );\n\n    ////////////////////////////////////////////////////////////////////////////////////\n    // A variety of tests of prefixes and requested URLs, to check that the URL prefix is\n    // stitched in properly\n    ////////////////////////////////////////////////////////////////////////////////////\n\n    let resp = test\n        .against(\n            Request::get(\"/\")\n                .header(\"Viceroy-Backend\", \"prefix-hello\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(resp.into_body().read_into_string().await?, \"/hello/\");\n\n    let resp = test\n        .against(\n            Request::get(\"/\")\n                .header(\"Viceroy-Backend\", \"prefix-hello-slash\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(resp.into_body().read_into_string().await?, \"/hello/\");\n\n    let resp = test\n        .against(\n            Request::get(\"/greeting.html\")\n                .header(\"Viceroy-Backend\", \"prefix-hello\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        resp.into_body().read_into_string().await?,\n        \"/hello/greeting.html\"\n    );\n\n    let resp = test\n        .against(\n            Request::get(\"/greeting.html\")\n                .header(\"Viceroy-Backend\", \"prefix-hello-slash\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        resp.into_body().read_into_string().await?,\n        \"/hello/greeting.html\"\n    );\n\n    ////////////////////////////////////////////////////////////////////////////////////\n    // Test that nonexistent backends produce an error\n    ////////////////////////////////////////////////////////////////////////////////////\n\n    let resp = test\n        .against(\n            Request::get(\"/greeting.html\")\n                .header(\"Viceroy-Backend\", \"nonsense\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n    assert!(resp.status().is_server_error());\n\n    Ok(())\n});\n\nviceroy_test!(override_host_works, |is_component| {\n    // Set up the test harness\n    let test = Test::using_fixture(\"upstream.wasm\")\n        .adapt_component(is_component)\n        .backend(\"override-host\", \"/\", Some(\"otherhost.com\"), |req| {\n            assert_eq!(\n                req.headers().get(header::HOST),\n                Some(&HeaderValue::from_static(\"otherhost.com\"))\n            );\n            Response::new(vec![])\n        })\n        .await;\n\n    let resp = test\n        .via_hyper()\n        .against(\n            Request::get(\"/override\")\n                .header(\"Viceroy-Backend\", \"override-host\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n\n    Ok(())\n});\n\n// Test that we can transparently gunzip responses when required.\nviceroy_test!(transparent_gunzip, |is_component| {\n    let resp = Test::using_fixture(\"gzipped-response.wasm\")\n        .adapt_component(is_component)\n        .backend(\"echo\", \"/\", None, |mut req| {\n            let mut response_builder = Response::builder();\n\n            for (key, value) in req.headers_mut().drain() {\n                if let Some(real_key) = key {\n                    response_builder = response_builder.header(real_key, value);\n                }\n            }\n\n            response_builder\n                .status(StatusCode::OK)\n                .body(req.into_body())\n                .unwrap()\n        })\n        .await\n        .against_empty()\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        \"hello, world!\\n\",\n        resp.into_body().read_into_string().await.unwrap()\n    );\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/upstream_async.rs",
    "content": "use std::sync::Arc;\n\nuse tokio::sync::Semaphore;\n\nuse {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{Response, StatusCode},\n};\n\nviceroy_test!(upstream_async_methods, |is_component| {\n    // Set up two backends that share a semaphore that starts with zero permits. `backend1` must\n    // take a semaphore permit and then \"forget\" it before returning its response. `backend2` adds a\n    // permit to the semaphore and promptly returns. This relationship allows the test fixtures to\n    // examine the behavior of the various pending request operators beyond just whether they\n    // eventually return the expected response.\n    let sema_backend1 = Arc::new(Semaphore::new(0));\n    let sema_backend2 = sema_backend1.clone();\n    let test = Test::using_fixture(\"upstream-async.wasm\")\n        .adapt_component(is_component)\n        .async_backend(\"backend1\", \"/\", None, move |_| {\n            let sema_backend1 = sema_backend1.clone();\n            Box::new(async move {\n                sema_backend1.acquire().await.unwrap().forget();\n                Response::builder()\n                    .header(\"Backend-1-Response\", \"\")\n                    .status(StatusCode::OK)\n                    .body(hyper::Body::empty())\n                    .unwrap()\n            })\n        })\n        .await\n        .async_backend(\"backend2\", \"/\", None, move |_| {\n            let sema_backend2 = sema_backend2.clone();\n            Box::new(async move {\n                sema_backend2.add_permits(1);\n                Response::builder()\n                    .header(\"Backend-2-Response\", \"\")\n                    .status(StatusCode::OK)\n                    .body(hyper::Body::empty())\n                    .unwrap()\n            })\n        })\n        .await;\n\n    // The meat of the test is on the guest side; we just check that we made it through successfully\n    let resp = test.against_empty().await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/upstream_dynamic.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{\n        Request, Response, StatusCode,\n        header::{self, HeaderValue},\n    },\n};\n\nviceroy_test!(upstream_sync, |is_component| {\n    ////////////////////////////////////////////////////////////////////////////////////\n    // Setup\n    ////////////////////////////////////////////////////////////////////////////////////\n\n    // Set up the test harness\n    let test = Test::using_fixture(\"upstream-dynamic.wasm\")\n        .adapt_component(is_component)\n        // The \"origin\" backend simply echos the request body\n        .backend(\"origin\", \"/\", None, |req| {\n            let body = req.into_body();\n            Response::new(body)\n        })\n        .await;\n\n    ////////////////////////////////////////////////////////////////////////////////////\n    // A simple round-trip echo test to \"origin\", but with a dynamic backend\n    ////////////////////////////////////////////////////////////////////////////////////\n\n    // Make sure the backends are started so we can know where to direct the requests\n    test.start_backend_servers().await;\n    let backend_uri = test.uri_for_backend_server(\"origin\").await;\n\n    let resp = test\n        .against(\n            Request::post(\"/\")\n                .header(\"Dynamic-Backend\", backend_uri.authority().unwrap().as_str())\n                .body(\"Hello, Viceroy!\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        resp.into_body().read_into_string().await?,\n        \"Hello, Viceroy!\"\n    );\n\n    ////////////////////////////////////////////////////////////////////////////////////\n    // Test that you can still use standard backends without a problem\n    ////////////////////////////////////////////////////////////////////////////////////\n\n    let resp = test\n        .against(\n            Request::post(\"/\")\n                .header(\"Static-Backend\", \"origin\")\n                .body(\"Hello, Viceroy!\")\n                .unwrap(),\n        )\n        .await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n    assert_eq!(\n        resp.into_body().read_into_string().await?,\n        \"Hello, Viceroy!\"\n    );\n\n    Ok(())\n});\n\nviceroy_test!(override_host_works, |is_component| {\n    // Set up the test harness\n    let test = Test::using_fixture(\"upstream-dynamic.wasm\")\n        .adapt_component(is_component)\n        .backend(\n            \"override-host\",\n            \"/\",\n            None, // Some(\"otherhost.com\"),\n            |req| {\n                assert_eq!(\n                    req.headers().get(header::HOST),\n                    Some(&HeaderValue::from_static(\"otherhost.com\"))\n                );\n                Response::new(vec![])\n            },\n        )\n        .await;\n    // Make sure the backends are started so we can know where to direct the request\n    test.start_backend_servers().await;\n    let backend_uri = test.uri_for_backend_server(\"override-host\").await;\n\n    let resp = test\n        .via_hyper()\n        .against(\n            Request::get(\"/override\")\n                .header(\"Dynamic-Backend\", backend_uri.authority().unwrap().as_str())\n                .header(\"With-Override\", \"otherhost.com\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n\n    Ok(())\n});\n\nviceroy_test!(duplication_errors_right, |is_component| {\n    // Set up the test harness\n    let test = Test::using_fixture(\"upstream-dynamic.wasm\")\n        .adapt_component(is_component)\n        .backend(\"static\", \"/\", None, |_| Response::new(vec![]))\n        .await;\n    // Make sure the backends are started so we can know where to direct the request\n    test.start_backend_servers().await;\n    let backend_uri = test.uri_for_backend_server(\"static\").await;\n    let backend_authority = backend_uri.authority().unwrap().as_str();\n\n    let resp = test\n        .against(\n            Request::get(\"/override\")\n                .header(\"Dynamic-Backend\", backend_authority)\n                .header(\"Supplementary-Backend\", \"dynamic-backend\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::CONFLICT);\n\n    let resp = test\n        .against(\n            Request::get(\"/override\")\n                .header(\"Dynamic-Backend\", backend_authority)\n                .header(\"Supplementary-Backend\", \"static\")\n                .body(\"\")\n                .unwrap(),\n        )\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::CONFLICT);\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/upstream_streaming.rs",
    "content": "use {\n    crate::{\n        common::{Test, TestResult},\n        viceroy_test,\n    },\n    hyper::{Response, StatusCode, body::HttpBody},\n};\n\n// Test that guests can stream a body into an upstream request.\nviceroy_test!(upstream_streaming, |is_component| {\n    // Set up the test harness\n    let test = Test::using_fixture(\"upstream-streaming.wasm\")\n        .adapt_component(is_component)\n        // The \"origin\" backend simply echos the request body\n        .backend(\"origin\", \"/\", None, |req| Response::new(req.into_body()))\n        .await;\n\n    // Test with an empty request\n    let mut resp = test.against_empty().await?;\n    assert_eq!(resp.status(), StatusCode::OK);\n\n    // accumulate the entire body to a vector\n    let mut body = Vec::new();\n    while let Some(chunk) = resp.data().await {\n        body.extend_from_slice(&chunk?);\n    }\n\n    // work with the body as a string, breaking it into lines\n    let body_str = String::from_utf8(body).unwrap();\n    let mut i: u32 = 0;\n    for line in body_str.lines() {\n        assert_eq!(line, i.to_string());\n        i += 1;\n    }\n    assert_eq!(i, 1000);\n\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/integration/vcpu_time.rs",
    "content": "use crate::{\n    common::{Test, TestResult},\n    viceroy_test,\n};\nuse hyper::{Request, Response, StatusCode};\n\nviceroy_test!(vcpu_time_getter_works, |is_component| {\n    let req = Request::get(\"/\")\n        .header(\"Accept\", \"text/html\")\n        .body(\"Hello, world!\")\n        .unwrap();\n\n    let resp = Test::using_fixture(\"vcpu_time_test.wasm\")\n        .adapt_component(is_component)\n        .backend(\"slow-server\", \"/\", None, |_| {\n            std::thread::sleep(std::time::Duration::from_millis(4000));\n            Response::builder()\n                .status(StatusCode::OK)\n                .body(vec![])\n                .unwrap()\n        })\n        .await\n        .against(req)\n        .await?;\n\n    assert_eq!(resp.status(), StatusCode::OK);\n    Ok(())\n});\n"
  },
  {
    "path": "cli/tests/trap-test/Cargo.toml",
    "content": "[package]\nname = \"trap-test\"\ndescription = \"A test to exercise the Viceroy functionality in which a hostcall experiences a FatalError and terminates the instance.\"\nversion = \"0.1.0\"\nauthors = []\nedition = \"2024\"\nlicense = \"Apache-2.0 WITH LLVM-exception\"\npublish = false\n\n[dependencies]\nanyhow = \"1.0.31\"\nfutures = \"0.3.0\"\nhttp = \"0.2.1\"\nhyper = \"=0.14.26\"\ntokio = { version = \"1.49.0\", features = [\"full\"] }\ntracing-subscriber = \"0.2.19\"\nviceroy-lib = { path = \"../../..\", features = [\"test-fatalerror-config\"] }\n\n# To indicate to cargo that this trap-test is not a member of the testing workspace specified by\n# ~/cli/tests/fixtures/Cargo.toml, place an empty workspace specification here.\n[workspace]\n"
  },
  {
    "path": "cli/tests/trap-test/src/main.rs",
    "content": "use std::collections::HashSet;\nuse {\n    hyper::{Body, Request, StatusCode},\n    viceroy_lib::{ExecuteCtx, ProfilingStrategy, WasmFeatures},\n};\n\n/// A shorthand for the path to our test fixtures' build artifacts for Rust tests.\nconst RUST_FIXTURE_PATH: &str = \"../../../test-fixtures/target/wasm32-wasip1/debug/\";\n\n/// A catch-all error, so we can easily use `?` in test cases.\npub type Error = Box<dyn std::error::Error + Send + Sync>;\n\n/// Handy alias for the return type of async Tokio tests\npub type TestResult = Result<(), Error>;\n\nasync fn fatal_error_traps_impl(adapt_core_wasm: bool) -> TestResult {\n    let module_path = format!(\"{RUST_FIXTURE_PATH}/response.wasm\");\n    let ctx = ExecuteCtx::new(\n        module_path,\n        ProfilingStrategy::None,\n        HashSet::new(),\n        None,\n        viceroy_lib::config::UnknownImportBehavior::LinkError,\n        adapt_core_wasm,\n        WasmFeatures::default(),\n    )?;\n    let req = Request::get(\"http://127.0.0.1:7676/\").body(Body::from(\"\"))?;\n    let local = \"127.0.0.1:80\".parse().unwrap();\n    let remote = \"127.0.0.1:0\".parse().unwrap();\n    let resp = ctx\n        .handle_request_with_runtime_error(req, local, remote)\n        .await?;\n\n    // The Guest was terminated and so should return a 500.\n    assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);\n\n    // Examine the cause in the body and assert that it is the expected\n    // Trap error supplied by the Guest.\n\n    let body = resp.into_body().read_into_string().await?;\n    let needle = \"Fatal error: [A fatal error occurred in the test-only implementation of header_values_get]\";\n    assert!(body.contains(needle), \"body missing expected string: {body}\");\n\n    Ok(())\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn fatal_error_traps() -> TestResult {\n    fatal_error_traps_impl(false).await\n}\n\n#[tokio::test(flavor = \"multi_thread\")]\nasync fn fatal_error_traps_component() -> TestResult {\n    fatal_error_traps_impl(true).await\n}\n"
  },
  {
    "path": "cli/tests/wasm/exit_code_4.wat",
    "content": "(module\n  (import \"wasi_snapshot_preview1\" \"proc_exit\"\n  (func $proc_exit (param i32)))\n  (export \"_start\" (func $_start))\n  (memory 10)\n  (export \"memory\" (memory 0))\n\n  (func $_start\n    (call $proc_exit (i32.const 4))\n  )\n)"
  },
  {
    "path": "cli/tests/wasm/invalid.wat",
    "content": "This is not a valid wat file\n"
  },
  {
    "path": "cli/tests/wasm/minimal.wat",
    "content": "(module)\n"
  },
  {
    "path": "cli/tests/wasm/trapping.wat",
    "content": "(module\n  (export \"_start\" (func $_start))\n  (func $_start\n    unreachable\n  )\n)"
  },
  {
    "path": "doc/RELEASING.md",
    "content": "# Releasing Viceroy\n\nBelow are the steps needed to do a Viceroy release:\n\n1. Make sure the Viceroy version has been bumped up to the current release\n   version. You might need to bump the minor version (e.g. 0.2.0 to 0.3.0) if\n   there are any semver breaking changes. Review the changes since the last\n   release just to be sure.\n1. Update the `Cargo.lock` files by running `make generate-lockfile`.\n1. Update `CHANGELOG.md` so that it contains all of the updates since the\n   previous version as its own commit. Remove the \"## Unreleased\" header.\n1. Create a local branch in the form `release-x.y.z` where `x`, `y`, and `z` are\n   the major, minor, and patch versions of Viceroy and have the tip of the\n   branch contain the Changelog commit.\n1. Run `make ci` locally to make sure that everything will pass before pushing\n   the branch and opening up a PR.\n1. After you get approval, run `git tag vx.y.z HEAD && git push origin vx.y.z`.\n   Pushing this tag will kick off a build for all of the release artifacts, create\n   a release on GitHub, and publish the crates to crates.io.\n1. Now, we should return to our release PR.\n    1. Update the version fields in `Cargo.toml` and `cli/Cargo.toml` to the\n     next patch version (so `z + 1`).\n    1. Update the dependency on `viceroy-lib` in `cli/Cargo.toml` to the next\n     patch version.\n    1. Update all the lockfiles by running `make generate-lockfile`.\n    1. Restore the `## Unreleased` header at the top of `CHANGELOG.md`.\n1. Get another approval and do a merge commit (not a squash) when CI passes. We don't squash because we want the tagged commit to be contained within the `main` branch\n"
  },
  {
    "path": "proptest-regressions/body.txt",
    "content": "# Seeds for failure cases proptest has generated in the past. It is\n# automatically read and these particular cases re-run before any\n# novel cases are generated.\n#\n# It is recommended to check this file in to source control so that\n# everyone who runs the test benefits from these saved cases.\ncc 0008d74790fb20a629ca4cc6f2c016c4a944c17d3059ee654ef4a48595b1fd0f # shrinks to body = Channel(Receiver { chan: Rx { inner: Chan { tx: Tx { block_tail: 0x760140044890, tail_position: 3 }, semaphore: Semaphore { semaphore: Semaphore { permits: 0 }, bound: 2 }, rx_waker: AtomicWaker, tx_count: 0, rx_fields: \"...\" } } })\ncc c1b60ad59f679fb3a4fc4bc96c50acedfc321467a9c0792f66321938054c0cad # shrinks to (body, chunk_lengths) = (b\"\\xe8\\xfdiT3\\x1dyr\", [0])\n"
  },
  {
    "path": "proptest-regressions/cache.txt",
    "content": "# Seeds for failure cases proptest has generated in the past. It is\n# automatically read and these particular cases re-run before any\n# novel cases are generated.\n#\n# It is recommended to check this file in to source control so that\n# everyone who runs the test benefits from these saved cases.\ncc 59cac97c28e31b87da4163d47031bc52ca7e07f8e90887911c569a29fbdc75c0 # shrinks to key = CacheKey([]), max_age = 9223372036854481161.46386714s, initial_age = None, value = []\n"
  },
  {
    "path": "proptest-regressions/collecting_body.txt",
    "content": "# Seeds for failure cases proptest has generated in the past. It is\n# automatically read and these particular cases re-run before any\n# novel cases are generated.\n#\n# It is recommended to check this file in to source control so that\n# everyone who runs the test benefits from these saved cases.\ncc c36f255c0841ebe6cdb08ce10d8e5a6f12777d08a3aa4b76dd9c65c3f0dfdd27 # shrinks to (body, start) = ([b\"\\0\"], 0)\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"1.95\"\ncomponents = [ \"rustfmt\", \"clippy\" ]\ntargets = [ \"wasm32-wasip1\", \"wasm32-wasip2\", \"wasm32-unknown-unknown\" ]\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "# Viceroy uses the default formatting settings.\n"
  },
  {
    "path": "src/acl.rs",
    "content": "use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};\nuse std::collections::HashMap;\nuse std::fmt::Display;\nuse std::net::{IpAddr, Ipv4Addr, Ipv6Addr};\nuse std::sync::Arc;\n\n/// Acls is a mapping of names to acl.\n#[derive(Clone, Debug, Default)]\npub struct Acls {\n    acls: HashMap<String, Arc<Acl>>,\n}\n\nimpl Acls {\n    pub fn new() -> Self {\n        Self {\n            acls: HashMap::new(),\n        }\n    }\n\n    pub fn get_acl(&self, name: &str) -> Option<&Arc<Acl>> {\n        self.acls.get(name)\n    }\n\n    pub fn insert(&mut self, name: String, acl: Acl) {\n        self.acls.insert(name, Arc::new(acl));\n    }\n}\n\n/// An acl is a collection of acl entries.\n///\n/// The JSON representation of this struct intentionally matches the JSON\n/// format used to create/update ACLs via api.fastly.com. The goal being\n/// to allow users to use the same JSON in Viceroy as in production.\n///\n/// Example:\n///\n/// ```json\n///    { \"entries\": [\n///        { \"op\": \"create\", \"prefix\": \"1.2.3.0/24\", \"action\": \"BLOCK\" },\n///        { \"op\": \"create\", \"prefix\": \"23.23.23.23/32\", \"action\": \"ALLOW\" },\n///        { \"op\": \"update\", \"prefix\": \"FACE::/32\", \"action\": \"ALLOW\" }\n///    ]}\n/// ```\n///\n/// Note that, in Viceroy, the `op` field is ignored.\n#[derive(Debug, Default, Deserialize)]\npub struct Acl {\n    pub(crate) entries: Vec<Entry>,\n}\n\nimpl Acl {\n    /// Lookup performs a naive lookup of the given IP address\n    /// over the acls entries.\n    ///\n    /// If the IP matches multiple ACL entries, then:\n    /// - The most specific match is returned (longest mask),\n    /// - and in case of a tie, the last entry wins.\n    pub fn lookup(&self, ip: IpAddr) -> Option<&Entry> {\n        self.entries.iter().fold(None, |acc, entry| {\n            if let Some(mask) = entry.prefix.is_match(ip)\n                && acc.is_none_or(|prev_match: &Entry| mask >= prev_match.prefix.mask)\n            {\n                return Some(entry);\n            }\n            acc\n        })\n    }\n}\n\n/// An entry is an IP prefix and its associated action.\n#[derive(Debug, Deserialize, Serialize, PartialEq)]\npub struct Entry {\n    prefix: Prefix,\n    action: Action,\n}\n\n/// A prefix is an IP and network mask.\n#[derive(Debug, PartialEq)]\npub struct Prefix {\n    ip: IpAddr,\n    mask: u8,\n}\n\nimpl Prefix {\n    pub(crate) fn new(ip: IpAddr, mask: u8) -> Self {\n        // Normalize IP based on mask.\n        let (ip, mask) = match ip {\n            IpAddr::V4(v4) => {\n                let mask = mask.clamp(1, 32);\n                let bit_mask = u32::MAX << (32 - mask);\n                (\n                    IpAddr::V4(Ipv4Addr::from_bits(v4.to_bits() & bit_mask)),\n                    mask,\n                )\n            }\n            IpAddr::V6(v6) => {\n                let mask = mask.clamp(1, 128);\n                let bit_mask = u128::MAX << (128 - mask);\n                (\n                    IpAddr::V6(Ipv6Addr::from_bits(v6.to_bits() & bit_mask)),\n                    mask,\n                )\n            }\n        };\n\n        Self { ip, mask }\n    }\n\n    /// If the given IP matches the prefix, then the prefix's\n    /// mask is returned.\n    pub(crate) fn is_match(&self, ip: IpAddr) -> Option<u8> {\n        let masked = Self::new(ip, self.mask);\n        if masked.ip == self.ip {\n            Some(self.mask)\n        } else {\n            None\n        }\n    }\n}\n\nimpl Display for Prefix {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_fmt(format_args!(\"{}/{}\", self.ip, self.mask))\n    }\n}\n\nimpl<'de> Deserialize<'de> for Prefix {\n    fn deserialize<D>(de: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let v = String::deserialize(de)?;\n        let (ip, mask) = v.split_once('/').ok_or(D::Error::custom(format!(\n            \"invalid format '{}': want IP/MASK\",\n            v\n        )))?;\n\n        let mask = mask\n            .parse::<u8>()\n            .map_err(|err| D::Error::custom(format!(\"invalid prefix {}: {}\", mask, err)))?;\n\n        // Detect whether the IP is v4 or v6.\n        let ip = match ip.contains(':') {\n            false => {\n                if !(1..=32).contains(&mask) {\n                    return Err(D::Error::custom(format!(\n                        \"mask outside allowed range [1, 32]: {}\",\n                        mask\n                    )));\n                }\n                ip.parse::<Ipv4Addr>().map(IpAddr::V4)\n            }\n            true => {\n                if !(1..=128).contains(&mask) {\n                    return Err(D::Error::custom(format!(\n                        \"mask outside allowed range [1, 128]: {}\",\n                        mask\n                    )));\n                }\n                ip.parse::<Ipv6Addr>().map(IpAddr::V6)\n            }\n        }\n        .map_err(|err| D::Error::custom(format!(\"invalid ip address {}: {}\", ip, err)))?;\n\n        Ok(Self::new(ip, mask))\n    }\n}\n\nimpl Serialize for Prefix {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        serializer.serialize_str(format!(\"{}\", self).as_str())\n    }\n}\n\nconst ACTION_ALLOW: &str = \"ALLOW\";\nconst ACTION_BLOCK: &str = \"BLOCK\";\n\n/// An action for a prefix.\n#[derive(Clone, Debug, PartialEq)]\npub enum Action {\n    Allow,\n    Block,\n    Other(String),\n}\n\nimpl<'de> Deserialize<'de> for Action {\n    fn deserialize<D>(de: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let action = String::deserialize(de)?;\n        Ok(match action.to_uppercase().as_str() {\n            ACTION_ALLOW => Self::Allow,\n            ACTION_BLOCK => Self::Block,\n            _ => Self::Other(action),\n        })\n    }\n}\n\nimpl Serialize for Action {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        match self {\n            Self::Allow => serializer.serialize_str(ACTION_ALLOW),\n            Self::Block => serializer.serialize_str(ACTION_BLOCK),\n            Self::Other(other) => serializer.serialize_str(format!(\"Other({})\", other).as_str()),\n        }\n    }\n}\n\n#[test]\nfn prefix_is_match() {\n    let prefix = Prefix::new(Ipv4Addr::new(192, 168, 100, 0).into(), 16);\n\n    assert_eq!(\n        prefix.is_match(Ipv4Addr::new(192, 168, 100, 0).into()),\n        Some(16)\n    );\n    assert_eq!(\n        prefix.is_match(Ipv4Addr::new(192, 168, 200, 200).into()),\n        Some(16)\n    );\n\n    assert_eq!(prefix.is_match(Ipv4Addr::new(192, 167, 0, 0).into()), None);\n    assert_eq!(prefix.is_match(Ipv4Addr::new(192, 169, 0, 0).into()), None);\n\n    let prefix = Prefix::new(Ipv6Addr::new(0xFACE, 0, 0, 0, 0, 0, 0, 0).into(), 16);\n    assert_eq!(\n        prefix.is_match(Ipv6Addr::new(0xFACE, 1, 2, 3, 4, 5, 6, 7).into()),\n        Some(16)\n    );\n\n    let v4 = Ipv4Addr::new(192, 168, 200, 200);\n    let v4_as_v6 = v4.to_ipv6_mapped();\n\n    assert_eq!(Prefix::new(v4.into(), 8).is_match(v4_as_v6.into()), None);\n    assert_eq!(Prefix::new(v4_as_v6.into(), 8).is_match(v4.into()), None);\n}\n\n#[test]\nfn acl_lookup() {\n    let acl = Acl {\n        entries: vec![\n            Entry {\n                prefix: Prefix::new(Ipv4Addr::new(192, 168, 100, 0).into(), 16),\n                action: Action::Block,\n            },\n            Entry {\n                prefix: Prefix::new(Ipv4Addr::new(192, 168, 100, 0).into(), 24),\n                action: Action::Block,\n            },\n            Entry {\n                prefix: Prefix::new(Ipv4Addr::new(192, 168, 100, 0).into(), 8),\n                action: Action::Block,\n            },\n        ],\n    };\n\n    match acl.lookup(Ipv4Addr::new(192, 168, 100, 1).into()) {\n        Some(lookup_match) => {\n            assert_eq!(acl.entries[1], *lookup_match);\n        }\n        None => panic!(\"expected lookup match\"),\n    };\n\n    match acl.lookup(Ipv4Addr::new(192, 168, 200, 1).into()) {\n        Some(lookup_match) => {\n            assert_eq!(acl.entries[0], *lookup_match);\n        }\n        None => panic!(\"expected lookup match\"),\n    };\n\n    match acl.lookup(Ipv4Addr::new(192, 1, 1, 1).into()) {\n        Some(lookup_match) => {\n            assert_eq!(acl.entries[2], *lookup_match);\n        }\n        None => panic!(\"expected lookup match\"),\n    };\n\n    if let Some(lookup_match) = acl.lookup(Ipv4Addr::new(1, 1, 1, 1).into()) {\n        panic!(\"expected no lookup match, got {:?}\", lookup_match)\n    };\n}\n\n#[test]\nfn acl_json_parse() {\n    // In the following JSON, the `op` field should be ignored. It's included\n    // to assert that the JSON format used with api.fastly.com to create/modify\n    // ACLs can be used in Viceroy as well.\n    let input = r#\"\n    { \"entries\": [\n        { \"op\": \"create\", \"prefix\": \"1.2.3.0/24\", \"action\": \"BLOCK\" },\n        { \"op\": \"update\", \"prefix\": \"192.168.0.0/16\", \"action\": \"BLOCK\" },\n        { \"op\": \"create\", \"prefix\": \"23.23.23.23/32\", \"action\": \"ALLOW\" },\n        { \"op\": \"update\", \"prefix\": \"1.2.3.4/32\", \"action\": \"ALLOW\" },\n        { \"op\": \"update\", \"prefix\": \"1.2.3.4/8\", \"action\": \"ALLOW\" }\n    ]}\n    \"#;\n    let acl: Acl = serde_json::from_str(input).expect(\"can decode\");\n\n    let want = vec![\n        Entry {\n            prefix: Prefix {\n                ip: IpAddr::V4(Ipv4Addr::new(1, 2, 3, 0)),\n                mask: 24,\n            },\n            action: Action::Block,\n        },\n        Entry {\n            prefix: Prefix {\n                ip: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)),\n                mask: 16,\n            },\n            action: Action::Block,\n        },\n        Entry {\n            prefix: Prefix {\n                ip: IpAddr::V4(Ipv4Addr::new(23, 23, 23, 23)),\n                mask: 32,\n            },\n            action: Action::Allow,\n        },\n        Entry {\n            prefix: Prefix {\n                ip: IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),\n                mask: 32,\n            },\n            action: Action::Allow,\n        },\n        Entry {\n            prefix: Prefix {\n                ip: IpAddr::V4(Ipv4Addr::new(1, 0, 0, 0)),\n                mask: 8,\n            },\n            action: Action::Allow,\n        },\n    ];\n\n    assert_eq!(acl.entries, want);\n}\n\n#[test]\nfn prefix_json_roundtrip() {\n    let assert_roundtrips = |input: &str, want: &str| {\n        let prefix: Prefix =\n            serde_json::from_str(format!(\"\\\"{}\\\"\", input).as_str()).expect(\"can decode\");\n        let got = serde_json::to_string(&prefix).expect(\"can encode\");\n        assert_eq!(\n            got,\n            format!(\"\\\"{}\\\"\", want),\n            \"'{}' roundtrip: got {}, want {}\",\n            input,\n            got,\n            want\n        );\n    };\n\n    assert_roundtrips(\"255.255.255.255/32\", \"255.255.255.255/32\");\n    assert_roundtrips(\"255.255.255.255/8\", \"255.0.0.0/8\");\n\n    assert_roundtrips(\"2002::1234:abcd:ffff:c0a8:101/64\", \"2002:0:0:1234::/64\");\n    assert_roundtrips(\"2000::AB/32\", \"2000::/32\");\n\n    // Invalid prefix.\n    assert!(serde_json::from_str::<Prefix>(\"\\\"1.2.3.4/33\\\"\").is_err());\n    assert!(serde_json::from_str::<Prefix>(\"\\\"200::/129\\\"\").is_err());\n    assert!(serde_json::from_str::<Prefix>(\"\\\"200::/none\\\"\").is_err());\n\n    // Invalid IP.\n    assert!(serde_json::from_str::<Prefix>(\"\\\"1.2.3.four/16\\\"\").is_err());\n    assert!(serde_json::from_str::<Prefix>(\"\\\"200::end/32\\\"\").is_err());\n\n    // Invalid format.\n    assert!(serde_json::from_str::<Prefix>(\"\\\"1.2.3.4\\\"\").is_err());\n    assert!(serde_json::from_str::<Prefix>(\"\\\"200::\\\"\").is_err());\n}\n\n#[test]\nfn action_json_roundtrip() {\n    let assert_roundtrips = |input: &str, want: &str| {\n        let action: Action =\n            serde_json::from_str(format!(\"\\\"{}\\\"\", input).as_str()).expect(\"can decode\");\n        let got = serde_json::to_string(&action).expect(\"can encode\");\n        assert_eq!(\n            got,\n            format!(\"\\\"{}\\\"\", want),\n            \"'{}' roundtrip: got {}, want {}\",\n            input,\n            got,\n            want\n        );\n    };\n\n    assert_roundtrips(\"ALLOW\", \"ALLOW\");\n    assert_roundtrips(\"allow\", \"ALLOW\");\n    assert_roundtrips(\"BLOCK\", \"BLOCK\");\n    assert_roundtrips(\"block\", \"BLOCK\");\n    assert_roundtrips(\"POTATO\", \"Other(POTATO)\");\n    assert_roundtrips(\"potato\", \"Other(potato)\");\n}\n"
  },
  {
    "path": "src/adapt.rs",
    "content": "use anyhow::Context;\n\n/// The full adapter.\nconst ADAPTER_BYTES: &[u8] = include_bytes!(\"../wasm_abi/data/viceroy-component-adapter.wasm\");\nconst ADAPTER_NOSHIFT_BYTES: &[u8] =\n    include_bytes!(\"../wasm_abi/data/viceroy-component-adapter.noshift.wasm\");\n\n/// A version of the adapter that doesn't provide the `http_incoming` export.\n///\n/// This is used by \"library\" components meant to be linked to a main component\n/// that does provide the `http_incoming` export.\nconst LIBRARY_ADAPTER_BYTES: &[u8] =\n    include_bytes!(\"../wasm_abi/data/viceroy-component-adapter.library.wasm\");\nconst LIBRARY_ADAPTER_NOSHIFT_BYTES: &[u8] =\n    include_bytes!(\"../wasm_abi/data/viceroy-component-adapter.library.noshift.wasm\");\n\n/// Check if the bytes represent a core wasm module, or a component.\npub fn is_component(bytes: &[u8]) -> bool {\n    wasmparser::Parser::is_component(bytes)\n}\n\n/// Given bytes that represent a core wasm module in the wat format, adapt it to a component using\n/// the viceroy adapter.\npub fn adapt_wat(wat: &str) -> anyhow::Result<Vec<u8>> {\n    let bytes = wat::parse_str(wat)?;\n    adapt_bytes(&bytes)\n}\n\n/// Given bytes that represent a core wasm module, adapt it to a component using the viceroy\n/// adapter.\npub fn adapt_bytes(bytes: &[u8]) -> anyhow::Result<Vec<u8>> {\n    // Determine if we have a main module or a library module.\n    let library = !has_export(bytes, \"_start\");\n    let needs_no_shift_adapter = has_wit_bindgen_imports(bytes);\n\n    let bytes = if needs_no_shift_adapter {\n        bytes.to_vec()\n    } else {\n        crate::shift_mem::shift_main_module(bytes)?\n    };\n    let module = mangle_imports(&bytes)?;\n\n    let adapter_bytes = match (library, needs_no_shift_adapter) {\n        (true, true) => LIBRARY_ADAPTER_NOSHIFT_BYTES,\n        (true, false) => LIBRARY_ADAPTER_BYTES,\n        (false, true) => ADAPTER_NOSHIFT_BYTES,\n        (false, false) => ADAPTER_BYTES,\n    };\n\n    let component = wit_component::ComponentEncoder::default()\n        .module(module.as_slice())?\n        // NOTE: the adapter uses the module name `wasi_snapshot_preview1` as it was originally a\n        // fork of the wasi_snapshot_preview1 adapter. The wasm has a different name to make the\n        // codebase make more sense, but plumbing that name all the way through the adapter would\n        // require adjusting all preview1 functions to have a mangled name, like\n        // \"wasi_snapshot_preview1#args_get\".\n        .adapter(\"wasi_snapshot_preview1\", adapter_bytes)?\n        .validate(true)\n        .encode()?;\n\n    // Add \"viceroy\" to the producers section.\n    let mut producers = wasm_metadata::Producers::empty();\n    let mut flags = Vec::with_capacity(2);\n    if library {\n        flags.push(\"library\");\n    }\n    if needs_no_shift_adapter {\n        flags.push(\"noshift\");\n    }\n    producers.add(\n        \"processed-by\",\n        \"viceroy adapt\",\n        &format!(\"{} ({})\", env!(\"CARGO_PKG_VERSION\"), flags.join(\", \")),\n    );\n    let component = producers\n        .add_to_wasm(&component)\n        .context(\"failed to add viceroy producer metadata to wasm\")?;\n\n    Ok(component)\n}\n\n/// We need to ensure that the imports of the core wasm module are all remapped to the single\n/// adapter `wasi_snapshot_preview1`, as that allows us to reuse common infrastructure in the\n/// adapter's implementation. To accomplish this, we change imports to all come from the\n/// `wasi_snapshot_preview1` module, and mangle the function name to\n/// `original_module#original_name`.\nfn mangle_imports(bytes: &[u8]) -> anyhow::Result<wasm_encoder::Module> {\n    let mut module = wasm_encoder::Module::new();\n\n    for payload in wasmparser::Parser::new(0).parse_all(bytes) {\n        let payload = payload?;\n        match payload {\n            wasmparser::Payload::Version {\n                encoding: wasmparser::Encoding::Component,\n                ..\n            } => {\n                anyhow::bail!(\"Mangling only supports core-wasm modules, not components\");\n            }\n\n            wasmparser::Payload::ImportSection(section) => {\n                let mut imports = wasm_encoder::ImportSection::new();\n\n                for import in section {\n                    let import = import?;\n                    let entity = wasm_encoder::EntityType::try_from(import.ty).map_err(|_| {\n                        anyhow::anyhow!(\n                            \"Failed to translate type for import {}:{}\",\n                            import.module,\n                            import.name\n                        )\n                    })?;\n\n                    if is_fastly_module(import.module) {\n                        // In order to build a single module that can serve as\n                        // the adapter for the many \"fastly_*\" modules we have,\n                        // as well as the \"env\" module we have, as well as for\n                        // the \"wasi_snapshot_preview1\" module, we mangle\n                        // \"fastly_*\" and \"env\" names and put them into the\n                        // \"wasi_snapshot_preview1\" module.\n                        let module = \"wasi_snapshot_preview1\";\n                        let name = format!(\"{}#{}\", import.module, import.name);\n                        imports.import(module, &name, entity);\n                    } else {\n                        // It's not a \"fastly_\" module, so it may be\n                        // \"wasi_snapshot_preview1\" which we should leave as-is,\n                        // or a wit-bindgen-generated import which doesn't need\n                        // adapting.\n                        imports.import(import.module, import.name, entity);\n                    }\n                }\n\n                module.section(&imports);\n            }\n\n            payload => {\n                if let Some((id, range)) = payload.as_section() {\n                    module.section(&wasm_encoder::RawSection {\n                        id,\n                        data: &bytes[range],\n                    });\n                }\n            }\n        }\n    }\n\n    Ok(module)\n}\n\n/// Test whether `bytes` holds a wasm binary with an export named `wanted`.\nfn has_export(bytes: &[u8], wanted: &str) -> bool {\n    for payload in wasmparser::Parser::new(0).parse_all(bytes) {\n        let Ok(payload) = payload else {\n            return false;\n        };\n        if let wasmparser::Payload::ExportSection(section) = payload {\n            for export in section {\n                let Ok(export) = export else {\n                    return false;\n                };\n                if export.name == wanted {\n                    return true;\n                }\n            }\n        }\n    }\n\n    false\n}\nfn is_fastly_module(module: &str) -> bool {\n    module.starts_with(\"fastly_\") || module == \"env\" || module == \"fastly\" || module == \"xqd\"\n}\nfn has_wit_bindgen_imports(bytes: &[u8]) -> bool {\n    for payload in wasmparser::Parser::new(0).parse_all(bytes) {\n        let Ok(payload) = payload else {\n            return false;\n        };\n        if let wasmparser::Payload::ImportSection(section) = payload {\n            for import in section {\n                let Ok(import) = import else {\n                    return false;\n                };\n                if !is_fastly_module(import.module) && import.module != \"wasi_snapshot_preview1\" {\n                    return true;\n                }\n            }\n        }\n    }\n\n    false\n}\n"
  },
  {
    "path": "src/async_io.rs",
    "content": "use {\n    crate::{\n        error::Error,\n        sandbox::Sandbox,\n        wiggle_abi::{fastly_async_io::FastlyAsyncIo, types::AsyncItemHandle},\n    },\n    futures::{FutureExt, TryFutureExt},\n    std::time::Duration,\n    tokio::time::timeout,\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyAsyncIo for Sandbox {\n    async fn select(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        handles: GuestPtr<[AsyncItemHandle]>,\n        timeout_ms: u32,\n    ) -> Result<u32, Error> {\n        let handles = handles.cast::<[u32]>();\n        if handles.len() == 0 && timeout_ms == 0 {\n            return Err(Error::InvalidArgument);\n        }\n\n        let select_fut = self\n            .select_impl(\n                memory\n                    // TODO: `GuestMemory::as_slice` only supports guest pointers to u8 slices in\n                    // wiggle 22.0.0, but `GuestMemory::to_vec` supports guest pointers to slices\n                    // of arbitrary types. As `GuestMemory::to_vec` will copy the contents of the\n                    // slice out of guest memory, we should switch this to `GuestMemory::as_slice`\n                    // once it is polymorphic in the element type of the slice.\n                    .to_vec(handles)?\n                    .into_iter()\n                    .map(|i| AsyncItemHandle::from(i).into()),\n            )\n            .map_ok(|done_idx| done_idx as u32);\n\n        if timeout_ms == 0 {\n            select_fut.await\n        } else {\n            timeout(Duration::from_millis(timeout_ms as u64), select_fut)\n                .await\n                .unwrap_or(Ok(u32::MAX))\n        }\n    }\n    fn is_ready(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: AsyncItemHandle,\n    ) -> Result<u32, Error> {\n        if self\n            .async_item_mut(handle.into())?\n            .await_ready()\n            .now_or_never()\n            .is_some()\n        {\n            Ok(1)\n        } else {\n            Ok(0)\n        }\n    }\n}\n"
  },
  {
    "path": "src/body.rs",
    "content": "//! Body type, for request and response bodies.\n\nuse futures::FutureExt;\n\nuse {\n    crate::{Error, error, streaming_body::StreamingBodyItem},\n    bytes::{BufMut, BytesMut},\n    flate2::write::GzDecoder,\n    futures::pin_mut,\n    http::header::HeaderMap,\n    http_body::{Body as HttpBody, SizeHint},\n    std::{\n        collections::VecDeque,\n        io::Write,\n        pin::Pin,\n        task::{Context, Poll},\n    },\n    tokio::sync::mpsc,\n};\n\ntype DecoderState = Box<GzDecoder<bytes::buf::Writer<BytesMut>>>;\n\n/// A chunk of bytes in a [`Body`].\n///\n/// A chunk represents a block of data in a body. Representing bodies as chunks allows us to append\n/// one body to another without copying, and makes it possible from parts of the body to come from\n/// different sources, including ongoing asynchronous streaming.\n#[derive(Debug)]\npub enum Chunk {\n    /// Wraps Hyper's http body representation.\n    ///\n    /// We use this variant for both data that's incoming from a Hyper request, and for owned byte\n    /// buffers that we've allocated while writing to a `Body`.\n    HttpBody(hyper::Body),\n    /// A channel for bodies that may be written to after headers have been sent, such as after\n    /// `send_downstream_streaming` or `send_async_streaming`.\n    ///\n    /// Since the channel yields chunks, this variant represents a *stream* of chunks rather than\n    /// one individual chunk. That stream is effectively \"flattened\" on-demand, as the `Body`\n    /// containing it is read.\n    Channel(mpsc::Receiver<StreamingBodyItem>),\n    /// A version of `HttpBody` that assumes that the interior data is gzip-compressed.\n    CompressedHttpBody(DecoderState, hyper::Body),\n}\n\nimpl Chunk {\n    pub fn compressed_body(body: hyper::Body) -> Chunk {\n        let initial_state = Box::new(GzDecoder::new(BytesMut::new().writer()));\n        Chunk::CompressedHttpBody(initial_state, body)\n    }\n}\n\nimpl From<&[u8]> for Chunk {\n    fn from(bytes: &[u8]) -> Self {\n        Self::HttpBody(hyper::Body::from(bytes.to_vec()))\n    }\n}\n\nimpl From<Vec<u8>> for Chunk {\n    fn from(vec: Vec<u8>) -> Self {\n        Self::HttpBody(hyper::Body::from(vec))\n    }\n}\n\nimpl From<bytes::Bytes> for Chunk {\n    fn from(bytes: bytes::Bytes) -> Self {\n        Self::HttpBody(hyper::Body::from(bytes))\n    }\n}\n\nimpl From<hyper::Body> for Chunk {\n    fn from(body: hyper::Body) -> Self {\n        Chunk::HttpBody(body)\n    }\n}\n\nimpl From<mpsc::Receiver<StreamingBodyItem>> for Chunk {\n    fn from(chan: mpsc::Receiver<StreamingBodyItem>) -> Self {\n        Chunk::Channel(chan)\n    }\n}\n\n/// An HTTP request or response body.\n///\n/// Most importantly, this type implements [`http_body::Body`][body-trait]. This type is an\n/// alternative to [`hyper::Body`][hyper-body], with facilities to write to an existing body, and\n/// to append bodies to one another.\n///\n/// [body-trait]: https://docs.rs/http-body/latest/http_body/trait.Body.html\n/// [hyper-body]: https://docs.rs/hyper/latest/hyper/body/struct.Body.html\n#[derive(Default, Debug)]\npub struct Body {\n    chunks: VecDeque<Chunk>,\n    pub(crate) trailers: HeaderMap,\n    pub(crate) trailers_ready: bool,\n}\n\nimpl Body {\n    /// Get a new, empty body.\n    pub fn empty() -> Self {\n        Self::default()\n    }\n\n    /// Push a new chunk onto the body.\n    pub fn push_back(&mut self, chunk: impl Into<Chunk>) {\n        self.chunks.push_back(chunk.into());\n    }\n\n    /// Push a new chunk onto the front of the body.\n    pub fn push_front(&mut self, chunk: impl Into<Chunk>) {\n        self.chunks.push_front(chunk.into());\n    }\n\n    /// Append another body to this body.\n    pub fn append(&mut self, body: Self) {\n        self.extend(body)\n    }\n\n    /// Analogue to the AsyncRead traits, but without running through std::io::Result.\n    ///\n    /// Attempts to read at least one byte into the buffer.\n    /// Waits until at least one byte is available. If an error is encountered,\n    /// returns the error; if the stream ends, returns Ok(0).\n    pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, error::Error> {\n        // poll_data may return empty chunks, which we need to filter out.\n        // Loop until we get a nonempty chunk, an error, or an end-of-stream.\n        loop {\n            match self.data().await {\n                None => return Ok(0),\n                Some(Err(e)) => return Err(e),\n\n                // Empty chunk; avoid signaling end-of-stream, wait for more data:\n                Some(Ok(bytes)) if bytes.is_empty() => continue,\n\n                Some(Ok(src)) => {\n                    let copy_len = std::cmp::min(buf.len(), src.len());\n                    buf[..copy_len].copy_from_slice(&src[..copy_len]);\n                    let remainder = &src[copy_len..];\n                    if !remainder.is_empty() {\n                        self.chunks.push_front(remainder.into());\n                    }\n                    return Ok(copy_len);\n                }\n            }\n        }\n    }\n\n    /// Read the entire body into a byte vector.\n    pub async fn read_into_vec(self) -> Result<Vec<u8>, error::Error> {\n        let mut body = Box::new(self);\n        let mut bytes = Vec::new();\n\n        while let Some(chunk) = body.data().await.transpose()? {\n            bytes.extend_from_slice(&chunk);\n        }\n        Ok(bytes)\n    }\n\n    /// Read the entire body into a `String`\n    ///\n    /// # Panics\n    ///\n    /// Panics if the body is not valid UTF-8.\n    pub async fn read_into_string(self) -> Result<String, error::Error> {\n        Ok(String::from_utf8(self.read_into_vec().await?).expect(\"Body was not UTF-8\"))\n    }\n\n    /// Block until the body has a chunk ready (or is known to be empty).\n    pub async fn await_ready(&mut self) {\n        // Attempt to read a chunk, blocking until one is available (or `None` signals end of stream)\n        if let Some(Ok(chunk)) = self.data().await {\n            // If we did get a chunk, put it back; subsequent read attempts will find this chunk without\n            // additional blocking.\n            self.chunks.push_front(chunk.into())\n        }\n    }\n\n    pub fn len(&self) -> Option<u64> {\n        let mut len = 0u64;\n\n        for chunk in &self.chunks {\n            if let Chunk::HttpBody(body) = chunk {\n                len = len.checked_add(body.size_hint().exact()?)?;\n            } else {\n                return None;\n            }\n        }\n\n        Some(len)\n    }\n\n    pub fn is_empty(&self) -> Option<bool> {\n        self.len().map(|len| len == 0)\n    }\n}\n\nimpl<T: Into<Chunk>> From<T> for Body {\n    fn from(chunk: T) -> Self {\n        let mut body = Body::empty();\n        body.push_back(chunk);\n        body\n    }\n}\n\nimpl Extend<Chunk> for Body {\n    fn extend<I: IntoIterator<Item = Chunk>>(&mut self, iter: I) {\n        self.chunks.extend(iter);\n    }\n}\n\nimpl IntoIterator for Body {\n    type Item = Chunk;\n    type IntoIter = <VecDeque<Chunk> as IntoIterator>::IntoIter;\n    fn into_iter(self) -> Self::IntoIter {\n        self.chunks.into_iter()\n    }\n}\n\nimpl HttpBody for Body {\n    type Data = bytes::Bytes;\n    type Error = error::Error;\n\n    fn poll_data(\n        mut self: Pin<&mut Self>,\n        cx: &mut Context,\n    ) -> Poll<Option<Result<Self::Data, Self::Error>>> {\n        while let Some(mut chunk) = self.chunks.pop_front() {\n            match chunk {\n                Chunk::HttpBody(mut body) => {\n                    let body_mut = &mut body;\n                    pin_mut!(body_mut);\n\n                    match body_mut.as_mut().poll_data(cx) {\n                        Poll::Pending => {\n                            // put the body back, so we can poll it again next time\n                            self.chunks.push_front(body.into());\n                            return Poll::Pending;\n                        }\n                        Poll::Ready(None) => {\n                            // no more bytes from this body, so continue the loop now that it's been\n                            // popped\n                            match body_mut.trailers().poll_unpin(cx) {\n                                Poll::Pending => {\n                                    self.chunks.push_front(body.into());\n                                    return Poll::Pending;\n                                }\n\n                                Poll::Ready(Err(e)) => {\n                                    return Poll::Ready(Some(Err(e.into())));\n                                }\n\n                                Poll::Ready(Ok(None)) => continue,\n\n                                Poll::Ready(Ok(Some(header_map))) => {\n                                    for (k, v) in header_map.iter() {\n                                        self.trailers.append(k, v.clone());\n                                    }\n                                    continue;\n                                }\n                            }\n                        }\n                        Poll::Ready(Some(item)) => {\n                            // put the body back, so we can poll it again next time\n                            self.chunks.push_front(body.into());\n                            return Poll::Ready(Some(item.map_err(Into::into)));\n                        }\n                    }\n                }\n                Chunk::Channel(mut receiver) => {\n                    let receiver_mut = &mut receiver;\n                    pin_mut!(receiver_mut);\n                    match receiver_mut.poll_recv(cx) {\n                        Poll::Pending => {\n                            // put the channel back, so we can poll it again next time\n                            self.chunks.push_front(receiver.into());\n                            return Poll::Pending;\n                        }\n                        Poll::Ready(None) => {\n                            // the channel completed without a Finish message, so yield an error\n                            return Poll::Ready(Some(Err(Error::UnfinishedStreamingBody)));\n                        }\n                        Poll::Ready(Some(StreamingBodyItem::Chunk(chunk))) => {\n                            // put the channel back first, so we can poll it again after the chunk it\n                            // just yielded\n                            self.chunks.push_front(receiver.into());\n                            // now push the chunk which will be polled appropriately the next time\n                            // through the loop\n                            self.chunks.push_front(chunk);\n                            continue;\n                        }\n                        Poll::Ready(Some(StreamingBodyItem::Finished(trailers))) => {\n                            self.trailers.extend(trailers);\n                            // it shouldn't be possible for any more chunks to arrive on this\n                            // channel, but just in case we won't try to read them; dropping the\n                            // receiver means we won't hit the `Ready(None)` case above that\n                            // indicates an unfinished streaming body\n                            continue;\n                        }\n                    }\n                }\n                Chunk::CompressedHttpBody(ref mut decoder_state, ref mut body) => {\n                    pin_mut!(body);\n\n                    match body.poll_data(cx) {\n                        Poll::Pending => {\n                            // put the body back, so we can poll it again next time\n                            self.chunks.push_front(chunk);\n                            return Poll::Pending;\n                        }\n                        Poll::Ready(None) => match decoder_state.try_finish() {\n                            Err(e) => return Poll::Ready(Some(Err(e.into()))),\n                            Ok(()) => {\n                                let chunk = decoder_state.get_mut().get_mut().split().freeze();\n                                return Poll::Ready(Some(Ok(chunk)));\n                            }\n                        },\n                        Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e.into()))),\n                        Poll::Ready(Some(Ok(bytes))) => {\n                            match decoder_state.write_all(&bytes) {\n                                Err(e) => return Poll::Ready(Some(Err(e.into()))),\n                                Ok(()) => {\n                                    decoder_state.flush().unwrap();\n                                    let resulting_bytes =\n                                        decoder_state.get_mut().get_mut().split().freeze();\n                                    // put the body back, so we can poll it again next time\n                                    self.chunks.push_front(chunk);\n                                    if resulting_bytes.is_empty() {\n                                        // If we got no bytes from this chunk, it might be just the gzip header\n                                        // we'll continue the loop to process more chunks\n                                        continue;\n                                    } else {\n                                        return Poll::Ready(Some(Ok(resulting_bytes)));\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        // With no more chunks arriving we can mark trailers as being ready.\n        self.trailers_ready = true;\n        Poll::Ready(None) // The queue of chunks is now empty!\n    }\n\n    fn poll_trailers(\n        self: Pin<&mut Self>,\n        _cx: &mut Context,\n    ) -> Poll<Result<Option<HeaderMap>, Self::Error>> {\n        if !self.chunks.is_empty() {\n            return Poll::Pending;\n        }\n        if self.trailers.is_empty() {\n            Poll::Ready(Ok(None))\n        } else {\n            Poll::Ready(Ok(Some(self.trailers.clone())))\n        }\n    }\n\n    /// This is an optional method, but implementing it correctly allows us to reduce the number of\n    /// cases where bodies get sent with chunked Transfer-Encoding instead of Content-Length.\n    fn size_hint(&self) -> SizeHint {\n        let mut size = 0;\n        for chunk in self.chunks.iter() {\n            match chunk {\n                // If this is a streaming body or a compressed chunk, immediately give up on the hint.\n                Chunk::Channel(_) => return SizeHint::default(),\n                Chunk::CompressedHttpBody(_, _) => return SizeHint::default(),\n                Chunk::HttpBody(body) => {\n                    // An `HttpBody` size hint will either be exact, or wide open. If the latter,\n                    // bail out with a wide-open range.\n                    if let Some(chunk_size) = body.size_hint().exact() {\n                        size += chunk_size;\n                    } else {\n                        return SizeHint::default();\n                    }\n                }\n            }\n        }\n        SizeHint::with_exact(size)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::io::Read;\n\n    use bytes::{Bytes, BytesMut};\n    use flate2::{Compression, GzBuilder};\n    use http::HeaderMap;\n    use proptest::prelude::*;\n\n    use crate::body::Chunk;\n\n    /// Proptest strategy: get a set of Bytes.\n    fn some_bytes() -> impl Strategy<Value = Bytes> {\n        proptest::collection::vec(any::<u8>(), 0..16).prop_map(|v| v.into())\n    }\n\n    // Gzip some bytes, with \"best\" compression and no header fields.\n    fn compress_body(body: &[u8]) -> Bytes {\n        let mut encoder =\n            GzBuilder::new().buf_read(std::io::Cursor::new(body), Compression::best());\n        let mut compressed = Vec::new();\n        encoder\n            .read_to_end(&mut compressed)\n            .expect(\"failed to compress gzip body\");\n        compressed.into()\n    }\n\n    // Gradually send the provided body to the provided sender, using the provided chunk lengths.\n    async fn trickle_body(mut sender: hyper::body::Sender, body: Bytes, chunk_lengths: Vec<usize>) {\n        // Put \"the whole body\" at the back of the chunk-lengths, so we'll send the whole body by\n        // the last iteration.\n        let all_chunks = chunk_lengths.into_iter().chain(std::iter::once(body.len()));\n        let mut remaining: &[u8] = &body;\n        for chunk_length in all_chunks {\n            if remaining.len() == 0 {\n                break;\n            }\n            let len = std::cmp::min(remaining.len(), chunk_length);\n            let to_send = &remaining[..len];\n            remaining = &remaining[len..];\n            let Ok(_) = sender.send_data(Bytes::copy_from_slice(to_send)).await else {\n                return;\n            };\n        }\n        let _ = sender.send_trailers(HeaderMap::default()).await;\n    }\n\n    /// Check that the Body produces the given data, when read using the AsyncRead-like API.\n    async fn check_body_read(\n        mut body: crate::body::Body,\n        want: Bytes,\n    ) -> Result<(), TestCaseError> {\n        let mut got = BytesMut::new();\n        let buf = &mut [0u8; 16];\n        while let Ok(n) = body.read(buf).await {\n            if n == 0 {\n                break;\n            }\n            got.extend_from_slice(&buf[..n]);\n        }\n        prop_assert_eq!(got.len(), want.len());\n        for (i, (got, want)) in got.into_iter().zip(want.into_iter()).enumerate() {\n            prop_assert_eq!(got, want, \"@{}: {} != {}\", i, got, want);\n        }\n\n        Ok(())\n    }\n\n    /// Test that a given body can round-trip, even if the body is split into chunks.\n    async fn test_chunked_body(\n        bytes: Bytes,\n        chunk_lengths: Vec<usize>,\n    ) -> Result<(), TestCaseError> {\n        let mut js = tokio::task::JoinSet::default();\n        let (sender, body) = hyper::Body::channel();\n        js.spawn({\n            let bytes = bytes.clone();\n            async move {\n                trickle_body(sender, bytes, chunk_lengths).await;\n                Ok(())\n            }\n        });\n        js.spawn(check_body_read(body.into(), bytes));\n        let results = js.join_all().await;\n        for result in results {\n            result?;\n        }\n        Ok(())\n    }\n\n    /// Test that a given body can round-trip the Gzip decoder, even if the compressed body\n    /// is split into chunks.\n    async fn test_gzip_body(\n        ungz_bytes: Bytes,\n        chunk_lengths: Vec<usize>,\n    ) -> Result<(), TestCaseError> {\n        let gz_bytes = compress_body(&ungz_bytes);\n\n        let mut js = tokio::task::JoinSet::default();\n        let (sender, gz_body) = hyper::Body::channel();\n        js.spawn({\n            let gz_bytes = gz_bytes.clone();\n            async move {\n                trickle_body(sender, gz_bytes, chunk_lengths).await;\n                Ok(())\n            }\n        });\n        let ungz_body: crate::body::Body = Chunk::compressed_body(gz_body).into();\n        js.spawn(check_body_read(ungz_body, ungz_bytes));\n        let results = js.join_all().await;\n        for result in results {\n            result?;\n        }\n        Ok(())\n    }\n\n    proptest! {\n        #[test]\n        fn gzip_chunks_reproduce_body(\n            (body, chunk_lengths) in some_bytes().prop_flat_map(|bytes| {\n                    let len = bytes.len();\n                    let chunk_length_strategy= proptest::collection::vec(0..=len, 0..=(len/8));\n            (Just(bytes), chunk_length_strategy)\n            }),\n        ) {\n            let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();\n            rt.block_on(test_gzip_body(body, chunk_lengths))?\n        }\n\n    }\n\n    proptest! {\n        #[test]\n        fn chunks_reproduce_body(\n            (body, chunk_lengths) in some_bytes().prop_flat_map(|bytes| {\n                    let len = bytes.len();\n                    let chunk_length_strategy= proptest::collection::vec(0..=len, 0..=(len/8));\n            (Just(bytes), chunk_length_strategy)\n            }),\n        ) {\n            let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();\n            rt.block_on(test_chunked_body(body, chunk_lengths))?\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/body_tee.rs",
    "content": "use futures::stream::{Stream, StreamExt};\nuse hyper::body::{Body, Bytes, HttpBody};\nuse std::collections::VecDeque;\nuse std::fmt;\nuse std::pin::Pin;\nuse std::sync::{Arc, Mutex};\nuse std::task::{Context, Poll, Waker};\n\n/// The \"tee\" needs a cloneable error that can be given to both forks of the output stream.\n#[derive(Clone, Debug)]\npub struct StringError(String);\n\nimpl fmt::Display for StringError {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.write_str(&self.0)\n    }\n}\n\nimpl std::error::Error for StringError {}\n\n#[derive(Debug, Default, Clone)]\nstruct ConsumerState {\n    waker: Option<Waker>,\n    cursor: usize,\n    active: bool,\n}\n\n/// The shared state between the two output streams.\n#[derive(Debug)]\nstruct SharedState {\n    /// The buffer holds chunks or an error from the source stream.\n    buffer: VecDeque<Result<Bytes, StringError>>,\n    /// The absolute index of the first element in the buffer.\n    offset: usize,\n    /// True if the source stream has finished.\n    is_done: bool,\n    /// State for the two consumer streams.\n    consumers: [ConsumerState; 2],\n}\n\nimpl Default for SharedState {\n    fn default() -> Self {\n        Self {\n            buffer: VecDeque::new(),\n            offset: 0,\n            is_done: false,\n            consumers: [\n                ConsumerState {\n                    active: true,\n                    ..Default::default()\n                },\n                ConsumerState {\n                    active: true,\n                    ..Default::default()\n                },\n            ],\n        }\n    }\n}\n\n/// A stream that is one of two outputs from the tee operation.\n#[derive(Debug)]\npub struct BodyTeeStream {\n    shared: Arc<Mutex<SharedState>>,\n    id: usize,\n}\n\n/// Tees a Body into two independent, error-propagating, and memory-safe streams.\npub async fn tee(mut hyper_body: Body) -> (Body, Body) {\n    if HttpBody::size_hint(&hyper_body).exact().is_some() {\n        // If the size is known, we MUST buffer the body to preserve the\n        // Content-Length.\n        let bytes = hyper::body::to_bytes(hyper_body)\n            .await\n            .expect(\"Failed to buffer known-size body\");\n        // `Bytes` is cheap to clone.\n        return (hyper::Body::from(bytes.clone()), hyper::Body::from(bytes));\n    }\n\n    let shared_state = Arc::new(Mutex::new(SharedState::default()));\n\n    let s1 = BodyTeeStream {\n        shared: shared_state.clone(),\n        id: 0,\n    };\n\n    let s2 = BodyTeeStream {\n        shared: shared_state.clone(),\n        id: 1,\n    };\n\n    tokio::spawn(async move {\n        loop {\n            let result = hyper_body.next().await;\n            let mut state = shared_state.lock().unwrap();\n\n            let finished = if let Some(item) = result {\n                // Convert any error into our simple, cloneable StringError.\n                let item_to_store = item.map_err(|e| StringError(e.to_string()));\n                let is_err = item_to_store.is_err();\n                state.buffer.push_back(item_to_store);\n                is_err\n            } else {\n                true\n            };\n\n            if finished {\n                state.is_done = true;\n            }\n\n            for consumer in state.consumers.iter_mut().filter(|c| c.active) {\n                if let Some(waker) = consumer.waker.take() {\n                    waker.wake();\n                }\n            }\n\n            drain_buffer(&mut state);\n\n            if finished {\n                break;\n            }\n        }\n    });\n\n    (Body::wrap_stream(s1), Body::wrap_stream(s2))\n}\n\nimpl HttpBody for BodyTeeStream {\n    type Data = Bytes;\n    // The error type must be convertible into hyper's error type. A boxed\n    // standard error is the idiomatic way to do this.\n    type Error = Box<dyn std::error::Error + Send + Sync>;\n\n    fn poll_data(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n    ) -> Poll<Option<Result<Self::Data, Self::Error>>> {\n        let this = self.get_mut();\n        let mut state = this.shared.lock().unwrap();\n\n        let SharedState {\n            buffer,\n            offset,\n            is_done,\n            consumers,\n            ..\n        } = &mut *state;\n\n        let consumer = &mut consumers[this.id];\n\n        if consumer.cursor >= *offset {\n            let buffer_index = consumer.cursor - *offset;\n            if let Some(result) = buffer.get(buffer_index) {\n                consumer.cursor += 1;\n                // FIX: When we read from the buffer, explicitly cast the boxed concrete\n                // error to a boxed trait object to satisfy the type checker.\n                return Poll::Ready(Some(result.clone().map_err(|e| Box::new(e) as Self::Error)));\n            }\n        }\n\n        if *is_done {\n            return Poll::Ready(None);\n        }\n\n        consumer.waker = Some(cx.waker().clone());\n        Poll::Pending\n    }\n\n    fn poll_trailers(\n        self: Pin<&mut Self>,\n        _cx: &mut Context<'_>,\n    ) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {\n        Poll::Ready(Ok(None))\n    }\n\n    fn is_end_stream(&self) -> bool {\n        let state = self.shared.lock().unwrap();\n        if !state.is_done {\n            return false;\n        }\n        let consumer = &state.consumers[self.id];\n        let total_buffered_chunks = state.offset + state.buffer.len();\n        consumer.cursor >= total_buffered_chunks\n    }\n}\n\n// so it can be used with `Body::wrap_stream`.\nimpl Stream for BodyTeeStream {\n    type Item = Result<Bytes, Box<dyn std::error::Error + Send + Sync>>;\n\n    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {\n        self.poll_data(cx)\n    }\n}\n\nimpl Drop for BodyTeeStream {\n    fn drop(&mut self) {\n        let mut state = self.shared.lock().unwrap();\n        state.consumers[self.id].active = false;\n\n        let other_id = 1 - self.id;\n        if state.consumers[other_id].active\n            && let Some(waker) = state.consumers[other_id].waker.take()\n        {\n            waker.wake();\n        }\n\n        drain_buffer(&mut state);\n    }\n}\n\n/// Helper to remove chunks from the buffer that all active consumers have read.\nfn drain_buffer(state: &mut SharedState) {\n    let min_cursor = state\n        .consumers\n        .iter()\n        .filter(|c| c.active)\n        .map(|c| c.cursor)\n        .min()\n        .unwrap_or(state.offset + state.buffer.len());\n\n    let to_drain = min_cursor.saturating_sub(state.offset);\n    if to_drain > 0 {\n        state.buffer.drain(0..to_drain);\n        state.offset += to_drain;\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use futures::stream::{self, StreamExt};\n    use hyper::{Body, body::Bytes};\n    use std::convert::Infallible;\n\n    #[tokio::test]\n    async fn test_simple_duplication() {\n        let chunks = vec![\"hello\", \" \", \"world\"];\n        let stream = stream::iter(chunks.clone()).map(|s| Ok::<_, Infallible>(Bytes::from(s)));\n        let body = Body::wrap_stream(stream);\n\n        let (body1, body2) = tee(body).await;\n\n        let res1_fut = body1\n            .map(|chunk_res| chunk_res.unwrap())\n            .collect::<Vec<_>>();\n        let res2_fut = body2\n            .map(|chunk_res| chunk_res.unwrap())\n            .collect::<Vec<_>>();\n\n        let (res1, res2) = futures::join!(res1_fut, res2_fut);\n\n        let res1_str: Vec<&str> = res1\n            .iter()\n            .map(|b| std::str::from_utf8(b).unwrap())\n            .collect();\n        let res2_str: Vec<&str> = res2\n            .iter()\n            .map(|b| std::str::from_utf8(b).unwrap())\n            .collect();\n\n        assert_eq!(res1_str, chunks);\n        assert_eq!(res2_str, chunks);\n    }\n\n    #[tokio::test]\n    async fn test_error_propagation() {\n        let error = std::io::Error::new(std::io::ErrorKind::Other, \"test error\");\n        let stream = stream::iter(vec![\n            Ok(Bytes::from(\"one\")),\n            Err(error),\n            Ok(Bytes::from(\"two\")),\n        ]);\n        let body = Body::wrap_stream(stream);\n\n        let (mut body1, mut body2) = tee(body).await;\n\n        assert_eq!(body1.next().await.unwrap().unwrap(), Bytes::from(\"one\"));\n        let err1 = body1.next().await.unwrap().unwrap_err();\n        assert!(\n            err1.to_string().contains(\"test error\"),\n            \"Got error: {}\",\n            err1\n        );\n        assert!(\n            body1.next().await.is_none(),\n            \"Stream should end after error\"\n        );\n\n        assert_eq!(body2.next().await.unwrap().unwrap(), Bytes::from(\"one\"));\n        let err2 = body2.next().await.unwrap().unwrap_err();\n        assert!(\n            err2.to_string().contains(\"test error\"),\n            \"Got error: {}\",\n            err1\n        );\n        assert!(\n            body2.next().await.is_none(),\n            \"Stream should end after error\"\n        );\n    }\n\n    #[tokio::test]\n    async fn test_error_with_one_consumer_dropped() {\n        let error = std::io::Error::new(std::io::ErrorKind::ConnectionAborted, \"aborted\");\n        let stream = stream::iter(vec![Ok(Bytes::from(\"first\")), Err(error)]);\n        let body = Body::wrap_stream(stream);\n\n        let (mut body1, body2) = tee(body).await;\n\n        drop(body2);\n\n        assert_eq!(body1.next().await.unwrap().unwrap(), Bytes::from(\"first\"));\n        let err1 = body1.next().await.unwrap().unwrap_err();\n        assert!(err1.to_string().contains(\"aborted\"));\n        assert!(\n            body1.next().await.is_none(),\n            \"Stream should end after error\"\n        );\n    }\n\n    #[tokio::test]\n    async fn test_size_hint_preservation() {\n        let data = \"this has a known size\";\n        let body = Body::from(data);\n        let original_size_hint = HttpBody::size_hint(&body);\n\n        assert_eq!(original_size_hint.exact(), Some(data.len() as u64));\n\n        let (body1, body2) = tee(body).await;\n\n        assert_eq!(\n            HttpBody::size_hint(&body1).exact(),\n            original_size_hint.exact()\n        );\n        assert_eq!(\n            HttpBody::size_hint(&body2).exact(),\n            original_size_hint.exact()\n        );\n\n        let body1_bytes = hyper::body::to_bytes(body1).await.unwrap();\n        let body2_bytes = hyper::body::to_bytes(body2).await.unwrap();\n\n        assert_eq!(body1_bytes, data.as_bytes());\n        assert_eq!(body2_bytes, data.as_bytes());\n    }\n}\n"
  },
  {
    "path": "src/cache/store.rs",
    "content": "//! Data structures & implementation details for the Viceroy cache.\n\nuse crate::cache::{Error, variance::VaryRule};\nuse bytes::Bytes;\nuse std::{\n    collections::{HashMap, VecDeque},\n    sync::{Arc, atomic::AtomicBool},\n    time::{Duration, Instant},\n};\nuse tokio::sync::watch;\n\nuse http::HeaderMap;\n\nuse crate::{body::Body, collecting_body::CollectingBody};\n\nuse super::{Found, SurrogateKeySet, WriteOptions, variance::Variant};\n\n/// Metadata associated with a particular object on insert.\n#[derive(Debug)]\npub struct ObjectMeta {\n    /// The time at which the object was inserted into this cache.\n    ///\n    /// This may be later than the time at which the object was created, i.e. the object's age;\n    /// include `initial_age` in any calculations that require the absolute age.\n    ///\n    /// We use Instant here rather than a calendar datetime (e.g. `chrono`) to ensure monotonicity.\n    /// All of the external interfaces & cache semantics are in terms of relative offsets (age),\n    /// not absolute timestamps; we should not be sensitive to resyncing the system clock.\n    inserted: Instant,\n    /// Initial age, if provided during setup.\n    initial_age: Duration,\n    /// Freshness lifetime\n    max_age: Duration,\n    /// stale-while-revalidate period; after max_age.\n    stale_while_revalidate: Duration,\n\n    request_headers: HeaderMap,\n    vary_rule: VaryRule,\n\n    user_metadata: Bytes,\n\n    length: Option<u64>,\n    surrogate_keys: SurrogateKeySet,\n\n    // Soft-purge bit: atomic so we don't have to wrap the whole thing in a lock.\n    // This can only transition false -> true.\n    soft_purge: AtomicBool,\n}\n\nimpl ObjectMeta {\n    /// Retrieve the current age of this object.\n    pub fn age(&self) -> Duration {\n        // Age in this cache, plus age upon insertion\n        self.inserted.elapsed() + self.initial_age\n    }\n\n    /// Maximum fresh age of this object.\n    pub fn max_age(&self) -> Duration {\n        self.max_age\n    }\n\n    /// Return true if the entry is fresh at the current time.\n    pub fn is_fresh(&self) -> bool {\n        !self.soft_purge.load(std::sync::atomic::Ordering::SeqCst) && (self.age() < self.max_age)\n    }\n\n    /// Return true if the entry is usable even if stale.\n    pub fn is_usable(&self) -> bool {\n        self.age() < (self.max_age + self.stale_while_revalidate)\n    }\n\n    /// The request headers associated with this request.\n    pub fn request_headers(&self) -> &HeaderMap {\n        &self.request_headers\n    }\n\n    /// The vary rule associated with this request.\n    pub fn vary_rule(&self) -> &VaryRule {\n        &self.vary_rule\n    }\n\n    /// The variant rule associated with this request.\n    pub fn variant(&self) -> Variant {\n        self.vary_rule.variant(&self.request_headers)\n    }\n\n    pub fn user_metadata(&self) -> Bytes {\n        self.user_metadata.clone()\n    }\n}\n\nimpl ObjectMeta {\n    fn new(value: WriteOptions, request_headers: HeaderMap) -> Self {\n        let inserted = Instant::now();\n        let WriteOptions {\n            vary_rule,\n            max_age,\n            initial_age,\n            stale_while_revalidate,\n            user_metadata,\n            length,\n            // There is no API that returns whether a cache entry has sensitive data.\n            // Viceroy doesn't change any behavior w/rt sensitive data; so, we ignore it here.\n            sensitive_data: _,\n            // Similarly, edge_max_age has no effect and cannot be read.\n            edge_max_age: _,\n            surrogate_keys,\n            ..\n        } = value;\n        ObjectMeta {\n            inserted,\n            initial_age,\n            stale_while_revalidate,\n            max_age,\n            request_headers,\n            vary_rule,\n            user_metadata,\n            length,\n            surrogate_keys,\n            soft_purge: AtomicBool::new(false),\n        }\n    }\n}\n\n/// Object(s) indexed by a CacheKey.\n#[derive(Debug, Default)]\npub struct CacheKeyObjects(watch::Sender<CacheKeyObjectsInner>);\n\nimpl CacheKeyObjects {\n    /// Get the applicable CacheData, if available.\n    pub fn get(&self, request_headers: &HeaderMap) -> Option<Arc<CacheData>> {\n        let key_objects = self.0.borrow();\n\n        for vary_rule in key_objects.vary_rules.iter() {\n            let response_key = vary_rule.variant(request_headers);\n            if let Some(object) = key_objects\n                .objects\n                .get(&response_key)\n                .and_then(|v| v.present.clone())\n            {\n                return Some(object);\n            }\n        }\n        None\n    }\n\n    /// Perform a transactional lookup.\n    ///\n    /// Returns a CacheData if existing data were found (even if stale),\n    /// and returns an Obligation if the data need to be freshened.\n    ///\n    /// If !ok_to_wait, returns a result immediately, without waiting for outstanding Obligations\n    /// to complete.\n    /// If ok_to_wait, this may await another task to complete or abandon its Obligation.\n    pub async fn transaction_get(\n        self: &Arc<Self>,\n        request_headers: &HeaderMap,\n        ok_to_wait: bool,\n    ) -> (Option<Arc<CacheData>>, Option<Obligation>) {\n        let mut sub = self.0.subscribe();\n\n        loop {\n            // We set this flag if we find an obligation that we can wait on,\n            // i.e. whose vary rule matches our request headers.\n            // Note that if we find a completed request while computing this value,\n            // we return immediately.\n            let mut awaitable = false;\n            {\n                // The read-locked portion.\n                // Note, this returns only fresh responses.\n                let key_objects = sub.borrow_and_update();\n\n                let response_values = key_objects\n                    .vary_rules\n                    .iter()\n                    .map(|v| v.variant(request_headers))\n                    .filter_map(|key| key_objects.objects.get(&key));\n                for cache_value in response_values {\n                    if let Some(data) = &cache_value.present {\n                        if data.meta.is_fresh() {\n                            // We have fresh data; no need to generate an obligation.\n                            return (Some(Arc::clone(data)), None);\n                        }\n                        if data.meta.is_usable() && cache_value.obligated {\n                            // It's not fresh, but it's within SWR, and someone has already been\n                            // obligated to freshen it.\n                            // So we can go ahead and use the current data, without generating an\n                            // obligation.\n                            return (Some(Arc::clone(data)), None);\n                        }\n                    }\n                    // Not usable, but if there's an obligation for it, we can wait on that\n                    // obligation.\n                    awaitable = awaitable || cache_value.obligated;\n                }\n            }\n            // Done computing awaitable, make it read-only:\n            let awaitable = awaitable;\n\n            // If we found an acceptable in-progress request while under the read lock,\n            // we can await. The subscription ensures that, even though we're\n            // \"waiting outside of the lock\", we'll still see the updated version.\n            if awaitable && ok_to_wait {\n                let _ = sub.changed().await;\n                continue;\n            }\n\n            // There's nothing usable in the cache, and no obligation we can wait on.\n            // Take the write lock with the intention of generating our own obligation.\n            let mut obligated: Option<Obligation> = None;\n            let mut data: Option<Arc<CacheData>> = None;\n            // Note that, even if this does modify the data table, we don't generate a\n            // notification: no task \"waits\" on a new obligation appearing, only on obligations\n            // being fulfilled.\n            self.0.send_if_modified(|key_objects| {\n                // Now under the write lock.\n                // We might have a stale result, or someone else might have an obligation;\n                // pick the best we can.\n                let response_keys: Vec<_> = key_objects\n                    .vary_rules\n                    .iter()\n                    .map(|v| v.variant(request_headers))\n                    .collect();\n                // These are the existing cache entries that would be valid for this request,\n                // taking into account vary rules.\n                // They may be stale or not-yet-filled (i.e. obligation-only); let's see what we\n                // can get out of them.\n                let response_keyed_objects: Vec<_> = response_keys\n                    .iter()\n                    .filter_map(|k| key_objects.objects.get(k).map(|v| (k, v)))\n                    .collect();\n\n                // First, if we have fresh data, we can immediately short-circuit.\n                if let Some(fresh) = response_keyed_objects\n                    .iter()\n                    .filter_map(|(_, cache_value)| cache_value.present.as_ref())\n                    .find(|cache_data| cache_data.meta.is_fresh())\n                {\n                    data = Some(Arc::clone(fresh));\n                    // Return without modifying anything.\n                    return false;\n                }\n\n                // If we have _stale but revalidatable_ entries, we can try to revalidate them\n                // instead.\n                if let Some((variant, revalidatable)) =\n                    response_keyed_objects.iter().find(|(_, cache_value)| {\n                        cache_value\n                            .present\n                            .as_ref()\n                            .is_some_and(|data| data.meta.is_usable())\n                    })\n                {\n                    let d = revalidatable.present.as_ref().unwrap();\n                    data = Some(Arc::clone(d));\n                    // Already an obligation? We've captured the data to return, so we're done.\n                    if revalidatable.obligated {\n                        return false;\n                    }\n                    let variant = Variant::clone(variant);\n\n                    key_objects.objects.insert(\n                        variant.clone(),\n                        CacheValue {\n                            obligated: true,\n                            present: Some(Arc::clone(d)),\n                        },\n                    );\n\n                    obligated = Some(Obligation {\n                        object: Arc::clone(self),\n                        variant,\n                        request_headers: request_headers.clone(),\n                        completed: false,\n                        present: data.clone(),\n                    });\n                    // We did modify the state of things; be honest, return true.\n                    // This may lead to an unnecessary wakeups and a small performance penalty; oh\n                    // well.\n                    return true;\n                }\n\n                // Nothing existing is usable, even with revalidation.\n                // We'll do an insert (or wait for one).\n\n                // We may have raced to produce an obligation to insert.\n                // Defer in favor of the existing obligation, without generating a new one.\n                if response_keyed_objects\n                    .iter()\n                    .any(|(_, value)| value.obligated)\n                {\n                    return false;\n                }\n\n                // Finally, generate an obligation to insert based on the most recent vary rule\n                // (or an empty default if there have been no vary rules so far.\n                let response_key = response_keys.into_iter().next().unwrap_or_else(|| {\n                    key_objects.vary_rules.push_front(VaryRule::default());\n                    VaryRule::default().variant(request_headers)\n                });\n                let pending = CacheValue {\n                    obligated: true,\n                    present: None,\n                };\n                key_objects.objects.insert(response_key.clone(), pending);\n                obligated = Some(Obligation {\n                    object: Arc::clone(self),\n                    variant: response_key,\n                    request_headers: request_headers.clone(),\n                    completed: false,\n                    present: None,\n                });\n\n                // We have modified the table. In theory we don't need to issue a notification,\n                // since any task waiting would be waiting on the *completion* of an obligation\n                // rather than the fulfillment.\n                false\n            });\n\n            // Now outside of the lock: return what we have.\n            if data.is_some() || obligated.is_some() || !ok_to_wait {\n                return (data, obligated);\n            }\n\n            // Return back to the top of the loop: look for the obligation we missed in the first\n            // read pass.\n        }\n    }\n\n    /// Insert into the corresponding entry.\n    ///\n    /// If a clear_obligation is provided, clear the \"obligated\" bit on that Variant in the same\n    /// transaction (so there's only one wakeup). Note the clear_obligation variant may differ from\n    /// the variant inserted: we place the obligation marker based on the _existing_ Vary rules,\n    /// but we insert based on the Vary rule received in the response.\n    ///\n    /// Returns the CacheData of the updated entry.\n    pub fn insert(\n        &self,\n        request_headers: HeaderMap,\n        options: WriteOptions,\n        body: Body,\n        clear_obligation: Option<Variant>,\n    ) -> Arc<CacheData> {\n        let meta = ObjectMeta::new(options, request_headers);\n        let vary_rule = meta.vary_rule().clone();\n\n        let variant = meta.variant();\n        let body = CollectingBody::new(body, meta.length);\n        let object = Arc::new(CacheData { body, meta });\n\n        // We return the updated object as well\n        let result = Arc::clone(&object);\n\n        self.0.send_modify(|cache_key_objects| {\n            if let Some(clear_obligation) = clear_obligation\n                && let Some(v) = cache_key_objects.objects.get_mut(&clear_obligation)\n            {\n                v.obligated = false;\n            }\n\n            // Update the position of the vary rule: this is the most-recent-inserted, so keep it at the front.\n            let vary_rule = if let Some((i, _)) = cache_key_objects\n                .vary_rules\n                .iter()\n                .enumerate()\n                .find(|&(_, rule)| rule == &vary_rule)\n            {\n                cache_key_objects\n                    .vary_rules\n                    .remove(i)\n                    .expect(\"index of a found item must be a valid index\")\n            } else {\n                vary_rule\n            };\n            cache_key_objects.vary_rules.push_front(vary_rule);\n\n            cache_key_objects\n                .objects\n                .entry(variant)\n                .or_default()\n                .present = Some(object);\n        });\n\n        result\n    }\n\n    /// Purge all variants associated with the given key.\n    ///\n    /// Returns the number of variants purged.\n    pub fn purge(&self, key: &super::SurrogateKey, soft_purge: bool) -> usize {\n        // This _shouldn't_ ever need a send- since we're only removing things, and only those\n        // which don't have obligations.\n        // But, we do it anyway, if we actually modified things.\n        let mut count = 0;\n        self.0.send_if_modified(|cache_key_objects| {\n            cache_key_objects.objects = cache_key_objects\n                .objects\n                .drain()\n                .filter_map(|(variant, value)| {\n                    let Some(present) = value.present.as_ref() else {\n                        // We may be considering an entry which is obligated, but not yet written.\n                        // In this case, we don't know its surrogate keys, so we leave it in the\n                        // set.\n                        return Some((variant, value));\n                    };\n\n                    if !present.get_meta().surrogate_keys.0.contains(key) {\n                        // Doesn't have this surrogate key; keep it.\n                        return Some((variant, value));\n                    }\n\n                    // Purge or soft purge. Either way:\n                    count += 1;\n\n                    if soft_purge {\n                        present\n                            .meta\n                            .soft_purge\n                            .store(true, std::sync::atomic::Ordering::SeqCst);\n                        Some((variant, value))\n                    } else if value.obligated {\n                        // This value has an outstanding obligation.\n                        // We don't want to clobber that; otherwise, the obligee will be Confused;\n                        // So, keep the CacheValue but remove the \"present\".\n                        Some((\n                            variant,\n                            CacheValue {\n                                present: None,\n                                obligated: true,\n                            },\n                        ))\n                    } else {\n                        // By failing to insert the CacheValue again, we purge the whole key.\n                        // There's nothing to preserve.\n                        None\n                    }\n                })\n                .collect();\n\n            // Notifications matter (only) to tasks waiting on an obligation,\n            // if the obligation was fulfilled or abandoned.\n            // We neither fulfilled nor abandoned any obligations, so we don't need to send\n            // an unnecessary notification.\n            false\n        });\n        count\n    }\n}\n\n#[derive(Debug, Default)]\nstruct CacheKeyObjectsInner {\n    /// All the vary rules that might apply.\n    /// Most-recent at the front, so we tend towards fresher responses.\n    vary_rules: VecDeque<VaryRule>,\n\n    /// The variants that may be served.\n    /// Each CacheValue may have its headers complete (completed or streaming body),\n    /// or may represent a task with an Obligation to fetch the corresponding object.\n    //\n    // INVARIANT: There is exactly one Obligation for each CacheValue::obligated.\n    objects: HashMap<Variant, CacheValue>,\n}\n\n/// Fully-indexed cache value, keyed by request (e.g. URL) and response (vary).\n///\n/// - Present but not obligated: e.g. fetched and fresh, stale and waiting an update\n/// - Present and obligated: e.g. stale, with a pending update\n/// - Obligated but not present: e.g. first fetch is transactional\n/// - Neither present nor obligated: e.g. transactional fetch was not completed\n#[derive(Debug, Default)]\nstruct CacheValue {\n    /// If this entry has been filled, the most recent data that has been inserted.\n    present: Option<Arc<CacheData>>,\n\n    /// Whether there is an outstanding Obligation to freshen this entry.\n    obligated: bool,\n}\n\n/// An obligation to fetch & update the cache.\n#[derive(Debug)]\npub struct Obligation {\n    object: Arc<CacheKeyObjects>,\n    request_headers: HeaderMap,\n    variant: Variant,\n    present: Option<Arc<CacheData>>,\n    completed: bool,\n}\n\nimpl Obligation {\n    /// Fulfill the obligation by providing a new entire entry.\n    ///\n    /// Returns a Found for the entry inserted.\n    pub fn insert(mut self, options: WriteOptions, body: Body) -> Found {\n        let request_headers = std::mem::take(&mut self.request_headers);\n        let variant = std::mem::take(&mut self.variant);\n        let data = self\n            .object\n            .insert(request_headers, options, body, Some(variant));\n        // Mild optimization: avoid re-acquiring the lock when we drop.\n        // We've already cleared the obligation flag.\n        self.completed = true;\n        data.into()\n    }\n\n    /// Fulfill the obligation by freshening the existing entry.\n    pub(super) async fn update(\n        mut self,\n        options: WriteOptions,\n    ) -> Result<(), (Self, crate::Error)> {\n        let Some(present) = &self.present else {\n            return Err((self, Error::NotRevalidatable.into()));\n        };\n        let body = match present.body().build().await {\n            Ok(body) => body,\n            Err(e) => return Err((self, e)),\n        };\n        let request_headers = std::mem::take(&mut self.request_headers);\n        let variant = std::mem::take(&mut self.variant);\n        let _ = self\n            .object\n            .insert(request_headers, options, body, Some(variant));\n        // Mild optimization: avoid re-acquiring the lock when we drop.\n        // We've already cleared the obligation flag.\n        self.completed = true;\n        Ok(())\n    }\n}\n\nimpl Drop for Obligation {\n    fn drop(&mut self) {\n        if self.completed {\n            // Obligation was already completed.\n            // Don't bother acquiring the notifier's lock.\n            return;\n        }\n        // Obligation was dropped without being completed.\n        // Remove our tracking bit from the map, along with a notification.\n        self.object.0.send_if_modified(|key_objects| {\n            if let Some(v) = key_objects.objects.get_mut(&self.variant) {\n                v.obligated = false;\n                true\n            } else {\n                // Something odd happened -- our variant is no longer in the map.\n                // In this case, we didn't change anything, so avoid a spurious wakeup.\n                false\n            }\n        });\n    }\n}\n\n/// The data stored in cache for a metadata-complete entry.\n#[derive(Debug)]\npub(crate) struct CacheData {\n    meta: ObjectMeta,\n    body: CollectingBody,\n}\n\n/// A holder for the get_body options.\npub struct GetBodyBuilder<'a> {\n    cache_data: &'a CacheData,\n    from: Option<u64>,\n    to: Option<u64>,\n    always_use_requested_range: bool,\n}\n\nimpl GetBodyBuilder<'_> {\n    /// Add range bounds to the body.\n    ///\n    /// If \"from\" is provided, \"to\" indicates an offset from the start of the cached item.\n    /// If \"to\" is provided but not \"from\", \"to\" indicates an offset from the end of the cached\n    /// item.\n    pub fn with_range(self, from: Option<u64>, to: Option<u64>) -> Self {\n        Self { from, to, ..self }\n    }\n\n    pub fn with_always_use_requested_range(self, always_use_requested_range: bool) -> Self {\n        Self {\n            always_use_requested_range,\n            ..self\n        }\n    }\n}\n\nimpl<'a> GetBodyBuilder<'a> {\n    /// Access the body of this cached item.\n    ///\n    /// In some cases (streaming), the Future may not become ready until the first byte of output is available.\n    pub async fn build(self) -> Result<Body, crate::Error> {\n        // Early \"return whole body\" cases:\n        // \"ignore requested range when length is unknown\", the old default:\n        let ignore_requested_range =\n            !self.always_use_requested_range && self.cache_data.length().is_none();\n        // No requested range provided:\n        let no_range_provided = self.from.is_none() && self.to.is_none();\n        // Known length and invalid range:\n        let valid_range = match (self.cache_data.length(), self.from, self.to) {\n            (None, _, _) => true,\n            (Some(length), None, Some(to)) if !(1..=length).contains(&to) => false,\n            (Some(length), Some(from), _) if !(0..length).contains(&from) => false,\n            (Some(length), Some(from), Some(to)) if !(from..length).contains(&to) => false,\n            _ => true,\n        };\n\n        // In each of these cases, we return the body immediately,\n        // without waiting for any body to exist.\n        if ignore_requested_range || no_range_provided || !valid_range {\n            return self.cache_data.body.read();\n        }\n\n        // At least one of (start, end) is provided.\n\n        let (start, end) = if let (None, Some(end)) = (self.from, self.to) {\n            // We need to convert from \"from the end\" to \"from the start\".\n            // To do that, we need a known or expected length.\n            if self.cache_data.length().is_none() {\n                // We don't have an expected length; we have to wait for the end of input.\n                self.cache_data.body.known_length().await?;\n            }\n\n            let length = self\n                .cache_data\n                .length()\n                .expect(\"unknown length after waiting\");\n            if end > length {\n                // Asked for more bytes than are available.\n                // In the case of an invalid range, Compute returns the entire body\n                // (as in HTTP).\n                return self.cache_data.body.read();\n            }\n            // Convert to a (start, ...) sequence:\n            (Some(length - end), None)\n        } else {\n            (self.from, self.to)\n        };\n\n        let start = start.unwrap_or(0);\n\n        // If the length is not known up-front,\n        // wait for the first byte to exist before returning a body.\n        // Yes, this only applies when the length is unknown.\n        if self.cache_data.length().is_none() {\n            self.cache_data.body.wait_length(start + 1).await?;\n        }\n\n        // Convert from inclusive bounds (GetBodyBuilder) to exclusive (read_range),\n        // and provide the body.\n        self.cache_data\n            .body\n            .read_range(start, end.map(|end| end + 1))\n    }\n}\n\nimpl CacheData {\n    /// Get a Body to read the cached object with.\n    pub(crate) fn body(&self) -> GetBodyBuilder<'_> {\n        GetBodyBuilder {\n            cache_data: self,\n            from: None,\n            to: None,\n            always_use_requested_range: false,\n        }\n    }\n\n    /// Access to object's metadata\n    pub(crate) fn get_meta(&self) -> &ObjectMeta {\n        &self.meta\n    }\n\n    /// Return the length of this object, if the final or expected length is known.\n    pub fn length(&self) -> Option<u64> {\n        self.body.length().or(self.meta.length)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::{sync::Arc, time::Duration};\n\n    use bytes::Bytes;\n    use http::{HeaderMap, HeaderName};\n\n    use crate::{\n        body::{Body, Chunk},\n        cache::{VaryRule, WriteOptions},\n    };\n\n    use super::CacheKeyObjects;\n\n    #[tokio::test]\n    async fn single_obligation() {\n        let objects = Arc::new(CacheKeyObjects::default());\n\n        let mut set = tokio::task::JoinSet::new();\n        for _ in 0..4 {\n            set.spawn({\n                let ko = Arc::clone(&objects);\n\n                async move {\n                    let (found, obligation) = ko.transaction_get(&HeaderMap::default(), true).await;\n                    // Either have the obligation to fetch, or was blocked until the obligation\n                    // completed.\n                    assert!(found.is_some() != obligation.is_some());\n\n                    if let Some(obligation) = obligation {\n                        let body: Body = \"hello\".as_bytes().into();\n                        obligation.insert(WriteOptions::new(Duration::from_secs(100)), body);\n                    }\n                    if let Some(found) = found {\n                        let body = found.body.read().unwrap().read_into_string().await.unwrap();\n                        assert_eq!(&body, \"hello\");\n                    }\n                }\n            });\n        }\n        let _ = set.join_all().await;\n        assert!(objects.get(&HeaderMap::default()).is_some())\n    }\n\n    #[tokio::test]\n    async fn obligation_when_stale() {\n        let objects = Arc::new(CacheKeyObjects::default());\n        let body: Body = \"hello\".as_bytes().into();\n\n        objects.insert(\n            HeaderMap::default(),\n            WriteOptions::new(Duration::ZERO),\n            body,\n            None,\n        );\n        let (_, obligation) = objects.transaction_get(&HeaderMap::default(), true).await;\n        // TODO: stale-while-revalidate: check that the stale data are provided\n        assert!(obligation.is_some());\n    }\n\n    #[tokio::test]\n    async fn obligation_by_vary_key() {\n        let objects = Arc::new(CacheKeyObjects::default());\n        let make_body = |s: &str| s.as_bytes().into();\n\n        let header_name = HeaderName::from_static(\"x-fastly-test\");\n\n        let vary = VaryRule::new([&header_name].into_iter());\n        let h1: HeaderMap = [(header_name.clone(), \"assert\".try_into().unwrap())]\n            .into_iter()\n            .collect();\n        let h2: HeaderMap = [(header_name.clone(), \"assume\".try_into().unwrap())]\n            .into_iter()\n            .collect();\n        let h3: HeaderMap = [(header_name.clone(), \"verify\".try_into().unwrap())]\n            .into_iter()\n            .collect();\n\n        objects.insert(\n            h3,\n            WriteOptions {\n                max_age: Duration::from_secs(100),\n                vary_rule: vary.clone(),\n                ..Default::default()\n            },\n            make_body(\"\"),\n            None,\n        );\n        let (found1, obligation1) = objects.transaction_get(&h1, true).await;\n        assert!(found1.is_none());\n        assert!(obligation1.is_some());\n        let (found2, obligation2) = objects.transaction_get(&h2, true).await;\n        assert!(found2.is_none());\n        assert!(obligation2.is_some());\n\n        // Another transaction on the same headers should pick up the same result:\n        let busy1 = objects.transaction_get(&h1, true);\n        let busy2 = objects.transaction_get(&h2, true);\n        obligation2.unwrap().insert(\n            WriteOptions {\n                vary_rule: vary.clone(),\n                max_age: Duration::from_secs(100),\n                ..Default::default()\n            },\n            make_body(\"object 2\"),\n        );\n        obligation1.unwrap().insert(\n            WriteOptions {\n                vary_rule: vary.clone(),\n                max_age: Duration::from_secs(100),\n                ..Default::default()\n            },\n            make_body(\"object 1\"),\n        );\n        match busy1.await {\n            (Some(found), None) => {\n                let s = found\n                    .body()\n                    .build()\n                    .await\n                    .unwrap()\n                    .read_into_string()\n                    .await\n                    .unwrap();\n                assert_eq!(&s, \"object 1\");\n            }\n            _ => {\n                panic!(\"expected to block on object 1\")\n            }\n        }\n        match busy2.await {\n            (Some(found), None) => {\n                let s = found\n                    .body()\n                    .build()\n                    .await\n                    .unwrap()\n                    .read_into_string()\n                    .await\n                    .unwrap();\n                assert_eq!(&s, \"object 2\");\n            }\n            _ => {\n                panic!(\"expected to block on object 2\")\n            }\n        }\n    }\n\n    #[tokio::test]\n    async fn modified_vary() {\n        let objects = Arc::new(CacheKeyObjects::default());\n        let make_body = |s: &str| s.as_bytes().into();\n\n        let header_name = HeaderName::from_static(\"x-fastly-test\");\n        let h1: HeaderMap = [(header_name.clone(), \"assert\".try_into().unwrap())]\n            .into_iter()\n            .collect();\n        let h2: HeaderMap = [(header_name.clone(), \"assume\".try_into().unwrap())]\n            .into_iter()\n            .collect();\n        let vary = VaryRule::new([&header_name].into_iter());\n\n        // No vary known in the original request:\n        let (found1, obligation1) = objects.transaction_get(&h1, true).await;\n        assert!(found1.is_none());\n        let obligation1 = obligation1.unwrap();\n        obligation1.insert(\n            WriteOptions {\n                max_age: Duration::from_secs(100),\n                vary_rule: vary.clone(),\n                ..Default::default()\n            },\n            make_body(\"object 1\"),\n        );\n\n        // A second query with the same headers should match:\n        assert!(objects.get(&h1).is_some());\n\n        // But not with different headers:\n        let (found2, obligation2) = objects.transaction_get(&h2, true).await;\n        assert!(found2.is_none());\n        assert!(obligation2.is_some());\n    }\n\n    #[tokio::test]\n    async fn drop_obligation() {\n        let ko = Arc::new(CacheKeyObjects::default());\n        let empty_headers = HeaderMap::default();\n\n        let (_not_found, obligation1) = ko.transaction_get(&empty_headers, true).await;\n        assert!(obligation1.is_some());\n        // This future won't resolve yet, while the obligation is outstanding:\n        let busy2 = ko.transaction_get(&empty_headers, true);\n        // But once we drop the first obligation...\n        std::mem::drop(obligation1);\n        // ... we should pick up another:\n        let (found2, obligation2) = busy2.await;\n        assert!(obligation2.is_some());\n        assert!(found2.is_none());\n    }\n\n    #[tokio::test]\n    async fn immediate_with_no_results() {\n        // The \"immediate\" invocation should return without blocking on obligations completing.\n        let ko = Arc::new(CacheKeyObjects::default());\n        let empty_headers = HeaderMap::default();\n\n        let (None, Some(obligation)) = ko.transaction_get(&empty_headers, false).await else {\n            panic!(\"unexpected value\")\n        };\n\n        // This should resolve immediately, even though there's an outstanding obligation:\n        let (not_found, not_obligated) = ko.transaction_get(&empty_headers, false).await;\n        assert!(not_found.is_none());\n        assert!(not_obligated.is_none());\n\n        let c: Chunk = Bytes::new().into();\n        let b: Body = c.into();\n        obligation.insert(WriteOptions::new(Duration::from_secs(100)), b);\n        let (Some(_), None) = ko.transaction_get(&empty_headers, false).await else {\n            panic!(\"unexpected value\")\n        };\n    }\n\n    #[tokio::test]\n    async fn returns_written_object() {\n        let objects = Arc::new(CacheKeyObjects::default());\n        let body: Body = \"hello\".as_bytes().into();\n\n        let e = objects.insert(\n            HeaderMap::default(),\n            WriteOptions::new(Duration::ZERO),\n            body,\n            None,\n        );\n\n        let body = e.body().build().await.expect(\"can read completed body\");\n        let s = body\n            .read_into_string()\n            .await\n            .expect(\"can collect completed body\");\n        assert_eq!(&s, \"hello\");\n    }\n}\n"
  },
  {
    "path": "src/cache/variance.rs",
    "content": "//! Support for request- and response-keyed variance, per HTTP's vary rules\n//!\n//! HTTP caching as described in RFC 9111 has two components to a key. The first is the \"request\n//! key\", defined by the caching entity -- typically consisting of the URL and often the method.\n//! The response from the server may include a Vary header, which lists request field names\n//! (i.e. header names) that affect the cacheability of the response. A subsequent request must\n//! match all the Vary values in order to use the cached result.\n//!\n//! The core cache API provides the bones of this.\n//!\n\nuse std::{collections::HashSet, str::FromStr};\n\nuse bytes::{Bytes, BytesMut};\npub use http::HeaderName;\nuse http::{HeaderMap, header::InvalidHeaderName};\n\nuse crate::Error;\n\n/// A rule for variance of a request.\n///\n/// This rule describes what fields (headers) are used to determine whether a new request \"matches\"\n/// a previous response.\n///\n/// VaryRule is canonicalized, with lowercase-named header names in sorted order.\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\npub struct VaryRule {\n    headers: Vec<HeaderName>,\n}\n\nimpl FromStr for VaryRule {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        let headers: Result<Vec<HeaderName>, InvalidHeaderName> =\n            s.split(\" \").map(HeaderName::try_from).collect();\n        Ok(VaryRule::new(headers?.iter()))\n    }\n}\n\nimpl VaryRule {\n    pub fn new<'a>(headers: impl IntoIterator<Item = &'a HeaderName>) -> VaryRule {\n        // Deduplicate:\n        let headers: HashSet<HeaderName> = headers.into_iter().cloned().collect();\n        let mut headers: Vec<HeaderName> = headers.into_iter().collect();\n        headers.sort_by(|a, b| a.as_str().cmp(b.as_str()));\n        VaryRule { headers }\n    }\n\n    /// Construct the Variant for the given headers: the (header, value) pairs that must be present\n    /// for a request to match a response.\n    pub fn variant(&self, headers: &HeaderMap) -> Variant {\n        let mut buf = BytesMut::new();\n        // Include the count, to avoid confusion from values that might contain our marker phrases.\n        buf.extend_from_slice(format!(\"[headers: {}]\", self.headers.len()).as_bytes());\n\n        for header in self.headers.iter() {\n            buf.extend_from_slice(format!(\"[header: {}]\", header.as_str().len()).as_bytes());\n            buf.extend_from_slice(header.as_str().as_bytes());\n\n            let values = headers.get_all(header);\n            buf.extend_from_slice(format!(\"[values: {}]\", values.iter().count()).as_bytes());\n\n            for value in values.iter() {\n                buf.extend_from_slice(format!(\"[value: {}]\", value.as_bytes().len()).as_bytes());\n                buf.extend_from_slice(value.as_bytes());\n            }\n        }\n        Variant {\n            signature: buf.into(),\n        }\n    }\n}\n\n/// The portion of a cache key that is defined by request and response.\n///\n/// A `vary_by` directive indicates that a cached object should only be matched if the headers\n/// listed in `vary_by` match that of the request that generated the cached object.\n#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Clone)]\npub struct Variant {\n    /// The internal representation is an HTTP header block: headers and values separated by a CRLF\n    /// sequence. However, since header values may contain arbitrary bytes, this is a Bytes rather\n    /// than a String.\n    signature: Bytes,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::VaryRule;\n\n    #[test]\n    fn vary_rule_unique_sorted() {\n        let vary1: VaryRule = \"unknown-header Accept content-type\".parse().unwrap();\n        let vary2: VaryRule = \"content-type unknown-header unknown-header Accept\"\n            .parse()\n            .unwrap();\n        assert_eq!(vary1, vary2);\n    }\n}\n"
  },
  {
    "path": "src/cache.rs",
    "content": "use core::str;\nuse std::{collections::HashSet, sync::Arc, time::Duration};\n\nuse bytes::Bytes;\n#[cfg(test)]\nuse proptest_derive::Arbitrary;\n\nuse crate::{\n    body::Body,\n    component::bindings::fastly::compute::types::Error as ComponentError,\n    wiggle_abi::types::{BodyHandle, CacheOverrideTag, FastlyStatus},\n};\n\nuse http::{HeaderMap, HeaderValue};\n\nmod store;\nmod variance;\n\nuse store::{CacheData, CacheKeyObjects, GetBodyBuilder, ObjectMeta, Obligation};\npub use variance::VaryRule;\n\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum Error {\n    #[error(\"invalid key\")]\n    InvalidKey,\n\n    #[error(\"handle is not writeable\")]\n    CannotWrite,\n\n    #[error(\"no entry for key in cache\")]\n    Missing,\n\n    #[error(\"invalid argument: {0}\")]\n    InvalidArgument(&'static str),\n\n    #[error(\"cache entry's body is currently being read by another body\")]\n    HandleBodyUsed,\n\n    #[error(\"cache entry is not revalidatable\")]\n    NotRevalidatable,\n}\n\nimpl From<Error> for crate::Error {\n    fn from(value: Error) -> Self {\n        crate::Error::CacheError(value)\n    }\n}\n\nimpl From<&Error> for FastlyStatus {\n    fn from(value: &Error) -> Self {\n        match value {\n            // TODO: cceckman-at-fastly: These may not correspond to the same errors as the compute\n            // platform uses. Check!\n            Error::InvalidKey => FastlyStatus::Inval,\n            Error::InvalidArgument(_) => FastlyStatus::Inval,\n            Error::CannotWrite => FastlyStatus::Badf,\n            Error::Missing => FastlyStatus::None,\n            Error::HandleBodyUsed => FastlyStatus::Badf,\n            Error::NotRevalidatable => FastlyStatus::Badf,\n        }\n    }\n}\n\nimpl From<Error> for ComponentError {\n    fn from(value: Error) -> Self {\n        match value {\n            // TODO: cceckman-at-fastly: These may not correspond to the same errors as the compute\n            // platform uses. Check!\n            Error::InvalidKey => ComponentError::InvalidArgument,\n            Error::InvalidArgument(_) => ComponentError::InvalidArgument,\n            Error::CannotWrite => ComponentError::AuxiliaryError,\n            Error::Missing => ComponentError::CannotRead,\n            Error::HandleBodyUsed => ComponentError::AuxiliaryError,\n            Error::NotRevalidatable => ComponentError::AuxiliaryError,\n        }\n    }\n}\n\n/// Primary cache key: an up-to-4KiB buffer.\n#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]\n#[cfg_attr(test, derive(Arbitrary))]\npub struct CacheKey(\n    #[cfg_attr(test, proptest(filter = \"|f| f.len() <= CacheKey::MAX_LENGTH\"))] Vec<u8>,\n);\n\nimpl CacheKey {\n    /// The maximum size of a cache key is 4KiB.\n    pub const MAX_LENGTH: usize = 4096;\n}\n\nimpl TryFrom<&Vec<u8>> for CacheKey {\n    type Error = Error;\n\n    fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {\n        value.as_slice().try_into()\n    }\n}\n\nimpl TryFrom<Vec<u8>> for CacheKey {\n    type Error = Error;\n\n    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {\n        if value.len() > Self::MAX_LENGTH {\n            Err(Error::InvalidKey)\n        } else {\n            Ok(CacheKey(value))\n        }\n    }\n}\n\nimpl TryFrom<&[u8]> for CacheKey {\n    type Error = Error;\n\n    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {\n        if value.len() > CacheKey::MAX_LENGTH {\n            Err(Error::InvalidKey)\n        } else {\n            Ok(CacheKey(value.to_owned()))\n        }\n    }\n}\n\nimpl TryFrom<&str> for CacheKey {\n    type Error = Error;\n\n    fn try_from(value: &str) -> Result<Self, Self::Error> {\n        value.as_bytes().try_into()\n    }\n}\n\n/// The result of a lookup: the object (if found), and/or an obligation to fetch.\n#[derive(Debug)]\npub struct CacheEntry {\n    key: CacheKey,\n    found: Option<Found>,\n    go_get: Option<Obligation>,\n\n    /// Respect the range in body() even when the body length is not yet known.\n    ///\n    /// When a cached item is Found, the length of the cached item may or may not be known:\n    /// if no expected length was provided and the body is still streaming, the length is unknown.\n    ///\n    /// When always_use_requested_range is false, and the length is unknown,\n    /// body() returns the full body regardless of the requested range.\n    /// When always_use_requested_range is true, and the length is unknown,\n    /// body() blocks until the start of the range is available.\n    always_use_requested_range: bool,\n}\n\nimpl CacheEntry {\n    /// Set the always_use_requested_range flag.\n    /// This applies to all subsequent lookups from this CacheEntry or future entries derived from it.\n    pub fn with_always_use_requested_range(self, always_use_requested_range: bool) -> Self {\n        Self {\n            always_use_requested_range,\n            ..self\n        }\n    }\n\n    /// Return a stub entry to hold in CacheBusy.\n    pub fn stub(&self) -> CacheEntry {\n        Self {\n            key: self.key.clone(),\n            found: None,\n            go_get: None,\n            always_use_requested_range: false,\n        }\n    }\n\n    /// Returns the key used to generate this CacheEntry.\n    pub fn key(&self) -> &CacheKey {\n        &self.key\n    }\n    /// Returns the data found in the cache, if any was present.\n    pub fn found(&self) -> Option<&Found> {\n        self.found.as_ref()\n    }\n\n    /// Returns the data found in the cache, if any was present.\n    pub fn found_mut(&mut self) -> Option<&mut Found> {\n        self.found.as_mut()\n    }\n\n    /// Returns the obligation to fetch, if required\n    pub fn go_get(&self) -> Option<&Obligation> {\n        self.go_get.as_ref()\n    }\n\n    /// Ignore the obligation to fetch, if present.\n    /// Returns true if it actually canceled an obligation.\n    pub fn cancel(&mut self) -> bool {\n        self.go_get.take().is_some()\n    }\n\n    /// Access the body of the cached item, if available.\n    pub async fn body(&self, from: Option<u64>, to: Option<u64>) -> Result<Body, crate::Error> {\n        let found = self\n            .found\n            .as_ref()\n            .ok_or(crate::Error::CacheError(Error::Missing))?;\n        found\n            .get_body()\n            .with_range(from, to)\n            .with_always_use_requested_range(self.always_use_requested_range)\n            .build()\n            .await\n    }\n\n    /// Insert the provided body into the cache.\n    ///\n    /// Returns a CacheEntry where the new item is Found.\n    pub fn insert(\n        &mut self,\n        options: WriteOptions,\n        body: Body,\n    ) -> Result<CacheEntry, crate::Error> {\n        let go_get = self.go_get.take().ok_or(Error::NotRevalidatable)?;\n        let found = go_get.insert(options, body);\n        Ok(CacheEntry {\n            key: self.key.clone(),\n            found: Some(found),\n            go_get: None,\n            always_use_requested_range: self.always_use_requested_range,\n        })\n    }\n\n    /// Freshen the existing cache item according to the new write options,\n    /// without changing the body.\n    pub async fn update(&mut self, options: WriteOptions) -> Result<(), crate::Error> {\n        let go_get = self.go_get.take().ok_or(Error::NotRevalidatable)?;\n        match go_get.update(options).await {\n            Ok(()) => Ok(()),\n            Err((go_get, err)) => {\n                // On failure, preserve the obligation.\n                self.go_get = Some(go_get);\n                Err(err)\n            }\n        }\n    }\n}\n\n/// A successful retrieval of an item from the cache.\n#[derive(Debug)]\npub struct Found {\n    data: Arc<CacheData>,\n\n    /// The handle for the last body used to read from this Found.\n    ///\n    /// Only one Body may be outstanding from a given Found at a time.\n    /// (This is an implementation restriction within the compute platform).\n    /// We mirror the BodyHandle here when we create it; we can later check whether the handle is\n    /// still valid, to find an outstanding read.\n    pub last_body_handle: Option<BodyHandle>,\n}\n\nimpl From<Arc<CacheData>> for Found {\n    fn from(data: Arc<CacheData>) -> Self {\n        Found {\n            data,\n            last_body_handle: None,\n        }\n    }\n}\n\nimpl Found {\n    fn get_body(&self) -> GetBodyBuilder<'_> {\n        self.data.as_ref().body()\n    }\n\n    /// Access the metadata of the cached object.\n    pub fn meta(&self) -> &ObjectMeta {\n        self.data.get_meta()\n    }\n\n    /// The length of the cached object, if known.\n    pub fn length(&self) -> Option<u64> {\n        self.data.length()\n    }\n}\n\n/// Cache for a service.\n///\n// TODO: cceckman-at-fastly:\n// Explain some about how this works:\n// - Request collapsing\n// - Stale-while-revalidate\npub struct Cache {\n    inner: moka::future::Cache<CacheKey, Arc<CacheKeyObjects>>,\n}\n\nimpl Default for Cache {\n    fn default() -> Self {\n        // TODO: cceckman-at-fastly:\n        // Weight by size, allow a cap on max size?\n        let inner = moka::future::Cache::builder()\n            .eviction_listener(|key, _value, cause| {\n                tracing::info!(\"cache eviction of {key:?}: {cause:?}\")\n            })\n            .build();\n        Cache { inner }\n    }\n}\n\nimpl Cache {\n    /// Perform a non-transactional lookup.\n    pub async fn lookup(&self, key: &CacheKey, headers: &HeaderMap) -> CacheEntry {\n        let found = self\n            .inner\n            .get_with_by_ref(key, async { Default::default() })\n            .await\n            .get(headers)\n            .map(|data| Found {\n                data,\n                last_body_handle: None,\n            });\n        CacheEntry {\n            key: key.clone(),\n            found,\n            go_get: None,\n            always_use_requested_range: false,\n        }\n    }\n\n    /// Perform a transactional lookup.\n    pub async fn transaction_lookup(\n        &self,\n        key: &CacheKey,\n        headers: &HeaderMap,\n        ok_to_wait: bool,\n    ) -> CacheEntry {\n        let (found, obligation) = self\n            .inner\n            .get_with_by_ref(key, async { Default::default() })\n            .await\n            .transaction_get(headers, ok_to_wait)\n            .await;\n        CacheEntry {\n            key: key.clone(),\n            found: found.map(|v| v.into()),\n            go_get: obligation,\n            always_use_requested_range: false,\n        }\n    }\n\n    /// Perform a non-transactional lookup for the given cache key.\n    /// Note: races with other insertions, including transactional insertions.\n    /// Last writer wins!\n    pub async fn insert(\n        &self,\n        key: &CacheKey,\n        request_headers: HeaderMap,\n        options: WriteOptions,\n        body: Body,\n    ) {\n        self.inner\n            .get_with_by_ref(key, async { Default::default() })\n            .await\n            .insert(request_headers, options, body, None);\n    }\n\n    /// Purge/soft-purge all cache entries corresponding to the given surrogate key.\n    /// Returns the number of entries (variants) purged.\n    ///\n    /// Note: this does not block concurrent reads _or inserts_; an insertion can race with the\n    /// purge.\n    pub fn purge(&self, key: SurrogateKey, soft_purge: bool) -> usize {\n        self.inner\n            .iter()\n            .map(|(_, entry)| entry.purge(&key, soft_purge))\n            .sum()\n    }\n}\n\n/// Options that can be applied to a write, e.g. insert or transaction_insert.\n#[derive(Default, Clone)]\npub struct WriteOptions {\n    pub max_age: Duration,\n    pub initial_age: Duration,\n    pub stale_while_revalidate: Duration,\n    pub vary_rule: VaryRule,\n    pub user_metadata: Bytes,\n    pub length: Option<u64>,\n    pub sensitive_data: bool,\n    pub edge_max_age: Duration,\n    pub surrogate_keys: SurrogateKeySet,\n}\n\nimpl WriteOptions {\n    pub fn new(max_age: Duration) -> Self {\n        WriteOptions {\n            max_age,\n            initial_age: Duration::ZERO,\n            stale_while_revalidate: Duration::ZERO,\n            vary_rule: VaryRule::default(),\n            user_metadata: Bytes::new(),\n            length: None,\n            sensitive_data: false,\n            edge_max_age: max_age,\n            surrogate_keys: Default::default(),\n        }\n    }\n}\n\n/// Optional override for response caching behavior.\n#[derive(Clone, Debug, Default)]\npub enum CacheOverride {\n    /// Do not override the behavior specified in the origin response's cache control headers.\n    #[default]\n    None,\n    /// Do not cache the response to this request, regardless of the origin response's headers.\n    Pass,\n    /// Override particular cache control settings.\n    ///\n    /// The origin response's cache control headers will be used for ttl and stale_while_revalidate if `None`.\n    Override {\n        ttl: Option<u32>,\n        stale_while_revalidate: Option<u32>,\n        pci: bool,\n        surrogate_key: Option<HeaderValue>,\n    },\n}\n\nimpl CacheOverride {\n    pub fn is_pass(&self) -> bool {\n        matches!(self, Self::Pass)\n    }\n\n    /// Convert from the representation suitable for passing across the ABI boundary.\n    ///\n    /// Returns `None` if the tag is not recognized. Depending on the tag, some of the values may be\n    /// ignored.\n    pub fn from_abi(\n        tag: u32,\n        ttl: u32,\n        swr: u32,\n        surrogate_key: Option<HeaderValue>,\n    ) -> Option<Self> {\n        CacheOverrideTag::from_bits(tag).map(|tag| {\n            if tag.contains(CacheOverrideTag::PASS) {\n                return CacheOverride::Pass;\n            }\n            if tag.is_empty() && surrogate_key.is_none() {\n                return CacheOverride::None;\n            }\n            let ttl = if tag.contains(CacheOverrideTag::TTL) {\n                Some(ttl)\n            } else {\n                None\n            };\n            let stale_while_revalidate = if tag.contains(CacheOverrideTag::STALE_WHILE_REVALIDATE) {\n                Some(swr)\n            } else {\n                None\n            };\n            let pci = tag.contains(CacheOverrideTag::PCI);\n            CacheOverride::Override {\n                ttl,\n                stale_while_revalidate,\n                pci,\n                surrogate_key,\n            }\n        })\n    }\n}\n\n/// Maximum length of surrogate keys (when combined with spaces).\nconst MAX_SURROGATE_KEYS_LENGTH: usize = 16 * 1024;\n/// Maximum length of a single surrogate key.\nconst MAX_SURROGATE_KEY_LENGTH: usize = 1024;\n\n#[derive(Debug, Default, Clone)]\npub struct SurrogateKeySet(HashSet<SurrogateKey>);\n\nimpl std::fmt::Display for SurrogateKeySet {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{{\")?;\n        for (i, item) in self.0.iter().enumerate() {\n            if i == 0 {\n                write!(f, \"{item}\")?;\n            } else {\n                write!(f, \" {item}\")?;\n            }\n        }\n        write!(f, \"}}\")\n    }\n}\n\nimpl TryFrom<&[u8]> for SurrogateKeySet {\n    type Error = crate::Error;\n\n    fn try_from(s: &[u8]) -> Result<Self, Self::Error> {\n        if s.len() > MAX_SURROGATE_KEYS_LENGTH {\n            return Err(\n                Error::InvalidArgument(\"surrogate key set exceeds maximum length (16Ki)\").into(),\n            );\n        }\n        let result: Result<HashSet<_>, _> = s\n            .split(|c| *c == b' ')\n            .filter(|sk| !sk.is_empty())\n            .map(SurrogateKey::try_from)\n            .collect();\n        Ok(SurrogateKeySet(result?))\n    }\n}\n\nimpl std::str::FromStr for SurrogateKeySet {\n    type Err = crate::Error;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        s.as_bytes().try_into()\n    }\n}\n\n/// A validated surrogate key: a non-empty string containing only visible ASCII characters.\n#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone)]\npub struct SurrogateKey(String);\n\nimpl std::fmt::Display for SurrogateKey {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        self.0.fmt(f)\n    }\n}\n\nimpl TryFrom<&[u8]> for SurrogateKey {\n    type Error = Error;\n\n    fn try_from(b: &[u8]) -> Result<Self, Self::Error> {\n        if b.len() > MAX_SURROGATE_KEY_LENGTH {\n            return Err(Error::InvalidArgument(\n                \"surrogate key exceeds maximum length (1024)\",\n            ));\n        }\n\n        if !b.iter().all(|c| c.is_ascii_graphic()) {\n            return Err(Error::InvalidArgument(\n                \"surrogate key contains characters other than graphical ASCII\",\n            ));\n        }\n        let s = unsafe {\n            // All graphic ASCII characters are equivalent to the corresponding UTF-8 codepoints.\n            str::from_utf8_unchecked(b)\n        };\n        Ok(SurrogateKey(s.to_owned()))\n    }\n}\n\nimpl std::str::FromStr for SurrogateKey {\n    type Err = Error;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        s.as_bytes().try_into()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::rc::Rc;\n\n    use http::HeaderName;\n    use proptest::prelude::*;\n\n    use super::*;\n\n    proptest! {\n        #[test]\n        fn reject_cache_key_too_long(l in 4097usize..5000) {\n            let mut v : Vec<u8> = Vec::new();\n            v.resize(l, 0);\n            CacheKey::try_from(&v).unwrap_err();\n        }\n    }\n\n    proptest! {\n        #[test]\n        fn accept_valid_cache_key_len(l in 0usize..4096) {\n            let mut v : Vec<u8> = Vec::new();\n            v.resize(l, 0);\n            let _ = CacheKey::try_from(&v).unwrap();\n        }\n    }\n\n    proptest! {\n        #[test]\n        fn nontransactional_insert_lookup(\n                key in any::<CacheKey>(),\n                max_age in any::<u32>(),\n                initial_age in any::<u32>(),\n                value in any::<Vec<u8>>()) {\n            let cache = Cache::default();\n\n            // We can't use tokio::test and proptest! together; both alter the signature of the\n            // test function, and are not aware of each other enough for it to pass.\n            let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();\n            rt.block_on(async {\n                let empty = cache.lookup(&key, &HeaderMap::default()).await;\n                assert!(empty.found().is_none());\n                // The non-transactional case does not produce an obligation (go_get)\n                assert!(empty.go_get().is_none());\n\n                let write_options = WriteOptions {\n                    max_age: Duration::from_secs(max_age as u64),\n                    initial_age: Duration::from_secs(initial_age as u64),\n                    ..Default::default()\n                };\n\n                cache.insert(&key, HeaderMap::default(), write_options, value.clone().into()).await;\n\n                let nonempty = cache.lookup(&key, &HeaderMap::default()).await;\n                let found = nonempty.found().expect(\"should have found inserted key\");\n                let got = found.get_body().build().await.unwrap().read_into_vec().await.unwrap();\n                assert_eq!(got, value);\n            });\n        }\n    }\n\n    #[test]\n    fn parse_surrogate_keys() {\n        let keys: SurrogateKeySet = \"keyA keyB\".parse().unwrap();\n        let key_a: SurrogateKey = \"keyA\".parse().unwrap();\n        let key_b: SurrogateKey = \"keyB\".parse().unwrap();\n        assert_eq!(keys.0.len(), 2);\n        assert!(keys.0.contains(&key_a));\n        assert!(keys.0.contains(&key_b));\n    }\n\n    #[tokio::test]\n    async fn insert_immediately_stale() {\n        let cache = Cache::default();\n        let key = ([1u8].as_slice()).try_into().unwrap();\n\n        // Insert an already-stale entry:\n        let write_options = WriteOptions {\n            max_age: Duration::from_secs(1),\n            initial_age: Duration::from_secs(2),\n            ..Default::default()\n        };\n\n        let mut body = Body::empty();\n        body.push_back([1u8].as_slice());\n\n        cache\n            .insert(&key, HeaderMap::default(), write_options, body)\n            .await;\n\n        let nonempty = cache.lookup(&key, &HeaderMap::default()).await;\n        let found = nonempty.found().expect(\"should have found inserted key\");\n        assert!(!found.meta().is_fresh());\n    }\n\n    #[tokio::test]\n    async fn test_vary() {\n        let cache = Cache::default();\n        let key = ([1u8].as_slice()).try_into().unwrap();\n\n        let header_name = HeaderName::from_static(\"x-viceroy-test\");\n        let request_headers: HeaderMap = [(header_name.clone(), HeaderValue::from_static(\"test\"))]\n            .into_iter()\n            .collect();\n\n        let write_options = WriteOptions {\n            max_age: Duration::from_secs(100),\n            vary_rule: VaryRule::new([&header_name].into_iter()),\n            ..Default::default()\n        };\n        let body = Body::empty();\n        cache\n            .insert(&key, request_headers.clone(), write_options, body)\n            .await;\n\n        let empty_headers = cache.lookup(&key, &HeaderMap::default()).await;\n        assert!(empty_headers.found().is_none());\n\n        let matched_headers = cache.lookup(&key, &request_headers).await;\n        assert!(matched_headers.found.is_some());\n\n        let r2_headers: HeaderMap = [(header_name.clone(), HeaderValue::from_static(\"assert\"))]\n            .into_iter()\n            .collect();\n        let mismatched_headers = cache.lookup(&key, &r2_headers).await;\n        assert!(mismatched_headers.found.is_none());\n    }\n\n    #[tokio::test]\n    async fn insert_stale_while_revalidate() {\n        let cache = Cache::default();\n        let key = ([1u8].as_slice()).try_into().unwrap();\n\n        // Insert an already-stale entry, with an SWR period:\n        let write_options = WriteOptions {\n            max_age: Duration::from_secs(1),\n            initial_age: Duration::from_secs(2),\n            stale_while_revalidate: Duration::from_secs(10),\n            ..Default::default()\n        };\n\n        let mut body = Body::empty();\n        body.push_back([1u8].as_slice());\n        cache\n            .insert(&key, HeaderMap::default(), write_options, body)\n            .await;\n\n        let nonempty = cache.lookup(&key, &HeaderMap::default()).await;\n        let found = nonempty.found().expect(\"should have found inserted key\");\n        assert!(!found.meta().is_fresh());\n        assert!(found.meta().is_usable());\n    }\n\n    #[tokio::test]\n    async fn insert_and_revalidate() {\n        let cache = Cache::default();\n        let key = ([1u8].as_slice()).try_into().unwrap();\n\n        // Insert an already-stale entry, with an SWR period:\n        let write_options = WriteOptions {\n            max_age: Duration::from_secs(1),\n            initial_age: Duration::from_secs(2),\n            stale_while_revalidate: Duration::from_secs(10),\n            ..Default::default()\n        };\n\n        let mut body = Body::empty();\n        body.push_back([1u8].as_slice());\n        cache\n            .insert(&key, HeaderMap::default(), write_options, body)\n            .await;\n\n        let mut txn1 = cache\n            .transaction_lookup(&key, &HeaderMap::default(), true)\n            .await;\n        let found = txn1.found().expect(\"should have found inserted key\");\n        assert!(!found.meta().is_fresh());\n        assert!(found.meta().is_usable());\n\n        assert!(txn1.go_get().is_some());\n\n        // Another lookup should not get the obligation *or* block\n        {\n            let txn2 = cache\n                .transaction_lookup(&key, &HeaderMap::default(), true)\n                .await;\n            let found = txn2.found().expect(\"should have found inserted key\");\n            assert!(!found.meta().is_fresh());\n            assert!(found.meta().is_usable());\n            assert!(txn2.go_get().is_none());\n        }\n\n        txn1.update(WriteOptions {\n            max_age: Duration::from_secs(10),\n            stale_while_revalidate: Duration::from_secs(10),\n            ..WriteOptions::default()\n        })\n        .await\n        .unwrap();\n\n        // After this, should get the new response:\n        let txn3 = cache\n            .transaction_lookup(&key, &HeaderMap::default(), true)\n            .await;\n        assert!(txn3.go_get().is_none());\n        let found = txn3.found().expect(\"should find updated entry\");\n        assert!(found.meta().is_usable());\n        assert!(found.meta().is_fresh());\n    }\n\n    #[tokio::test]\n    async fn cannot_revalidate_first_insert() {\n        let cache = Cache::default();\n        let key = ([1u8].as_slice()).try_into().unwrap();\n\n        let mut txn1 = cache\n            .transaction_lookup(&key, &HeaderMap::default(), false)\n            .await;\n        assert!(txn1.found().is_none());\n        let opts = WriteOptions {\n            max_age: Duration::from_secs(10),\n            stale_while_revalidate: Duration::from_secs(10),\n            ..WriteOptions::default()\n        };\n        txn1.update(opts.clone()).await.unwrap_err();\n\n        // But we should still be able to insert.\n        txn1.insert(opts.clone(), Body::empty()).unwrap();\n    }\n\n    #[tokio::test]\n    async fn purge_by_surrogate_key() {\n        let cache = Cache::default();\n        let key: CacheKey = ([1u8].as_slice()).try_into().unwrap();\n\n        let surrogate_keys: SurrogateKeySet = \"one two three\".parse().unwrap();\n        let opts = WriteOptions {\n            max_age: Duration::from_secs(100),\n            surrogate_keys: surrogate_keys.clone(),\n            ..WriteOptions::default()\n        };\n\n        cache\n            .insert(&key, HeaderMap::default(), opts.clone(), Body::empty())\n            .await;\n        let result = cache.lookup(&key, &HeaderMap::default()).await;\n        assert!(result.found().is_some());\n\n        assert_eq!(cache.purge(\"two\".parse().unwrap(), false), 1);\n        let result = cache.lookup(&key, &HeaderMap::default()).await;\n        assert!(result.found().is_none());\n    }\n\n    #[tokio::test]\n    async fn purge_variant_by_surrogate_key() {\n        let cache = Rc::new(Cache::default());\n        let key: CacheKey = ([1u8].as_slice()).try_into().unwrap();\n\n        // \"Introduction and Variations on a Theme by Mozart, Op. 9, is one of Fernando Sor's most\n        // famous works for guitar.\"\n        let vary_header = HeaderName::from_static(\"opus-9\");\n        let insert = {\n            let cache = cache.clone();\n            let key = key.clone();\n\n            move |surrogate_keys: &str, variation: &str| {\n                let mut headers = HeaderMap::new();\n                headers.insert(vary_header.clone(), variation.try_into().unwrap());\n                let surrogate_keys: SurrogateKeySet = surrogate_keys.parse().unwrap();\n                let opts = WriteOptions {\n                    max_age: Duration::from_secs(100),\n                    surrogate_keys: surrogate_keys.clone(),\n                    vary_rule: VaryRule::new([&vary_header]),\n                    ..WriteOptions::default()\n                };\n                let cache = cache.clone();\n                let key = key.clone();\n                async move {\n                    cache\n                        .insert(&key, headers.clone(), opts.clone(), Body::empty())\n                        .await;\n                    headers\n                }\n            }\n        };\n\n        let h1 = insert(\"one two three\", \"twinkle twinkle\").await;\n        let h2 = insert(\"one three\", \"abcdefg\").await;\n        assert!(cache.lookup(&key, &h1).await.found().is_some());\n        assert!(cache.lookup(&key, &h2).await.found().is_some());\n\n        assert_eq!(cache.purge(\"two\".parse().unwrap(), false), 1);\n        assert!(cache.lookup(&key, &h1).await.found().is_none());\n        assert!(cache.lookup(&key, &h2).await.found().is_some());\n    }\n\n    #[tokio::test]\n    async fn soft_purge() {\n        let cache = Cache::default();\n        let key: CacheKey = ([1u8].as_slice()).try_into().unwrap();\n\n        let surrogate_keys: SurrogateKeySet = \"one two three\".parse().unwrap();\n        let opts = WriteOptions {\n            max_age: Duration::from_secs(100),\n            surrogate_keys: surrogate_keys.clone(),\n            ..WriteOptions::default()\n        };\n\n        cache\n            .insert(&key, HeaderMap::default(), opts.clone(), Body::empty())\n            .await;\n        let result = cache.lookup(&key, &HeaderMap::default()).await;\n        assert!(result.found().is_some());\n\n        assert_eq!(cache.purge(\"two\".parse().unwrap(), true), 1);\n        let result = cache\n            .transaction_lookup(&key, &HeaderMap::default(), false)\n            .await;\n        let found = result.found().unwrap();\n        assert!(!found.meta().is_fresh());\n        assert!(found.meta().is_usable());\n        assert!(result.go_get().is_some());\n    }\n}\n"
  },
  {
    "path": "src/collecting_body.rs",
    "content": "//! Provides `CollectingBody`, a body that can be concurrently written-to (via a `StreamingBody`\n//! handle) and read-from (via multiple `Body`) handles.\n\nuse bytes::Bytes;\nuse http::HeaderMap;\nuse http_body::Body as HttpBody;\nuse tokio::sync::watch;\n\nuse crate::{\n    Error,\n    body::{Body, Chunk},\n    error,\n    streaming_body::StreamingBody,\n};\n\n/// CollectingBody is a body for caching and request collapsing.\n/// It allows writing a body while concurrently reading it, from multiple readers.\n///\n/// CollectingBody primarily exists to implement the cache APIs. Cache APIs allow three things to\n/// happen concurrently:\n/// - Streaming in a body, from an `insert`/`replace`-style request, implemented via StreamingBody\n/// - Storage of that body in the cache\n/// - Streaming out of the body, from a `lookup`/`TransactionInsertBuilder:execute_and_stream_back`\n///   request, from the same or a different sandbox\n///\n/// CollectingBody provides a place for this to happen. It accepts a `Body` as a source of data,\n/// e.g. one from a `StreamingBody` or an origin response; stores the data for future retrieval;\n/// and can return new `Body`s from `::read` that produce the full content.\n#[derive(Debug)]\npub struct CollectingBody {\n    inner: watch::Receiver<CollectingBodyInner>,\n}\n\nimpl Default for CollectingBodyInner {\n    fn default() -> Self {\n        CollectingBodyInner::Streaming(Vec::default())\n    }\n}\n\nimpl CollectingBody {\n    /// Returns the length of the body, if it is complete.\n    pub fn length(&self) -> Option<u64> {\n        let state = self.inner.borrow();\n        match *state {\n            CollectingBodyInner::Streaming(_) => None,\n            CollectingBodyInner::Complete { ref body, .. } => {\n                Some(body.iter().map(|chunk| chunk.len()).sum::<usize>() as u64)\n            }\n            CollectingBodyInner::Error(_) => None,\n        }\n    }\n\n    /// Wait until the length is known (or the writer hangs up without finishing).\n    pub async fn known_length(&self) -> Result<u64, error::Error> {\n        let mut recv = self.inner.clone();\n        let state = recv\n            .wait_for(|state| !matches!(state, CollectingBodyInner::Streaming(_)))\n            .await\n            .expect(\"CollectingBody terminated, but not in state Complete or Error\");\n        match &*state {\n            CollectingBodyInner::Streaming(_) => unreachable!(\"wait_for un-matched this\"),\n            CollectingBodyInner::Complete { body, .. } => {\n                Ok(body.iter().map(|v| v.len() as u64).sum())\n            }\n            CollectingBodyInner::Error(error) => {\n                tracing::warn!(\n                    \"could not determine length of cache body; write error: {}\",\n                    error\n                );\n                Err(Error::UnfinishedStreamingBody)\n            }\n        }\n    }\n\n    /// Wait until at least this many bytes have been written, or the body is complete.\n    /// Returns Ok() if the body has at least `want` bytes, Err() otherwise.\n    pub async fn wait_length(&self, want: u64) -> Result<(), error::Error> {\n        let mut recv = self.inner.clone();\n        let state = recv\n            .wait_for(|state| match state {\n                CollectingBodyInner::Streaming(items) => {\n                    let length: u64 = items.iter().map(|v| v.len() as u64).sum();\n                    length >= want\n                }\n                _ => true,\n            })\n            .await\n            .expect(\"CollectingBody terminated, but not in state Complete or Error\");\n\n        match &*state {\n            CollectingBodyInner::Error(_) => Err(Error::UnfinishedStreamingBody),\n            CollectingBodyInner::Complete { body, .. } => {\n                let length: u64 = body.iter().map(|v| v.len() as u64).sum();\n                if length >= want {\n                    Ok(())\n                } else {\n                    Err(Error::UnfinishedStreamingBody)\n                }\n            }\n            CollectingBodyInner::Streaming(_) => Ok(()),\n        }\n    }\n\n    /// Create a new CollectingBody that stores & streams from the provided Body.\n    ///\n    /// Writes to the StreamingBody are collected, and propagated to all readers of this\n    /// CollectingBody.\n    pub fn new(from: Body, length: Option<u64>) -> CollectingBody {\n        let (tx, rx) = watch::channel(CollectingBodyInner::default());\n        let body = CollectingBody { inner: rx };\n        tokio::task::spawn(Self::tee(from, tx, length));\n        body\n    }\n\n    /// \"tee\" a single Body to the watch channel.\n    ///\n    /// This is the worker thread behind a CollectingBody. It reads the data from the provided body\n    /// into a `tokio::sync::watch` channel, which (a) accumulates the body + trailers and (b)\n    /// notifies any subscribed readers of the updates. The readers can safely miss updates or\n    /// start late, as they always can eventually read the state.\n    async fn tee(\n        mut rx: Body,\n        tx: watch::Sender<CollectingBodyInner>,\n        expected_length: Option<u64>,\n    ) {\n        // IMPORTANT IMPLEMENTATION NOTE:\n        // This should always exit with the watched state as Error or Complete.\n\n        // Read data first:\n        let mut length = 0;\n        while let Some(chunk) = rx.data().await {\n            match chunk {\n                Ok(data) => {\n                    tx.send_modify(|state| {\n                        if let CollectingBodyInner::Streaming(chunks) = state {\n                            length += data.len();\n                            chunks.push(data);\n                        } else {\n                            panic!(\"received data after CollectingBody is complete\");\n                        }\n\n                        // Generate length-exceeded error before exiting send_modify.\n                        match expected_length {\n                            Some(expected_length) if (length as u64) > expected_length => {\n                                *state = CollectingBodyInner::Error(Error::UnfinishedStreamingBody);\n                            }\n                            _ => (),\n                        }\n                    });\n                }\n                Err(Error::Again) => continue,\n                Err(e) => {\n                    tx.send_modify(move |state| {\n                        *state = CollectingBodyInner::Error(e);\n                    });\n                    return;\n                }\n            }\n        }\n\n        // We're done with all the data.\n        // Check that we didn't underfill.\n        match expected_length {\n            Some(expected_length) if (length as u64) != expected_length => {\n                tx.send_modify(move |state| {\n                    *state = CollectingBodyInner::Error(Error::UnfinishedStreamingBody);\n                });\n                return;\n            }\n            _ => (),\n        }\n\n        // Then wait for trailers (if any) to be present, which is the \"finish\" signal.\n        let trailers = rx.trailers().await;\n        tx.send_modify(move |state| match trailers {\n            Ok(trailers) => {\n                let CollectingBodyInner::Streaming(chunks) = state else {\n                    panic!(\"received trailers after CollectingBody is complete\")\n                };\n                let mut body = Vec::new();\n                std::mem::swap(&mut body, chunks);\n                *state = CollectingBodyInner::Complete {\n                    body,\n                    trailers: trailers.unwrap_or_default(),\n                }\n            }\n            Err(e) => *state = CollectingBodyInner::Error(e),\n        });\n    }\n\n    /// Get a new read handle to this body.\n    pub fn read(&self) -> Result<Body, error::Error> {\n        self.read_range(0, None)\n    }\n\n    /// Get a handle to read the requested range from this body.\n    /// Start is inclusive, end is exclusive.\n    pub fn read_range(&self, start: u64, end: Option<u64>) -> Result<Body, error::Error> {\n        let mut upstream = self.inner.clone();\n        let (mut tx, rx) = StreamingBody::new();\n        tokio::task::spawn(async move {\n            let mut next_chunk = 0;\n            let mut cursor = 0u64;\n            // The receiver tracks the \"current\" value, and assumes that the value at the receiver\n            // is \"seen\" to begin with.\n            // So we have a do-while loop, with the \"changed\" condition at the bottom.\n            loop {\n                // We'll need to .await to send chunks, but we can't do that while holding a read\n                // lock on the watch channel.\n                // Open a new scope for the lock, copy out the work we need to do, then release the\n                // lock at the end of the scope.\n                let (send_chunks, trailers) = {\n                    // Acquire the read lock:\n                    let current_value = upstream.borrow_and_update();\n                    let send_chunks: Vec<Bytes> = current_value.chunks()[next_chunk..].to_owned();\n                    let trailers = current_value.trailers().cloned();\n                    if let CollectingBodyInner::Error(ref e) = *current_value {\n                        // To trigger a guest error, it is sufficient to not .finish() the\n                        // StreamingBody (so, early return).\n                        // As of 2025-03-21, though, this code is new, so we don't want to\n                        // completely swallow errors!\n                        tracing::warn!(\"error in reading from CollectingBody: {e}\");\n                        return;\n                    }\n                    (send_chunks, trailers)\n                };\n\n                // If send_chunks is nonempty, it contains data for us to forward:\n                next_chunk += send_chunks.len();\n                for chunk in send_chunks {\n                    let chunk_start = cursor;\n                    let chunk_end = cursor + chunk.len() as u64;\n                    cursor = chunk_end;\n\n                    if end.is_some_and(|end| chunk_start >= end) {\n                        // We have sent all the bytes we need to; don't process more chunks on this\n                        // pass.\n                        // But we don't immediately return, as there are still (in the future)\n                        // trailers to process.\n                        break;\n                    }\n\n                    // We need to send either the whole chunk, or a portion of it.\n                    let slice_start = std::cmp::max(start, chunk_start);\n                    let slice_end = std::cmp::min(end.unwrap_or(u64::MAX), chunk_end);\n                    if slice_end <= slice_start {\n                        // Empty slice, skip this chunk.\n                        continue;\n                    }\n\n                    let chunk = if slice_start == chunk_start && slice_end == chunk_end {\n                        // Proceed without copy\n                        chunk\n                    } else {\n                        // Copy out only the bytes of interest\n                        let range = slice_start.saturating_sub(chunk_start) as usize\n                            ..(slice_end.saturating_sub(chunk_start)) as usize;\n                        Bytes::copy_from_slice(&chunk[range])\n                    };\n\n                    if tx.send_chunk(chunk).await.is_err() {\n                        // Reader hung up; we don't care anymore.\n                        return;\n                    }\n                }\n                // And we may have gotten the trailers, which are the \"body is done\" signal:\n                if let Some(trailers) = trailers {\n                    for (k, v) in trailers.iter() {\n                        tx.append_trailer(k.clone(), v.clone());\n                    }\n\n                    // Did the body terminate before our \"end\" offset?\n                    if let Some(end) = end\n                        && cursor < end\n                    {\n                        // Reached the end-of-input without getting the offset we wanted.\n                        // This should be an \"unfinished streaming body\" error;\n                        // so, return without \"finish\"ing.\n                        return;\n                    }\n\n                    // We don't wait for the channel to be closed;\n                    // if the object stays in the cache, the object will be around ~forever.\n                    // Trailers sent -> we're done.\n                    let _ = tx.finish();\n                    return;\n                }\n\n                // Now that we've processed the current state, wait for a change.\n                if upstream.changed().await.is_err() {\n                    return;\n                    // If there's an error, the Channel is closed.\n                    //\n                    // This should only happen if the sender has hung up, i.e. if the object has\n                    // been evicted from the cache *and* the writer is done. In which case:\n                    // - If the writer shut down cleanly, great, we've already shut down cleanly\n                    //      too.\n                    // - If the writer didn't, we don't either.\n                }\n            }\n        });\n        let c: Chunk = rx.into();\n        let b: Body = c.into();\n        Ok(b)\n    }\n}\n\n/// The state of a CollectingBody, within the pubsub (watch) channel.\n#[derive(Debug)]\nenum CollectingBodyInner {\n    Streaming(Vec<Bytes>),\n    Complete {\n        body: Vec<Bytes>,\n        trailers: HeaderMap,\n    },\n    Error(Error),\n}\n\nimpl CollectingBodyInner {\n    /// The chunks currently available.\n    fn chunks(&self) -> &[Bytes] {\n        match self {\n            CollectingBodyInner::Streaming(body) | CollectingBodyInner::Complete { body, .. } => {\n                body\n            }\n            _ => &[],\n        }\n    }\n\n    /// The trailers provided.\n    ///\n    /// Trailers are only present (Some()) if streaming is complete;\n    /// None indicates streaming is still in progress.\n    fn trailers(&self) -> Option<&HeaderMap> {\n        match self {\n            CollectingBodyInner::Complete { trailers, .. } => Some(trailers),\n            _ => None,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::sync::Arc;\n\n    use bytes::{Bytes, BytesMut};\n    use http::{HeaderName, HeaderValue};\n    use tokio::{sync::oneshot, task::JoinSet};\n\n    use crate::{\n        Error,\n        body::{Body, Chunk},\n        collecting_body::CollectingBody,\n        streaming_body::StreamingBody,\n    };\n    use proptest::prelude::*;\n\n    #[tokio::test]\n    async fn stream_and_collect() {\n        let (mut tx, rx) = StreamingBody::new();\n        let chunk: Chunk = rx.into();\n        let body: Body = chunk.into();\n        let collect = CollectingBody::new(body, None);\n\n        let data: Vec<u8> = (0..10).collect();\n\n        // Single-byte chunks:\n        for i in 0..data.len() {\n            tx.send_chunk(&data[i..i + 1]).await.unwrap();\n        }\n        tx.finish().unwrap();\n        let read = collect.read().unwrap();\n        let bytes = read.read_into_vec().await.unwrap();\n        assert_eq!(&bytes, &data);\n    }\n\n    #[tokio::test]\n    async fn stream_and_receive() {\n        let (mut tx, rx) = StreamingBody::new();\n        let chunk: Chunk = rx.into();\n        let body: Body = chunk.into();\n        let collect = CollectingBody::new(body, None);\n\n        let data: Arc<Vec<u8>> = Arc::new((0..10).collect());\n\n        let mut set = JoinSet::new();\n        // Readers:\n        set.spawn({\n            let data = Arc::clone(&data);\n            let read = collect.read().unwrap();\n\n            async move {\n                let bytes = read.read_into_vec().await.unwrap();\n                assert_eq!(&bytes, &*data);\n            }\n        });\n        set.spawn({\n            let data = Arc::clone(&data);\n            let read = collect.read().unwrap();\n\n            async move {\n                let bytes = read.read_into_vec().await.unwrap();\n                assert_eq!(&bytes, &*data);\n            }\n        });\n        // Writer:\n        set.spawn(async move {\n            // Single-byte chunks:\n            for i in 0..data.len() {\n                // Intentionally yield to interleave tasks.\n                tokio::task::yield_now().await;\n                tx.send_chunk(&data[i..i + 1]).await.unwrap();\n            }\n            tx.finish().unwrap();\n        });\n\n        set.join_all().await;\n    }\n\n    #[tokio::test]\n    async fn partial_read_partial_stream() {\n        let (mut tx, rx) = StreamingBody::new();\n        let chunk: Chunk = rx.into();\n        let body: Body = chunk.into();\n        let collect = CollectingBody::new(body, None);\n\n        let data: Arc<Vec<u8>> = Arc::new((0..10).collect());\n        tx.send_chunk(&data[0..2]).await.unwrap();\n        tx.send_chunk(&data[2..5]).await.unwrap();\n\n        let (send, recv) = oneshot::channel();\n\n        // Now start the concurrent read + write:\n        let reader = {\n            let data = Arc::clone(&data);\n            tokio::task::spawn(async move {\n                let mut read = collect.read().unwrap();\n\n                // Wait for the body to have some data ready...\n                read.await_ready().await;\n                // Then signal the writer to write further\n                send.send(()).unwrap();\n                let bytes = read.read_into_vec().await.unwrap();\n                assert_eq!(&bytes, &*data);\n            })\n        };\n        // Wait until the reader is caught up with what is written so far:\n        recv.await.unwrap();\n\n        // Finish the write:\n        tx.send_chunk(&data[5..7]).await.unwrap();\n        tx.send_chunk(&data[7..]).await.unwrap();\n        tx.finish().unwrap();\n\n        reader.await.unwrap();\n    }\n\n    #[tokio::test]\n    async fn unfinished_stream() {\n        let (mut tx, rx) = StreamingBody::new();\n        let chunk: Chunk = rx.into();\n        let body: Body = chunk.into();\n        let collect = CollectingBody::new(body, None);\n\n        let data: Arc<Vec<u8>> = Arc::new((0..10).collect());\n        tx.send_chunk(&data[0..2]).await.unwrap();\n        tx.send_chunk(&data[2..5]).await.unwrap();\n\n        let (send, recv) = oneshot::channel();\n\n        // Start a concurrent read, read some of it:\n        let reader = {\n            tokio::task::spawn(async move {\n                let mut read = collect.read().unwrap();\n                read.await_ready().await;\n                send.send(()).unwrap();\n\n                let err = read.read_into_vec().await.unwrap_err();\n                let Error::UnfinishedStreamingBody = err else {\n                    panic!(\"incorrect error type for streaming error\")\n                };\n            })\n        };\n        // Try to catch the reader up:\n        recv.await.unwrap();\n\n        // Write more, but drop without .finish()ing.\n        tx.send_chunk(&data[5..7]).await.unwrap();\n        std::mem::drop(tx);\n\n        reader.await.unwrap();\n    }\n\n    #[tokio::test]\n    async fn reads_trailers() {\n        let (mut tx, rx) = StreamingBody::new();\n        let chunk: Chunk = rx.into();\n        let body: Body = chunk.into();\n        let collect = CollectingBody::new(body, None);\n        // Start a concurrent read, read some of it:\n        let reader = {\n            tokio::task::spawn(async move {\n                let mut body = collect.read().unwrap();\n\n                while !body.trailers_ready {\n                    body.await_ready().await\n                }\n\n                let v = body.trailers.get(\"~^.^~\").unwrap();\n                assert_eq!(v, r#\"\"is a cat *and* a valid header name\"\"#);\n\n                let data = body.read_into_vec().await.unwrap();\n                assert!(data.is_empty());\n            })\n        };\n\n        tx.append_trailer(\n            HeaderName::from_static(\"~^.^~\"),\n            HeaderValue::from_static(r#\"\"is a cat *and* a valid header name\"\"#),\n        );\n        tx.finish().unwrap();\n\n        reader.await.unwrap();\n    }\n\n    #[tokio::test]\n    async fn completed_stream() {\n        let (mut tx, rx) = StreamingBody::new();\n        // Write the full stream before reading:\n        let chunk: Chunk = rx.into();\n        let body: Body = chunk.into();\n        tx.send_chunk(b\"hello\".as_slice()).await.unwrap();\n        tx.finish().unwrap();\n\n        let collect = CollectingBody::new(body, None);\n        let data = collect.read().unwrap().read_into_vec().await.unwrap();\n        assert_eq!(data, b\"hello\");\n    }\n\n    #[tokio::test]\n    async fn precise_length() {\n        let (mut tx, rx) = StreamingBody::new();\n        // Write the full stream before reading:\n        let chunk: Chunk = rx.into();\n        let body: Body = chunk.into();\n        const BODY: &[u8] = b\"hello\";\n        tx.send_chunk(BODY).await.unwrap();\n        tx.finish().unwrap();\n\n        let collect = CollectingBody::new(body, Some(BODY.len() as u64));\n        let data = collect.read().unwrap().read_into_vec().await.unwrap();\n        assert_eq!(data, BODY);\n    }\n\n    #[tokio::test]\n    async fn error_on_expected_length_underfill() {\n        let (mut tx, rx) = StreamingBody::new();\n\n        let collect = CollectingBody::new(Body::from(Chunk::from(rx)), Some(10));\n\n        tx.send_chunk(b\"hello\".as_slice()).await.unwrap();\n        tx.finish().unwrap();\n\n        let err = collect.read().unwrap().read_into_vec().await.unwrap_err();\n        assert!(matches!(err, Error::UnfinishedStreamingBody));\n    }\n\n    #[tokio::test]\n    async fn error_on_range_underfill() {\n        let (mut tx, rx) = StreamingBody::new();\n\n        let collect = CollectingBody::new(Body::from(Chunk::from(rx)), None);\n\n        let reader = collect.read_range(0, Some(10)).unwrap();\n\n        tx.send_chunk(b\"hello\".as_slice()).await.unwrap();\n        tx.finish().unwrap();\n\n        let err = reader.read_into_vec().await.unwrap_err();\n        assert!(matches!(err, Error::UnfinishedStreamingBody));\n    }\n\n    #[tokio::test]\n    async fn error_on_overfill() {\n        let (mut tx, rx) = StreamingBody::new();\n\n        let collect = CollectingBody::new(Body::from(Chunk::from(rx)), Some(2));\n\n        tx.send_chunk(b\"hello\".as_slice()).await.unwrap();\n        tx.finish().unwrap();\n\n        let err = collect.read().unwrap().read_into_vec().await.unwrap_err();\n        assert!(matches!(err, Error::UnfinishedStreamingBody));\n    }\n\n    /// Proptest strategy: get a nonempty set of Bytes.\n    fn some_bytes() -> impl Strategy<Value = Bytes> {\n        proptest::collection::vec(any::<u8>(), 1..16).prop_map(|v| v.into())\n    }\n\n    /// Proptest strategy: a nonempty set of chunks (Byte blobs).\n    fn some_chunks() -> impl Strategy<Value = Vec<Bytes>> {\n        proptest::collection::vec(some_bytes(), 1..16)\n    }\n\n    /// Utility function for converting from other errors to a TestCaseError, with an annotation.\n    fn fail_test_case<E: std::fmt::Display>(\n        note: &str,\n    ) -> impl Fn(E) -> TestCaseError + use<'_, E> {\n        move |err: E| TestCaseError::fail(format!(\"{note}: {}\", err))\n    }\n\n    async fn test_start_range(body_chunks: Vec<Bytes>, start: u64) -> Result<(), TestCaseError> {\n        let full_body: Bytes = body_chunks\n            .iter()\n            .cloned()\n            .fold(BytesMut::new(), |mut acc, b| {\n                acc.extend(b);\n                acc\n            })\n            .into();\n        if start as usize >= full_body.len() {\n            return Err(TestCaseError::Reject(\"invalid start\".into()));\n        }\n\n        let (mut tx, rx) = StreamingBody::new();\n        let cb = CollectingBody::new(rx.into(), None);\n\n        let mut js = JoinSet::new();\n        js.spawn(async move {\n            for chunk in body_chunks {\n                tx.send_chunk(chunk)\n                    .await\n                    .map_err(fail_test_case(\"error sending chunk\"))?;\n            }\n            tx.finish()\n                .map_err(fail_test_case(\"error finishing write\"))?;\n            Ok(())\n        });\n        js.spawn(async move {\n            let body = cb\n                .read_range(start, None)\n                .map_err(fail_test_case(\"error getting body\"))?;\n            let got = body\n                .read_into_vec()\n                .await\n                .map_err(fail_test_case(\"error reading body\"))?;\n            let want = &full_body[(start as usize)..];\n\n            prop_assert_eq!(&got, want, \"mismatched body data\");\n\n            Ok(())\n        });\n\n        let _: Vec<_> = js.join_all().await.into_iter().collect::<Result<_, _>>()?;\n        Ok(())\n    }\n\n    async fn test_start_end_range(\n        body_chunks: Vec<Bytes>,\n        start: u64,\n        end: u64,\n    ) -> Result<(), TestCaseError> {\n        let full_body: Bytes = body_chunks\n            .iter()\n            .cloned()\n            .fold(BytesMut::new(), |mut acc, b| {\n                acc.extend(b);\n                acc\n            })\n            .into();\n        if start as usize >= full_body.len() {\n            return Err(TestCaseError::Reject(\"invalid start\".into()));\n        }\n\n        let (mut tx, rx) = StreamingBody::new();\n        let cb = CollectingBody::new(rx.into(), None);\n\n        let mut js = JoinSet::new();\n        js.spawn(async move {\n            for chunk in body_chunks {\n                tx.send_chunk(chunk)\n                    .await\n                    .map_err(fail_test_case(\"error sending chunk\"))?;\n            }\n            tx.finish()\n                .map_err(fail_test_case(\"error finishing write\"))?;\n            Ok(())\n        });\n        js.spawn(async move {\n            let body = cb\n                .read_range(start, Some(end))\n                .map_err(fail_test_case(\"error getting body\"))?;\n            let got = body\n                .read_into_vec()\n                .await\n                .map_err(fail_test_case(\"error reading body\"))?;\n            let want = &full_body[(start as usize)..(end as usize)];\n\n            prop_assert_eq!(&got, want, \"mismatched body data\");\n\n            Ok(())\n        });\n\n        let _: Vec<_> = js.join_all().await.into_iter().collect::<Result<_, _>>()?;\n        Ok(())\n    }\n\n    proptest! {\n        #[test]\n        fn read_body_from_start(\n            (body, start) in some_chunks().prop_flat_map(|chunks| {\n                    let len = chunks.iter().map(|v| v.len() as u64).sum();\n                    (Just(chunks), 0..len)\n            }),\n        ) {\n            let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();\n            rt.block_on(test_start_range(body, start))?;\n        }\n\n    }\n\n    proptest! {\n        #[test]\n        fn read_body_start_end(\n            (body, start, end) in some_chunks().prop_flat_map(|chunks| {\n                    let len = chunks.iter().map(|v| v.len() as u64).sum();\n                    (Just(chunks), 0..len, Just(len))\n            }).prop_flat_map(|(chunks, start, len)| {\n                // Inclusive of start is fine:\n                let end = start..len;\n            (Just(chunks), Just(start), end)\n        })) {\n            let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();\n            rt.block_on(test_start_end_range(body, start, end))?;\n        }\n\n    }\n}\n"
  },
  {
    "path": "src/component/adapter/http_req.rs",
    "content": "use {\n    crate::component::bindings::{\n        fastly::adapter::adapter_http_req,\n        fastly::compute::http_downstream::Host as HttpDownstream,\n        fastly::compute::{http_req, types},\n    },\n    crate::linking::{ComponentCtx, SandboxView},\n    wasmtime::component::Resource,\n};\n\n/// Extension trait to add a `.ds_req_handle()` for `ComponentCtx` so to help\n/// `http_req` implementations forward to `http_downstream` implementations.\ntrait DsView {\n    fn ds_req_handle(&self) -> Resource<http_req::Request>;\n}\nimpl DsView for ComponentCtx {\n    fn ds_req_handle(&self) -> Resource<http_req::Request> {\n        self.sandbox().downstream_request().into()\n    }\n}\n\nimpl adapter_http_req::Host for ComponentCtx {\n    fn get_original_header_names(\n        &mut self,\n        max_len: u64,\n        cursor: u32,\n    ) -> Result<(String, Option<u32>), types::Error> {\n        HttpDownstream::downstream_original_header_names(\n            self,\n            self.ds_req_handle(),\n            max_len,\n            cursor,\n        )\n    }\n\n    fn original_header_count(&mut self) -> Result<u32, types::Error> {\n        let h = self.ds_req_handle();\n        HttpDownstream::downstream_original_header_count(self, h)\n    }\n\n    fn downstream_tls_cipher_openssl_name(\n        &mut self,\n        max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        let h = self.ds_req_handle();\n        HttpDownstream::downstream_tls_cipher_openssl_name(self, h, max_len)\n    }\n\n    fn downstream_tls_protocol(&mut self, max_len: u64) -> Result<Option<Vec<u8>>, types::Error> {\n        let h = self.ds_req_handle();\n        HttpDownstream::downstream_tls_protocol(self, h, max_len)\n    }\n\n    fn downstream_tls_raw_client_certificate_deprecated(\n        &mut self,\n        max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        let h = self.ds_req_handle();\n        HttpDownstream::downstream_tls_raw_client_certificate(self, h, max_len)\n    }\n}\n"
  },
  {
    "path": "src/component/adapter.rs",
    "content": "//! Implementations for `fastly:adapter` interfaces.\n\npub mod http_req;\n"
  },
  {
    "path": "src/component/backend.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{backend, http_types, types},\n    crate::config::{Backend, ClientCertInfo},\n    crate::error::Error,\n    crate::sandbox::Sandbox,\n    crate::secret_store::SecretLookup,\n    crate::wiggle_abi::SecretStoreError,\n    http::HeaderValue,\n    http::Uri,\n    std::mem::take,\n    wasmtime::component::{Resource, ResourceTable},\n};\n\npub(crate) async fn register_dynamic_backend(\n    sandbox: &mut Sandbox,\n    table: &mut ResourceTable,\n    prefix: String,\n    target: String,\n    options: Resource<backend::DynamicBackendOptions>,\n) -> Result<Resource<String>, types::Error> {\n    let options = take(table.get_mut(&options)?);\n\n    let name = prefix.as_str();\n    let origin_name = target.as_str();\n\n    let override_host = if let Some(host_override) = options.host_override {\n        if host_override.is_empty() {\n            return Err(types::Error::InvalidArgument);\n        }\n\n        if host_override.len() > 1024 {\n            return Err(types::Error::InvalidArgument);\n        }\n\n        Some(HeaderValue::from_bytes(host_override.as_bytes())?)\n    } else {\n        None\n    };\n\n    let use_tls = options.use_tls;\n    let scheme = if use_tls { \"https\" } else { \"http\" };\n\n    let ca_certs = if use_tls {\n        if let Some(ca_cert) = options.ca_cert {\n            if ca_cert.is_empty() {\n                return Err(types::Error::InvalidArgument);\n            }\n\n            if ca_cert.len() > (64 * 1024) {\n                return Err(types::Error::InvalidArgument);\n            }\n\n            let mut byte_cursor = std::io::Cursor::new(ca_cert.as_bytes());\n            rustls_pemfile::certs(&mut byte_cursor)?\n                .drain(..)\n                .map(rustls::Certificate)\n                .collect()\n        } else {\n            vec![]\n        }\n    } else {\n        vec![]\n    };\n\n    let mut cert_host = if let Some(cert_hostname) = options.cert_hostname {\n        if cert_hostname.is_empty() {\n            return Err(types::Error::InvalidArgument);\n        }\n\n        if cert_hostname.len() > 1024 {\n            return Err(types::Error::InvalidArgument);\n        }\n\n        Some(cert_hostname)\n    } else {\n        None\n    };\n\n    let use_sni = if let Some(sni_hostname) = options.sni_hostname {\n        if sni_hostname.is_empty() {\n            false\n        } else if sni_hostname.len() > 1024 {\n            return Err(types::Error::InvalidArgument);\n        } else {\n            if let Some(cert_host) = &cert_host {\n                if cert_host != &sni_hostname {\n                    // because we're using rustls, we cannot support distinct SNI and cert hostnames\n                    return Err(types::Error::InvalidArgument);\n                }\n            } else {\n                cert_host = Some(sni_hostname);\n            }\n\n            true\n        }\n    } else {\n        true\n    };\n\n    let client_cert = if let Some((client_cert, client_key)) = options.client_cert {\n        let key_lookup = sandbox\n            .secret_lookup(client_key)\n            .ok_or(Error::SecretStoreError(\n                SecretStoreError::InvalidSecretHandle(client_key),\n            ))?;\n        let key = match &key_lookup {\n            SecretLookup::Standard {\n                store_name,\n                secret_name,\n            } => sandbox\n                .secret_stores()\n                .get_store(store_name)\n                .ok_or(Error::SecretStoreError(\n                    SecretStoreError::InvalidSecretHandle(client_key),\n                ))?\n                .get_secret(secret_name)\n                .ok_or(Error::SecretStoreError(\n                    SecretStoreError::InvalidSecretHandle(client_key),\n                ))?\n                .plaintext(),\n\n            SecretLookup::Injected { plaintext } => plaintext,\n        };\n\n        Some(ClientCertInfo::new(client_cert.as_bytes(), key)?)\n    } else {\n        None\n    };\n\n    let grpc = options.grpc;\n\n    let uri = Uri::builder()\n        .scheme(scheme)\n        .authority(origin_name)\n        .path_and_query(\"/\")\n        .build()\n        .map_err(|_e| types::Error::InvalidArgument)?;\n\n    let new_backend = Backend {\n        uri,\n        override_host,\n        cert_host,\n        use_sni,\n        grpc,\n        client_cert,\n        ca_certs,\n        health: crate::config::BackendHealth::Unknown,\n    };\n\n    if !sandbox.add_backend(name, new_backend) {\n        return Err(Error::BackendNameRegistryError(name.to_string()).into());\n    }\n\n    let res = table.push(prefix)?;\n\n    Ok(res)\n}\n\npub(crate) fn exists(sandbox: &mut Sandbox, backend: &str) -> bool {\n    sandbox.backend(backend).is_some()\n}\n\npub(crate) fn is_healthy(\n    sandbox: &mut Sandbox,\n    backend: &str,\n) -> Result<backend::BackendHealth, types::Error> {\n    let backend = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    match &backend.health {\n        crate::config::BackendHealth::Unknown => Ok(backend::BackendHealth::Unknown),\n        crate::config::BackendHealth::Healthy => Ok(backend::BackendHealth::Healthy),\n        crate::config::BackendHealth::Unhealthy => Ok(backend::BackendHealth::Unhealthy),\n    }\n}\n\npub(crate) fn is_dynamic(sandbox: &mut Sandbox, backend: &str) -> Result<bool, types::Error> {\n    if sandbox.dynamic_backend(backend).is_some() {\n        Ok(true)\n    } else if sandbox.backend(backend).is_some() {\n        Ok(false)\n    } else {\n        Err(Error::InvalidArgument.into())\n    }\n}\n\npub(crate) fn get_host(\n    sandbox: &mut Sandbox,\n    backend: &str,\n    _max_len: u64,\n) -> Result<String, types::Error> {\n    // just doing this to get a different error if the backend doesn't exist\n    let _ = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    Err(Error::Unsupported {\n        msg: \"`get-host` is not actually supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_override_host(\n    sandbox: &mut Sandbox,\n    backend: &str,\n    max_len: u64,\n) -> Result<Option<Vec<u8>>, types::Error> {\n    let backend = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    if let Some(host) = backend.override_host.as_ref() {\n        let host = host.to_str()?;\n\n        if host.len() > usize::try_from(max_len).unwrap() {\n            return Err(Error::BufferLengthError {\n                buf: \"host_out\",\n                len: \"host_max_len\",\n            }\n            .into());\n        }\n\n        Ok(Some(host.as_bytes().to_owned()))\n    } else {\n        Ok(None)\n    }\n}\n\npub(crate) fn get_port(sandbox: &mut Sandbox, backend: &str) -> Result<u16, types::Error> {\n    let backend = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    match backend.uri.port_u16() {\n        Some(port) => Ok(port),\n        None => {\n            if backend.uri.scheme() == Some(&http::uri::Scheme::HTTPS) {\n                Ok(443)\n            } else {\n                Ok(80)\n            }\n        }\n    }\n}\n\npub(crate) fn get_connect_timeout_ms(\n    sandbox: &mut Sandbox,\n    backend: &str,\n) -> Result<u32, types::Error> {\n    // just doing this to get a different error if the backend doesn't exist\n    let _ = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    Err(Error::Unsupported {\n        msg: \"connection timing is not actually supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_first_byte_timeout_ms(\n    sandbox: &mut Sandbox,\n    backend: &str,\n) -> Result<u32, types::Error> {\n    // just doing this to get a different error if the backend doesn't exist\n    let _ = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    Err(Error::Unsupported {\n        msg: \"connection timing is not actually supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_between_bytes_timeout_ms(\n    sandbox: &mut Sandbox,\n    backend: &str,\n) -> Result<u32, types::Error> {\n    // just doing this to get a different error if the backend doesn't exist\n    let _ = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    Err(Error::Unsupported {\n        msg: \"connection timing is not actually supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn is_tls(sandbox: &mut Sandbox, backend: &str) -> Result<bool, types::Error> {\n    let backend = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    Ok(backend.uri.scheme() == Some(&http::uri::Scheme::HTTPS))\n}\n\npub(crate) fn get_tls_min_version(\n    sandbox: &mut Sandbox,\n    backend: &str,\n) -> Result<Option<http_types::TlsVersion>, types::Error> {\n    // just doing this to get a different error if the backend doesn't exist\n    let _ = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    // health checks are not enabled in Viceroy :(\n    Err(Error::Unsupported {\n        msg: \"tls version flags are not supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_tls_max_version(\n    sandbox: &mut Sandbox,\n    backend: &str,\n) -> Result<Option<http_types::TlsVersion>, types::Error> {\n    // just doing this to get a different error if the backend doesn't exist\n    let _ = sandbox.backend(backend).ok_or(Error::InvalidArgument)?;\n    // health checks are not enabled in Viceroy :(\n    Err(Error::Unsupported {\n        msg: \"tls version flags are not supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_http_keepalive_time(\n    _sandbox: &mut Sandbox,\n    _backend: &str,\n) -> Result<backend::TimeoutMs, types::Error> {\n    Err(Error::Unsupported {\n        msg: \"backend.get-http-keepalive-time is not supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_tcp_keepalive_enable(\n    _sandbox: &mut Sandbox,\n    _backend: &str,\n) -> Result<bool, types::Error> {\n    Err(Error::Unsupported {\n        msg: \"backend.get-tcp-keepalive-enable is not supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_tcp_keepalive_interval(\n    _sandbox: &mut Sandbox,\n    _backend: &str,\n) -> Result<backend::TimeoutSecs, types::Error> {\n    Err(Error::Unsupported {\n        msg: \"backend.get-tcp-keepalive-interval is not supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_tcp_keepalive_probes(\n    _sandbox: &mut Sandbox,\n    _backend: &str,\n) -> Result<backend::ProbeCount, types::Error> {\n    Err(Error::Unsupported {\n        msg: \"backend.get-tcp-keepalive-probes is not supported in Viceroy\",\n    }\n    .into())\n}\n\npub(crate) fn get_tcp_keepalive_time(\n    _sandbox: &mut Sandbox,\n    _backend: &str,\n) -> Result<backend::TimeoutSecs, types::Error> {\n    Err(Error::Unsupported {\n        msg: \"backend.get_tcp_keepalive_time not supported in Viceroy\",\n    }\n    .into())\n}\n"
  },
  {
    "path": "src/component/compute/acl.rs",
    "content": "use crate::component::bindings::fastly::compute::{acl, http_body, types};\nuse crate::linking::{ComponentCtx, SandboxView};\nuse std::net::IpAddr;\nuse wasmtime::component::Resource;\n\nimpl acl::Host for ComponentCtx {}\n\nimpl acl::HostAcl for ComponentCtx {\n    fn open(&mut self, acl_name: String) -> Result<Resource<acl::Acl>, types::OpenError> {\n        let handle = self\n            .sandbox_mut()\n            .acl_handle_by_name(&acl_name)\n            .ok_or(types::OpenError::NotFound)?;\n        Ok(handle.into())\n    }\n\n    fn lookup(\n        &mut self,\n        acl_handle: Resource<acl::Acl>,\n        ip_addr: acl::IpAddress,\n    ) -> Result<Option<Resource<http_body::Body>>, acl::AclError> {\n        let acl = self.sandbox().acl_by_handle(acl_handle.into()).unwrap();\n\n        let ip: IpAddr = ip_addr.into();\n\n        match acl.lookup(ip) {\n            Some(entry) => {\n                let body =\n                    serde_json::to_vec_pretty(&entry).map_err(|_| acl::AclError::GenericError)?;\n                let body_handle = self.sandbox_mut().insert_body(body.into());\n                Ok(Some(body_handle.into()))\n            }\n            None => Ok(None),\n        }\n    }\n\n    fn drop(&mut self, _acl_handle: Resource<acl::Acl>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/async_io.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::async_io,\n    crate::{\n        linking::{ComponentCtx, SandboxView},\n        wiggle_abi,\n    },\n    anyhow::bail,\n    futures::FutureExt,\n    std::time::Duration,\n    wasmtime::component::Resource,\n};\n\nimpl async_io::Host for ComponentCtx {\n    async fn select(&mut self, hs: Vec<Resource<async_io::Pollable>>) -> wasmtime::Result<u32> {\n        if hs.is_empty() {\n            bail!(\"`select` without a timeout must have at least one handle\");\n        }\n\n        let select_fut = self.sandbox_mut().select_impl(\n            hs.into_iter()\n                .map(|i| wiggle_abi::types::AsyncItemHandle::from(i).into()),\n        );\n\n        let h = select_fut.await.unwrap();\n        Ok(h as u32)\n    }\n\n    async fn select_with_timeout(\n        &mut self,\n        hs: Vec<Resource<async_io::Pollable>>,\n        timeout_ms: u32,\n    ) -> Option<u32> {\n        let select_fut = self.sandbox_mut().select_impl(hs.into_iter().map(|i| {\n            crate::sandbox::AsyncItemHandle::from(wiggle_abi::types::AsyncItemHandle::from(i))\n        }));\n\n        tokio::time::timeout(Duration::from_millis(timeout_ms as u64), select_fut)\n            .await\n            .ok()\n            .map(|h| h.unwrap() as u32)\n    }\n}\n\nimpl async_io::HostPollable for ComponentCtx {\n    fn new_ready(&mut self) -> Resource<async_io::Pollable> {\n        wiggle_abi::types::AsyncItemHandle::from(self.sandbox_mut().new_ready()).into()\n    }\n\n    fn is_ready(&mut self, handle: Resource<async_io::Pollable>) -> bool {\n        let handle = wiggle_abi::types::AsyncItemHandle::from(handle);\n        self.sandbox_mut()\n            .async_item_mut(handle.into())\n            .unwrap()\n            .await_ready()\n            .now_or_never()\n            .is_some()\n    }\n\n    fn drop(&mut self, handle: Resource<async_io::Pollable>) -> wasmtime::Result<()> {\n        let handle = wiggle_abi::types::AsyncItemHandle::from(handle).into();\n\n        // Use `.take_async_item` instead of manipulating\n        // `self.sandbox_mut().async_items` directly, so that any extra state\n        // associated with the item is also cleared.\n        let _ = self.sandbox_mut().take_async_item(handle).unwrap();\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/backend.rs",
    "content": "use std::num::NonZeroUsize;\nuse std::time::Duration;\n\nuse {\n    crate::component::bindings::fastly::compute::{backend, http_types, types},\n    crate::linking::ComponentCtx,\n    crate::wiggle_abi::types::SecretHandle,\n    wasmtime::component::Resource,\n    wasmtime_wasi_io::IoView,\n};\n\nimpl backend::Host for ComponentCtx {\n    async fn register_dynamic_backend(\n        &mut self,\n        prefix: String,\n        target: String,\n        options: Resource<backend::DynamicBackendOptions>,\n    ) -> Result<Resource<String>, types::Error> {\n        crate::component::backend::register_dynamic_backend(\n            &mut self.sandbox,\n            &mut self.wasi_table,\n            prefix,\n            target,\n            options,\n        )\n        .await\n    }\n}\n\nimpl backend::HostBackend for ComponentCtx {\n    fn open(&mut self, name: String) -> Result<Resource<String>, backend::OpenError> {\n        if !crate::component::backend::exists(&mut self.sandbox, &name) {\n            return Err(backend::OpenError::NotFound);\n        }\n\n        let res = self.table().push(name).unwrap();\n\n        Ok(res)\n    }\n\n    fn get_name(&mut self, name: Resource<String>) -> String {\n        self.wasi_table.get(&name).unwrap().to_owned()\n    }\n\n    fn is_healthy(\n        &mut self,\n        name: Resource<String>,\n    ) -> Result<backend::BackendHealth, types::Error> {\n        let name = self.wasi_table.get(&name).unwrap();\n        crate::component::backend::is_healthy(&mut self.sandbox, name)\n    }\n\n    fn is_dynamic(&mut self, name: Resource<String>) -> Result<bool, types::Error> {\n        let name = self.wasi_table.get(&name).unwrap();\n        crate::component::backend::is_dynamic(&mut self.sandbox, name)\n    }\n\n    fn get_host(&mut self, name: Resource<String>, max_len: u64) -> Result<String, types::Error> {\n        let name = self.wasi_table.get(&name).unwrap();\n        crate::component::backend::get_host(&mut self.sandbox, name, max_len)\n    }\n\n    fn get_override_host(\n        &mut self,\n        name: Resource<String>,\n        max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        let name = self.wasi_table.get(&name).unwrap();\n        crate::component::backend::get_override_host(&mut self.sandbox, name, max_len)\n    }\n\n    fn get_port(&mut self, backend: Resource<String>) -> Result<u16, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_port(&mut self.sandbox, backend)\n    }\n\n    fn get_connect_timeout_ms(&mut self, backend: Resource<String>) -> Result<u32, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_connect_timeout_ms(&mut self.sandbox, backend)\n    }\n\n    fn get_first_byte_timeout_ms(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<u32, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_first_byte_timeout_ms(&mut self.sandbox, backend)\n    }\n\n    fn get_between_bytes_timeout_ms(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<u32, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_between_bytes_timeout_ms(&mut self.sandbox, backend)\n    }\n\n    fn get_http_keepalive_time(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<backend::TimeoutMs, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_http_keepalive_time(&mut self.sandbox, backend)\n    }\n\n    fn get_tcp_keepalive_enable(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<bool, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_tcp_keepalive_enable(&mut self.sandbox, backend)\n    }\n\n    fn get_tcp_keepalive_interval(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<backend::TimeoutSecs, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_tcp_keepalive_interval(&mut self.sandbox, backend)\n    }\n\n    fn get_tcp_keepalive_probes(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<backend::ProbeCount, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_tcp_keepalive_probes(&mut self.sandbox, backend)\n    }\n\n    fn get_tcp_keepalive_time(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<backend::TimeoutSecs, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_tcp_keepalive_time(&mut self.sandbox, backend)\n    }\n\n    fn is_tls(&mut self, backend: Resource<String>) -> Result<bool, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::is_tls(&mut self.sandbox, backend)\n    }\n\n    fn get_tls_min_version(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<Option<http_types::TlsVersion>, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_tls_min_version(&mut self.sandbox, backend)\n    }\n\n    fn get_tls_max_version(\n        &mut self,\n        backend: Resource<String>,\n    ) -> Result<Option<http_types::TlsVersion>, types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::backend::get_tls_max_version(&mut self.sandbox, backend)\n    }\n\n    fn drop(&mut self, backend: Resource<String>) -> wasmtime::Result<()> {\n        self.table().delete(backend)?;\n        Ok(())\n    }\n}\n\n/// Implementation of the `DynamicBackendOptions` resource.\n#[derive(Debug)]\npub struct BackendBuilder {\n    pub(crate) host_override: Option<String>,\n    pub(crate) connect_timeout: u32,\n    pub(crate) first_byte_timeout: u32,\n    pub(crate) between_bytes_timeout: u32,\n    pub(crate) use_tls: bool,\n    pub(crate) tls_min_version: Option<backend::TlsVersion>,\n    pub(crate) tls_max_version: Option<backend::TlsVersion>,\n    pub(crate) cert_hostname: Option<String>,\n    pub(crate) ca_cert: Option<String>,\n    pub(crate) ciphers: Option<String>,\n    pub(crate) sni_hostname: Option<String>,\n    pub(crate) client_cert: Option<(String, SecretHandle)>,\n    pub(crate) keepalive: bool,\n    pub(crate) http_keepalive_time_ms: u32,\n    pub(crate) tcp_keepalive_enable: u32,\n    pub(crate) tcp_keepalive_interval_secs: u32,\n    pub(crate) tcp_keepalive_probes: u32,\n    pub(crate) tcp_keepalive_time_secs: u32,\n    pub(crate) max_connections: u32,\n    pub(crate) max_use: Option<NonZeroUsize>,\n    pub(crate) max_lifetime: Duration,\n    pub(crate) prefer_ipv6: bool,\n    pub(crate) grpc: bool,\n    pub(crate) pooling: bool,\n}\n\nimpl Default for BackendBuilder {\n    fn default() -> Self {\n        BackendBuilder {\n            host_override: None,\n            connect_timeout: 1_000,\n            first_byte_timeout: 15_000,\n            between_bytes_timeout: 10_000,\n            use_tls: false,\n            tls_min_version: None,\n            tls_max_version: None,\n            cert_hostname: None,\n            ca_cert: None,\n            ciphers: None,\n            sni_hostname: None,\n            client_cert: None,\n            keepalive: false,\n            http_keepalive_time_ms: 0,\n            tcp_keepalive_enable: 0,\n            tcp_keepalive_interval_secs: 0,\n            tcp_keepalive_probes: 0,\n            tcp_keepalive_time_secs: 0,\n            max_connections: 0,\n            max_use: None,\n            max_lifetime: Duration::ZERO,\n            grpc: false,\n            pooling: true,\n            prefer_ipv6: true,\n        }\n    }\n}\n\nimpl backend::HostDynamicBackendOptions for ComponentCtx {\n    fn new(&mut self) -> wasmtime::Result<Resource<backend::DynamicBackendOptions>> {\n        let builder = BackendBuilder::default();\n\n        Ok(self.table().push(builder)?)\n    }\n\n    fn override_host(&mut self, config: Resource<backend::DynamicBackendOptions>, value: String) {\n        self.table().get_mut(&config).unwrap().host_override = Some(value);\n    }\n\n    fn connect_timeout(&mut self, config: Resource<backend::DynamicBackendOptions>, value: u32) {\n        self.table().get_mut(&config).unwrap().connect_timeout = value;\n    }\n\n    fn first_byte_timeout(&mut self, config: Resource<backend::DynamicBackendOptions>, value: u32) {\n        self.table().get_mut(&config).unwrap().first_byte_timeout = value;\n    }\n\n    fn between_bytes_timeout(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        value: u32,\n    ) {\n        self.table().get_mut(&config).unwrap().between_bytes_timeout = value;\n    }\n\n    fn use_tls(&mut self, config: Resource<backend::DynamicBackendOptions>, value: bool) {\n        self.table().get_mut(&config).unwrap().use_tls = value;\n    }\n\n    fn tls_min_version(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        value: backend::TlsVersion,\n    ) {\n        self.table().get_mut(&config).unwrap().tls_min_version = Some(value);\n    }\n\n    fn tls_max_version(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        value: backend::TlsVersion,\n    ) {\n        self.table().get_mut(&config).unwrap().tls_max_version = Some(value);\n    }\n\n    fn cert_hostname(&mut self, config: Resource<backend::DynamicBackendOptions>, value: String) {\n        self.table().get_mut(&config).unwrap().cert_hostname = Some(value);\n    }\n\n    fn ca_certificate(&mut self, config: Resource<backend::DynamicBackendOptions>, value: String) {\n        self.table().get_mut(&config).unwrap().ca_cert = Some(value);\n    }\n\n    fn tls_ciphers(&mut self, config: Resource<backend::DynamicBackendOptions>, value: String) {\n        self.table().get_mut(&config).unwrap().ciphers = Some(value);\n    }\n\n    fn sni_hostname(&mut self, config: Resource<backend::DynamicBackendOptions>, value: String) {\n        self.table().get_mut(&config).unwrap().sni_hostname = Some(value);\n    }\n\n    fn client_cert(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        client_cert: String,\n        client_key: Resource<backend::Secret>,\n    ) {\n        let client_key = SecretHandle::from(client_key);\n        self.table().get_mut(&config).unwrap().client_cert = Some((client_cert, client_key));\n    }\n\n    fn http_keepalive_time_ms(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        value: u32,\n    ) {\n        let config = self.table().get_mut(&config).unwrap();\n        config.keepalive = true;\n        config.http_keepalive_time_ms = value;\n    }\n\n    fn tcp_keepalive_enable(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        value: u32,\n    ) {\n        let config = self.table().get_mut(&config).unwrap();\n        config.keepalive = true;\n        config.tcp_keepalive_enable = value;\n    }\n\n    fn tcp_keepalive_interval_secs(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        value: u32,\n    ) {\n        let config = self.table().get_mut(&config).unwrap();\n        config.keepalive = true;\n        config.tcp_keepalive_interval_secs = value;\n    }\n\n    fn tcp_keepalive_probes(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        value: u32,\n    ) {\n        let config = self.table().get_mut(&config).unwrap();\n        config.keepalive = true;\n        config.tcp_keepalive_probes = value;\n    }\n\n    fn tcp_keepalive_time_secs(\n        &mut self,\n        config: Resource<backend::DynamicBackendOptions>,\n        value: u32,\n    ) {\n        let config = self.table().get_mut(&config).unwrap();\n        config.keepalive = true;\n        config.tcp_keepalive_time_secs = value;\n    }\n\n    fn max_connections(&mut self, config: Resource<backend::DynamicBackendOptions>, value: u32) {\n        self.table().get_mut(&config).unwrap().max_connections = value;\n    }\n\n    fn max_use(&mut self, config: Resource<backend::DynamicBackendOptions>, value: u32) {\n        self.table().get_mut(&config).unwrap().max_use = NonZeroUsize::new(value as _);\n    }\n\n    fn max_lifetime_ms(&mut self, config: Resource<backend::DynamicBackendOptions>, value: u32) {\n        self.table().get_mut(&config).unwrap().max_lifetime = Duration::from_millis(value as _);\n    }\n\n    fn grpc(&mut self, config: Resource<backend::DynamicBackendOptions>, value: bool) {\n        self.table().get_mut(&config).unwrap().grpc = value;\n    }\n\n    fn pooling(&mut self, config: Resource<backend::DynamicBackendOptions>, value: bool) {\n        self.table().get_mut(&config).unwrap().pooling = value;\n    }\n\n    fn prefer_ipv6(&mut self, config: Resource<backend::DynamicBackendOptions>, value: bool) {\n        self.table().get_mut(&config).unwrap().prefer_ipv6 = value;\n    }\n\n    fn drop(&mut self, options: Resource<backend::DynamicBackendOptions>) -> wasmtime::Result<()> {\n        self.table().delete(options)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/cache.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{cache as api, http_body, types},\n    crate::{\n        body::Body,\n        cache::{self, CacheKey, SurrogateKeySet, VaryRule, WriteOptions},\n        error::Error,\n        linking::{ComponentCtx, SandboxView},\n        sandbox::{PeekableTask, PendingCacheTask, Sandbox},\n        wiggle_abi::types::{CacheBusyHandle, CacheHandle},\n    },\n    bytes::Bytes,\n    http::HeaderMap,\n    std::{sync::Arc, time::Duration},\n    wasmtime::component::Resource,\n};\n\n// Utility for remapping the errors.\nfn get_key(key: Vec<u8>) -> Result<CacheKey, types::Error> {\n    key.try_into()\n        .map_err(|_| types::Error::BufferLen(CacheKey::MAX_LENGTH as u64))\n}\n\nfn load_write_options(options: &api::WriteOptions) -> Result<WriteOptions, Error> {\n    // Headers must be handled before load_write_options:\n    assert!(\n        options.request_headers.is_none(),\n        \"Viceroy bug! headers must be handled before load_write_options\"\n    );\n\n    let max_age = Duration::from_nanos(options.max_age_ns);\n\n    let initial_age = if let Some(initial_age_ns) = options.initial_age_ns {\n        Duration::from_nanos(initial_age_ns)\n    } else {\n        Duration::ZERO\n    };\n\n    let stale_while_revalidate =\n        if let Some(stale_while_revalidate_ns) = options.stale_while_revalidate_ns {\n            Duration::from_nanos(stale_while_revalidate_ns)\n        } else {\n            Duration::ZERO\n        };\n\n    let vary_rule = if let Some(vary_rule) = &options.vary_rule {\n        vary_rule.parse()?\n    } else {\n        VaryRule::default()\n    };\n\n    let user_metadata = if let Some(user_metadata) = &options.user_metadata {\n        Bytes::copy_from_slice(user_metadata)\n    } else {\n        Bytes::new()\n    };\n\n    let length = options.length;\n    let sensitive_data = options.sensitive_data;\n\n    let edge_max_age = if let Some(edge_max_age_ns) = options.edge_max_age_ns {\n        Duration::from_nanos(edge_max_age_ns)\n    } else {\n        max_age\n    };\n    if edge_max_age > max_age {\n        tracing::error!(\n            \"deliver node max age {} must be less than TTL {}\",\n            edge_max_age.as_secs(),\n            max_age.as_secs()\n        );\n        return Err(Error::InvalidArgument);\n    }\n\n    let surrogate_keys = if let Some(surrogate_keys) = &options.surrogate_keys {\n        surrogate_keys.as_bytes().try_into()?\n    } else {\n        SurrogateKeySet::default()\n    };\n\n    Ok(WriteOptions {\n        max_age,\n        initial_age,\n        stale_while_revalidate,\n        vary_rule,\n        user_metadata,\n        length,\n        sensitive_data,\n        surrogate_keys,\n        edge_max_age,\n    })\n}\n\nstruct LookupOptions {\n    headers: HeaderMap,\n    always_use_requested_range: bool,\n}\n\nfn load_lookup_options(\n    sandbox: &Sandbox,\n    options: api::LookupOptions,\n) -> Result<LookupOptions, Error> {\n    let headers = if let Some(request_headers) = options.request_headers {\n        let handle = request_headers;\n        let parts = sandbox.request_parts(handle.into())?;\n        parts.headers.clone()\n    } else {\n        HeaderMap::default()\n    };\n\n    let always_use_requested_range = options.always_use_requested_range;\n\n    Ok(LookupOptions {\n        headers,\n        always_use_requested_range,\n    })\n}\n\nimpl api::Host for ComponentCtx {\n    async fn insert(\n        &mut self,\n        key: Vec<u8>,\n        mut options: api::WriteOptions,\n    ) -> Result<Resource<api::Body>, types::Error> {\n        let key: CacheKey = get_key(key)?;\n        let cache = Arc::clone(self.sandbox().cache());\n\n        let request_headers = if let Some(handle) = options.request_headers.take() {\n            let parts = self.sandbox().request_parts(handle.into())?;\n            parts.headers.clone()\n        } else {\n            HeaderMap::default()\n        };\n        let options = load_write_options(&options)?;\n\n        let handle = self.sandbox_mut().insert_body(Body::empty());\n        let read_body = self.sandbox_mut().begin_streaming(handle)?;\n        cache\n            .insert(&key, request_headers, options, read_body)\n            .await;\n        Ok(handle.into())\n    }\n\n    async fn replace_insert(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n        _options: api::WriteOptions,\n    ) -> Result<Resource<api::Body>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn await_entry(\n        &mut self,\n        handle: Resource<api::PendingEntry>,\n    ) -> Result<Resource<api::Entry>, types::Error> {\n        let handle = CacheBusyHandle::from(handle).into();\n        // Swap out for a distinct handle, so we don't hit a repeated `close`+`close_busy`:\n        let entry = self.sandbox_mut().cache_entry_mut(handle).await?;\n        let mut other_entry = entry.stub();\n        std::mem::swap(entry, &mut other_entry);\n        let task = PeekableTask::spawn(Box::pin(async move { Ok(other_entry) })).await;\n        let h: CacheHandle = self\n            .sandbox_mut()\n            .insert_cache_op(PendingCacheTask::new(task))\n            .into();\n        Ok(h.into())\n    }\n\n    fn close_pending_entry(\n        &mut self,\n        handle: Resource<api::PendingEntry>,\n    ) -> Result<(), types::Error> {\n        let handle = CacheBusyHandle::from(handle).into();\n        // Don't wait for the transaction to complete; drop the future to cancel.\n        let _ = self.sandbox_mut().take_cache_entry(handle)?;\n        Ok(())\n    }\n\n    async fn close_entry(&mut self, handle: Resource<api::Entry>) -> Result<(), types::Error> {\n        let _ = self\n            .sandbox_mut()\n            .take_cache_entry(handle.into())?\n            .task()\n            .recv()\n            .await?;\n        Ok(())\n    }\n\n    async fn close_replace_entry(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n    ) -> Result<(), types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n}\n\nimpl api::HostReplaceEntry for ComponentCtx {\n    async fn replace(\n        &mut self,\n        key: Vec<u8>,\n        _options: api::ReplaceOptions,\n    ) -> Result<Resource<api::ReplaceEntry>, types::Error> {\n        let _key: CacheKey = get_key(key)?;\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_age_ns(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n    ) -> Result<Option<api::DurationNs>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_body(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n        _options: api::GetBodyOptions,\n    ) -> Result<Option<Resource<http_body::Body>>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_hits(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n    ) -> Result<Option<u64>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_length(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n    ) -> Result<Option<u64>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_max_age_ns(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n    ) -> Result<Option<api::DurationNs>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_stale_while_revalidate_ns(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n    ) -> Result<Option<api::DurationNs>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_state(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n    ) -> Result<Option<api::LookupState>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_user_metadata(\n        &mut self,\n        _handle: Resource<api::ReplaceEntry>,\n        _max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn drop(&mut self, _entry: Resource<api::ReplaceEntry>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl api::HostEntry for ComponentCtx {\n    async fn lookup(\n        &mut self,\n        key: Vec<u8>,\n        options: api::LookupOptions,\n    ) -> Result<Resource<api::Entry>, types::Error> {\n        let LookupOptions {\n            headers,\n            always_use_requested_range,\n        } = load_lookup_options(self.sandbox(), options)?;\n\n        let key: CacheKey = get_key(key)?;\n        let cache = Arc::clone(self.sandbox().cache());\n\n        let task = PeekableTask::spawn(Box::pin(async move {\n            Ok(cache\n                .lookup(&key, &headers)\n                .await\n                .with_always_use_requested_range(always_use_requested_range))\n        }))\n        .await;\n        let task = PendingCacheTask::new(task);\n        let handle: CacheHandle = self.sandbox_mut().insert_cache_op(task).into();\n        Ok(handle.into())\n    }\n\n    async fn get_body(\n        &mut self,\n        handle: Resource<api::Entry>,\n        options: api::GetBodyOptions,\n    ) -> Result<Resource<http_body::Body>, types::Error> {\n        let handle = handle.into();\n\n        let from = options.from;\n        let to = options.to;\n\n        // We wind up re-borrowing `found` and `self.sandbox` several times here, to avoid\n        // borrowing the both of them at once.\n        // (It possible that inserting a body would change the address of Found, by re-shuffling\n        // the AsyncItems table; we have to live by borrowck's rules.)\n        //\n        // We have an exclusive borrow self.sandbox_mut() for the lifetime of this call,\n        // so even though we're re-borrowing/repeating lookups, we know we won't run into TOCTOU.\n\n        let entry = self.sandbox_mut().cache_entry(handle).await?;\n\n        // Preemptively (optimistically) start a read. Don't worry, the Drop impl for Body will\n        // clean up the copying task.\n        // We have to do this to allow `found`'s lifetime to end before self.sandbox().body, which\n        // has to re-borrow self.self.sandbox().\n        let body = entry.body(from, to).await?;\n        let found = entry\n            .found()\n            .ok_or(Error::CacheError(crate::cache::Error::Missing))?;\n\n        if let Some(prev_handle) = found.last_body_handle {\n            // Check if they're still reading the previous handle.\n            if self.sandbox().body(prev_handle).is_ok() {\n                return Err(Error::CacheError(cache::Error::HandleBodyUsed).into());\n            }\n        };\n\n        let body_handle = self.sandbox_mut().insert_body(body);\n\n        // Finalize by committing the handle as \"the last read\".\n        // We have to borrow `found` again, this time as mutable.\n        self.sandbox_mut()\n            .cache_entry_mut(handle)\n            .await?\n            .found_mut()\n            .unwrap()\n            .last_body_handle = Some(body_handle);\n\n        Ok(body_handle.into())\n    }\n\n    async fn transaction_lookup(\n        &mut self,\n        key: Vec<u8>,\n        options: api::LookupOptions,\n    ) -> Result<Resource<api::Entry>, types::Error> {\n        let h = self.transaction_lookup_async(key, options).await?;\n        api::Host::await_entry(self, h).await\n    }\n\n    async fn transaction_lookup_async(\n        &mut self,\n        key: Vec<u8>,\n        options: api::LookupOptions,\n    ) -> Result<Resource<api::PendingEntry>, types::Error> {\n        let LookupOptions {\n            headers,\n            always_use_requested_range,\n        } = load_lookup_options(self.sandbox(), options)?;\n\n        let key: CacheKey = get_key(key)?;\n        let cache = Arc::clone(self.sandbox().cache());\n\n        // Look up once, joining the transaction only if obligated:\n        let e = cache\n            .transaction_lookup(&key, &headers, false)\n            .await\n            .with_always_use_requested_range(always_use_requested_range);\n        let ready = e.found().is_some() || e.go_get().is_some();\n        // If we already got _something_, we can provide an already-complete PeekableTask.\n        // Otherwise we need to spawn it and let it block in the background.\n        let task = if ready {\n            PeekableTask::complete(e)\n        } else {\n            PeekableTask::spawn(Box::pin(async move {\n                Ok(cache\n                    .transaction_lookup(&key, &headers, true)\n                    .await\n                    .with_always_use_requested_range(always_use_requested_range))\n            }))\n            .await\n        };\n\n        let task = PendingCacheTask::new(task);\n        let handle: CacheBusyHandle = self.sandbox_mut().insert_cache_op(task).into();\n        Ok(handle.into())\n    }\n\n    async fn transaction_insert(\n        &mut self,\n        handle: Resource<api::Entry>,\n        options: api::WriteOptions,\n    ) -> Result<Resource<http_body::Body>, types::Error> {\n        let (body, cache_handle) = self\n            .transaction_insert_and_stream_back(handle, options)\n            .await?;\n        // Ignore the \"stream back\" handle\n        let _ = self.sandbox_mut().take_cache_entry(cache_handle.into())?;\n        Ok(body)\n    }\n\n    async fn transaction_insert_and_stream_back(\n        &mut self,\n        handle: Resource<api::Entry>,\n        options: api::WriteOptions,\n    ) -> Result<(Resource<http_body::Body>, Resource<api::Entry>), types::Error> {\n        // No request headers here; request headers come from the original lookup.\n        if options.request_headers.is_some() {\n            return Err(Error::InvalidArgument.into());\n        }\n        let options = load_write_options(&options)?;\n\n        // Optimistically start a body, so we don't have to reborrow self.sandbox_mut()\n        let body_handle = self.sandbox_mut().insert_body(Body::empty());\n        let read_body = self.sandbox_mut().begin_streaming(body_handle)?;\n\n        let e = self\n            .sandbox_mut()\n            .cache_entry_mut(handle.into())\n            .await?\n            .insert(options, read_body)?;\n        // Return a new handle for the read end.\n        let handle: CacheHandle = self\n            .sandbox_mut()\n            .insert_cache_op(PendingCacheTask::new(PeekableTask::complete(e)))\n            .into();\n\n        Ok((body_handle.into(), handle.into()))\n    }\n\n    async fn transaction_update(\n        &mut self,\n        handle: Resource<api::Entry>,\n        options: api::WriteOptions,\n    ) -> Result<(), types::Error> {\n        // No request headers here; request headers come from the original lookup.\n        if options.request_headers.is_some() {\n            return Err(Error::InvalidArgument.into());\n        }\n        let options = load_write_options(&options)?;\n\n        let entry = self.sandbox_mut().cache_entry_mut(handle.into()).await?;\n        // The path here is:\n        // InvalidCacheHandle -> FastlyStatus::BADF -> (ABI boundary) ->\n        // CacheError::InvalidOperation\n        entry.update(options).await?;\n        Ok(())\n    }\n\n    async fn transaction_cancel(\n        &mut self,\n        handle: Resource<api::Entry>,\n    ) -> Result<(), types::Error> {\n        let entry = self.sandbox_mut().cache_entry_mut(handle.into()).await?;\n        if entry.cancel() {\n            Ok(())\n        } else {\n            Err(Error::CacheError(cache::Error::CannotWrite).into())\n        }\n    }\n\n    async fn get_state(\n        &mut self,\n        handle: Resource<api::Entry>,\n    ) -> Result<api::LookupState, types::Error> {\n        let entry = self.sandbox_mut().cache_entry_mut(handle.into()).await?;\n\n        let mut state = api::LookupState::empty();\n        if let Some(found) = entry.found() {\n            // At the moment, Compute only returns FOUND if the object is fresh.\n            // We adopt the same behavior here, as SDKs (including old SDK versions) do not check\n            // the USABLE bit.\n            if found.meta().is_usable() {\n                state |= api::LookupState::USABLE;\n                state |= api::LookupState::FOUND;\n\n                if !found.meta().is_fresh() {\n                    state |= api::LookupState::STALE;\n                }\n            }\n        }\n        if entry.go_get().is_some() {\n            state |= api::LookupState::MUST_INSERT_OR_UPDATE\n        }\n\n        Ok(state)\n    }\n\n    async fn get_user_metadata(\n        &mut self,\n        handle: Resource<api::Entry>,\n        max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        let entry = self.sandbox_mut().cache_entry(handle.into()).await?;\n\n        let md_bytes = entry\n            .found()\n            .map(|found| found.meta().user_metadata())\n            .ok_or(crate::Error::CacheError(crate::cache::Error::Missing))?;\n        let len = md_bytes.len() as u64;\n        if len > max_len {\n            return Err(types::Error::BufferLen(len));\n        }\n        Ok(Some(md_bytes.into()))\n    }\n\n    async fn get_length(\n        &mut self,\n        handle: Resource<api::Entry>,\n    ) -> Result<Option<u64>, types::Error> {\n        let Some(found) = self.sandbox_mut().cache_entry(handle.into()).await?.found() else {\n            return Ok(None);\n        };\n        Ok(found.length())\n    }\n\n    async fn get_max_age_ns(\n        &mut self,\n        handle: Resource<api::Entry>,\n    ) -> Result<Option<u64>, types::Error> {\n        let entry = self.sandbox_mut().cache_entry_mut(handle.into()).await?;\n        if let Some(found) = entry.found() {\n            Ok(Some(found.meta().max_age().as_nanos().try_into().unwrap()))\n        } else {\n            Ok(None)\n        }\n    }\n\n    async fn get_stale_while_revalidate_ns(\n        &mut self,\n        _handle: Resource<api::Entry>,\n    ) -> Result<Option<u64>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    async fn get_age_ns(\n        &mut self,\n        handle: Resource<api::Entry>,\n    ) -> Result<Option<u64>, types::Error> {\n        let entry = self.sandbox_mut().cache_entry_mut(handle.into()).await?;\n        if let Some(found) = entry.found() {\n            Ok(Some(found.meta().age().as_nanos().try_into().unwrap()))\n        } else {\n            Ok(None)\n        }\n    }\n\n    async fn get_hits(\n        &mut self,\n        _handle: Resource<api::Entry>,\n    ) -> Result<Option<u64>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn drop(&mut self, _entry: Resource<api::Entry>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl api::HostExtraReplaceOptions for ComponentCtx {\n    fn new(&mut self) -> wasmtime::Result<Resource<api::ExtraReplaceOptions>> {\n        Ok(Resource::<api::ExtraReplaceOptions>::new_own(0))\n    }\n\n    fn drop(&mut self, _options: Resource<api::ExtraReplaceOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl api::HostExtraGetBodyOptions for ComponentCtx {\n    fn drop(&mut self, _options: Resource<api::ExtraGetBodyOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl api::HostExtraWriteOptions for ComponentCtx {\n    fn new(&mut self) -> wasmtime::Result<Resource<api::ExtraWriteOptions>> {\n        Ok(Resource::<api::ExtraWriteOptions>::new_own(0))\n    }\n\n    fn drop(&mut self, _options: Resource<api::ExtraWriteOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl api::HostExtraLookupOptions for ComponentCtx {\n    fn new(&mut self) -> wasmtime::Result<Resource<api::ExtraLookupOptions>> {\n        Ok(Resource::<api::ExtraLookupOptions>::new_own(0))\n    }\n\n    fn drop(&mut self, _options: Resource<api::ExtraLookupOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/compute_runtime.rs",
    "content": "use crate::component::bindings::fastly::compute::compute_runtime;\nuse crate::linking::ComponentCtx;\nuse std::sync::atomic::Ordering;\n\nimpl compute_runtime::Host for ComponentCtx {\n    fn get_vcpu_ms(&mut self) -> u64 {\n        self.sandbox().active_cpu_time_us.load(Ordering::SeqCst) / 1000\n    }\n\n    fn get_heap_mib(&mut self) -> compute_runtime::MemoryMib {\n        self.sandbox().get_heap_usage_mib()\n    }\n\n    fn get_sandbox_id(&mut self) -> String {\n        format!(\"{:032x}\", self.sandbox().sandbox_id())\n    }\n\n    fn get_hostname(&mut self) -> String {\n        \"localhost\".to_owned()\n    }\n\n    fn get_pop(&mut self) -> String {\n        \"XXX\".to_owned()\n    }\n\n    fn get_region(&mut self) -> String {\n        \"Somewhere\".to_owned()\n    }\n\n    fn get_cache_generation(&mut self) -> u64 {\n        0\n    }\n\n    fn get_customer_id(&mut self) -> String {\n        \"0000000000000000000000\".to_owned()\n    }\n\n    fn get_is_staging(&mut self) -> bool {\n        false\n    }\n\n    fn get_service_id(&mut self) -> String {\n        \"0000000000000000000000\".to_owned()\n    }\n\n    fn get_service_version(&mut self) -> u64 {\n        0\n    }\n\n    fn get_namespace_id(&mut self) -> String {\n        \"\".to_owned()\n    }\n}\n"
  },
  {
    "path": "src/component/compute/config_store.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{config_store, types},\n    crate::linking::{ComponentCtx, SandboxView},\n    crate::wiggle_abi::types::{ConfigStoreHandle, DictionaryHandle},\n    wasmtime::component::Resource,\n};\n\nimpl config_store::HostStore for ComponentCtx {\n    fn open(&mut self, name: String) -> Result<Resource<config_store::Store>, types::OpenError> {\n        let handle = self.sandbox_mut().dictionary_handle(name.as_str())?;\n        let handle = ConfigStoreHandle::from(u32::from(handle));\n        Ok(handle.into())\n    }\n\n    fn get(\n        &mut self,\n        store: Resource<config_store::Store>,\n        name: String,\n        max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        let handle = DictionaryHandle::from(store.rep());\n        let dict = &self.sandbox().dictionary(handle)?.contents;\n\n        let item = if let Some(item) = dict.get(&name) {\n            item\n        } else {\n            return Ok(None);\n        };\n\n        if item.len() > usize::try_from(max_len).unwrap() {\n            return Err(types::Error::BufferLen(u64::try_from(item.len()).unwrap()));\n        }\n\n        Ok(Some(item.clone()))\n    }\n\n    fn drop(&mut self, _store: Resource<config_store::Store>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl config_store::Host for ComponentCtx {}\n"
  },
  {
    "path": "src/component/compute/device_detection.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{device_detection, types},\n    crate::linking::ComponentCtx,\n};\n\nimpl device_detection::Host for ComponentCtx {\n    fn lookup(&mut self, user_agent: String, max_len: u64) -> Result<Option<String>, types::Error> {\n        if let Some(result) = self.sandbox().device_detection_lookup(&user_agent) {\n            if result.len() > max_len as usize {\n                return Err(types::Error::BufferLen(\n                    u64::try_from(result.len()).unwrap_or(0),\n                ));\n            }\n\n            Ok(Some(result))\n        } else {\n            Ok(None)\n        }\n    }\n}\n"
  },
  {
    "path": "src/component/compute/dictionary.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{dictionary, types},\n    crate::linking::{ComponentCtx, SandboxView},\n    wasmtime::component::Resource,\n};\n\nimpl dictionary::HostDictionary for ComponentCtx {\n    fn open(&mut self, name: String) -> Result<Resource<dictionary::Dictionary>, types::OpenError> {\n        let handle = self.sandbox_mut().dictionary_handle(name.as_str())?;\n        Ok(handle.into())\n    }\n\n    fn lookup(\n        &mut self,\n        h: Resource<dictionary::Dictionary>,\n        key: String,\n        max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        let dict = &self.sandbox().dictionary(h.into())?.contents;\n\n        let item = if let Some(item) = dict.get(&key) {\n            item\n        } else {\n            return Ok(None);\n        };\n\n        if item.len() > usize::try_from(max_len).unwrap() {\n            return Err(types::Error::BufferLen(u64::try_from(item.len()).unwrap()));\n        }\n\n        Ok(Some(item.as_str().to_owned()))\n    }\n\n    fn drop(&mut self, _dictionary: Resource<dictionary::Dictionary>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl dictionary::Host for ComponentCtx {}\n"
  },
  {
    "path": "src/component/compute/erl.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{erl, types},\n    crate::linking::ComponentCtx,\n};\n\nuse wasmtime::component::Resource;\n\nimpl erl::Host for ComponentCtx {}\n\nimpl erl::HostRateCounter for ComponentCtx {\n    fn open(&mut self, name: String) -> Result<Resource<String>, erl::OpenError> {\n        let res = self.wasi_table.push(name).unwrap();\n\n        Ok(res)\n    }\n\n    fn get_name(&mut self, rc: Resource<String>) -> String {\n        self.wasi_table.get(&rc).unwrap().to_owned()\n    }\n\n    fn check_rate(\n        &mut self,\n        rc: Resource<String>,\n        entry: String,\n        delta: u32,\n        window: u32,\n        limit: u32,\n        pb: Resource<String>,\n        ttl: u32,\n    ) -> Result<bool, types::Error> {\n        let rc = self.wasi_table.get(&rc).unwrap();\n        let pb = self.wasi_table.get(&pb).unwrap();\n        crate::component::erl::check_rate(\n            &mut self.sandbox,\n            rc,\n            entry,\n            delta,\n            window,\n            limit,\n            pb,\n            ttl,\n        )\n    }\n\n    fn increment(\n        &mut self,\n        rc: Resource<String>,\n        entry: String,\n        delta: u32,\n    ) -> Result<(), types::Error> {\n        let rc = self.wasi_table.get(&rc).unwrap();\n        crate::component::erl::ratecounter_increment(&mut self.sandbox, rc, entry, delta)\n    }\n\n    fn lookup_rate(\n        &mut self,\n        rc: Resource<String>,\n        entry: String,\n        window: u32,\n    ) -> Result<u32, types::Error> {\n        let rc = self.wasi_table.get(&rc).unwrap();\n        crate::component::erl::ratecounter_lookup_rate(&mut self.sandbox, rc, entry, window)\n    }\n\n    fn lookup_count(\n        &mut self,\n        rc: Resource<String>,\n        entry: String,\n        duration: u32,\n    ) -> Result<u32, types::Error> {\n        let rc = self.wasi_table.get(&rc).unwrap();\n        crate::component::erl::ratecounter_lookup_count(&mut self.sandbox, rc, entry, duration)\n    }\n\n    fn drop(&mut self, ratecounter: Resource<String>) -> wasmtime::Result<()> {\n        self.wasi_table.delete(ratecounter)?;\n        Ok(())\n    }\n}\n\nimpl erl::HostPenaltyBox for ComponentCtx {\n    fn open(&mut self, name: String) -> Result<Resource<String>, erl::OpenError> {\n        let res = self.wasi_table.push(name).unwrap();\n\n        Ok(res)\n    }\n\n    fn get_name(&mut self, pb: Resource<String>) -> String {\n        self.wasi_table.get(&pb).unwrap().to_owned()\n    }\n\n    fn add(&mut self, pb: Resource<String>, entry: String, ttl: u32) -> Result<(), types::Error> {\n        let pb = self.wasi_table.get(&pb).unwrap();\n        crate::component::erl::penaltybox_add(&mut self.sandbox, pb, entry, ttl)\n    }\n\n    fn has(&mut self, pb: Resource<String>, entry: String) -> Result<bool, types::Error> {\n        let pb = self.wasi_table.get(&pb).unwrap();\n        crate::component::erl::penaltybox_has(&mut self.sandbox, pb, entry)\n    }\n\n    fn drop(&mut self, penaltybox: Resource<String>) -> wasmtime::Result<()> {\n        self.wasi_table.delete(penaltybox)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/error.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{http_req, kv_store::KvError, types},\n    crate::{\n        config::ClientCertError,\n        error::{self, HandleError},\n        object_store::{KeyValidationError, KvStoreError, ObjectStoreError},\n        wiggle_abi::{DictionaryError, SecretStoreError},\n    },\n    http::{\n        header::{InvalidHeaderName, InvalidHeaderValue, ToStrError},\n        method::InvalidMethod,\n        status::InvalidStatusCode,\n        uri::InvalidUri,\n    },\n    wasmtime_wasi::ResourceTableError,\n};\n\nimpl types::Error {\n    pub fn with_empty_detail(self) -> http_req::ErrorWithDetail {\n        http_req::ErrorWithDetail {\n            detail: None,\n            error: self,\n        }\n    }\n}\n\nimpl From<std::convert::Infallible> for types::Error {\n    fn from(_: std::convert::Infallible) -> Self {\n        unreachable!()\n    }\n}\n\nimpl From<HandleError> for types::Error {\n    fn from(_: HandleError) -> Self {\n        panic!(\"`HandleError` should not be used in components\")\n    }\n}\n\nimpl From<ClientCertError> for types::Error {\n    fn from(_: ClientCertError) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<InvalidStatusCode> for types::Error {\n    fn from(_: InvalidStatusCode) -> Self {\n        types::Error::InvalidArgument\n    }\n}\n\nimpl From<InvalidHeaderName> for types::Error {\n    fn from(_: InvalidHeaderName) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<InvalidHeaderValue> for types::Error {\n    fn from(_: InvalidHeaderValue) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<std::str::Utf8Error> for types::Error {\n    fn from(_: std::str::Utf8Error) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<std::io::Error> for types::Error {\n    fn from(_: std::io::Error) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<ToStrError> for types::Error {\n    fn from(_: ToStrError) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<InvalidMethod> for types::Error {\n    fn from(_: InvalidMethod) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<InvalidUri> for types::Error {\n    fn from(_: InvalidUri) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<std::string::FromUtf8Error> for types::Error {\n    fn from(_: std::string::FromUtf8Error) -> Self {\n        types::Error::InvalidArgument\n    }\n}\n\nimpl From<wiggle::GuestError> for types::Error {\n    fn from(err: wiggle::GuestError) -> Self {\n        use wiggle::GuestError::*;\n        match err {\n            // We may want to expand the FastlyStatus enum to distinguish between more of these\n            // values.\n            PtrNotAligned { .. }\n            | InvalidFlagValue { .. }\n            | InvalidEnumValue { .. }\n            | PtrOutOfBounds { .. }\n            | PtrOverflow\n            | InvalidUtf8 { .. }\n            | TryFromIntError { .. } => types::Error::InvalidArgument,\n            // These errors indicate either a pathological user input or an internal programming\n            // error\n            SliceLengthsDiffer => types::Error::GenericError,\n            // Recursive case: InFunc wraps a GuestError with some context which\n            // doesn't determine what sort of FastlyStatus we return.\n            InFunc { err, .. } => Self::from(*err),\n        }\n    }\n}\n\nimpl From<ObjectStoreError> for types::Error {\n    fn from(err: ObjectStoreError) -> Self {\n        use ObjectStoreError::*;\n        match err {\n            MissingObject => panic!(\"`MissingObject` should not be used in components\"),\n            PoisonedLock => panic!(\"{}\", err),\n            UnknownObjectStore(_) => types::Error::InvalidArgument,\n        }\n    }\n}\n\nimpl From<KvStoreError> for types::Error {\n    fn from(err: KvStoreError) -> Self {\n        use KvStoreError::*;\n        match err {\n            Uninitialized => panic!(\"{}\", err),\n            BadRequest => types::Error::InvalidArgument,\n            PreconditionFailed => types::Error::InvalidArgument,\n            PayloadTooLarge => types::Error::InvalidArgument,\n            InternalError => types::Error::InvalidArgument,\n            TooManyRequests => types::Error::InvalidArgument,\n        }\n    }\n}\n\nimpl From<ResourceTableError> for types::Error {\n    fn from(err: ResourceTableError) -> Self {\n        panic!(\"{}\", err)\n    }\n}\n\nimpl From<KvStoreError> for KvError {\n    fn from(err: KvStoreError) -> Self {\n        use KvStoreError::*;\n        match err {\n            Uninitialized => panic!(\"{}\", err),\n            BadRequest => KvError::BadRequest,\n            PreconditionFailed => KvError::PreconditionFailed,\n            PayloadTooLarge => KvError::PayloadTooLarge,\n            InternalError => KvError::InternalError,\n            TooManyRequests => KvError::TooManyRequests,\n        }\n    }\n}\n\nimpl From<KeyValidationError> for types::Error {\n    fn from(_: KeyValidationError) -> Self {\n        types::Error::GenericError\n    }\n}\n\nimpl From<SecretStoreError> for types::Error {\n    fn from(err: SecretStoreError) -> Self {\n        use SecretStoreError::*;\n        match err {\n            UnknownSecretStore(_) => {\n                panic!(\"`UnknownSecretStore` should not be used in components\")\n            }\n            UnknownSecret(_) => panic!(\"`UnknownSecret` should not be used in components\"),\n            InvalidSecretStoreHandle(_) => types::Error::InvalidArgument,\n            InvalidSecretHandle(_) => types::Error::InvalidArgument,\n        }\n    }\n}\n\nimpl From<DictionaryError> for types::Error {\n    fn from(err: DictionaryError) -> Self {\n        use DictionaryError::*;\n        match err {\n            UnknownDictionaryItem(_) => {\n                panic!(\"`UnknownDictionaryItem` should not be used in components\")\n            }\n            UnknownDictionary(_) => types::Error::InvalidArgument,\n        }\n    }\n}\n\nimpl From<error::Error> for types::Error {\n    fn from(err: error::Error) -> Self {\n        use error::Error;\n        match err {\n            Error::BufferLengthError { .. } => types::Error::BufferLen(0),\n            Error::InvalidArgument => types::Error::InvalidArgument,\n            Error::Unsupported { .. } => types::Error::Unsupported,\n            Error::HandleError { .. } => panic!(\"`HandleError` should not be used in components\"),\n            Error::InvalidStatusCode { .. } => types::Error::InvalidArgument,\n            // Map specific kinds of `hyper::Error` into their respective error codes.\n            Error::HyperError(e) if e.is_parse() => types::Error::HttpInvalid,\n            Error::HyperError(e) if e.is_user() => types::Error::HttpUser,\n            Error::HyperError(e) if e.is_incomplete_message() => types::Error::HttpIncomplete,\n            Error::HyperError(_) => types::Error::GenericError,\n            // BackendConnectionError wraps hyper::Error with context\n            Error::BackendConnectionError { source, .. } if source.is_parse() => {\n                types::Error::HttpInvalid\n            }\n            Error::BackendConnectionError { source, .. } if source.is_user() => {\n                types::Error::HttpUser\n            }\n            Error::BackendConnectionError { source, .. } if source.is_incomplete_message() => {\n                types::Error::HttpIncomplete\n            }\n            Error::BackendConnectionError { .. } => types::Error::GenericError,\n            // Destructuring a GuestError is recursive, so we use a helper function:\n            Error::GuestError(e) => e.into(),\n            // We delegate to some error types' own implementation of `to_fastly_status`.\n            Error::DictionaryError(e) => e.into(),\n            Error::ObjectStoreError(e) => e.into(),\n            Error::KvStoreError(e) => e.into(),\n            Error::SecretStoreError(e) => e.into(),\n            Error::CacheError(e) => e.into(),\n            Error::ValueAbsent => panic!(\"`ValueAbsent` should not be used in components\"),\n            Error::LimitExceeded { .. } => types::Error::LimitExceeded,\n            // All other hostcall errors map to a generic `ERROR` value.\n            Error::AbiVersionMismatch\n            | Error::Again\n            | Error::BackendUrl(_)\n            | Error::BadCerts(_)\n            | Error::DownstreamRequestError(_)\n            | Error::DownstreamRespSending\n            | Error::FastlyConfig(_)\n            | Error::FatalError(_)\n            | Error::FileFormat\n            | Error::Infallible(_)\n            | Error::InvalidClientCert(_)\n            | Error::InvalidHeaderName(_)\n            | Error::InvalidHeaderValue(_)\n            | Error::InvalidMethod(_)\n            | Error::InvalidUri(_)\n            | Error::IoError(_)\n            | Error::MissingDownstreamMetadata\n            | Error::NotAvailable(_)\n            | Error::Other(_)\n            | Error::ProfilingStrategy\n            | Error::StreamingChunkSend\n            | Error::UnknownBackend(_)\n            | Error::Utf8Expected(_)\n            | Error::BackendNameRegistryError(_)\n            | Error::HttpError(_)\n            | Error::UnknownObjectStore(_)\n            | Error::ObjectStoreKeyValidationError(_)\n            | Error::UnfinishedStreamingBody\n            | Error::ToStr(_)\n            | Error::InvalidAlpnResponse { .. }\n            | Error::DeviceDetectionError(_)\n            | Error::SharedMemory\n            | Error::TlsNoCAAvailable\n            | Error::TlsNoValidCACerts\n            | Error::TlsInvalidHost\n            | Error::TlsCertificateValidationFailed => types::Error::GenericError,\n        }\n    }\n}\n\nimpl From<error::Error> for types::OpenError {\n    fn from(err: error::Error) -> Self {\n        use error::Error;\n        match err {\n            Error::UnknownObjectStore(_)\n            | Error::UnknownBackend(_)\n            | Error::SecretStoreError(SecretStoreError::UnknownSecretStore(_))\n            | Error::DictionaryError(DictionaryError::UnknownDictionary(_)) => {\n                types::OpenError::NotFound\n            }\n\n            Error::InvalidArgument => types::OpenError::InvalidSyntax,\n            Error::LimitExceeded { .. } => types::OpenError::LimitExceeded,\n            Error::Unsupported { .. } => types::OpenError::Unsupported,\n            _ => types::OpenError::GenericError,\n        }\n    }\n}\n\nimpl From<error::Error> for KvError {\n    fn from(err: error::Error) -> Self {\n        use error::Error;\n        match err {\n            Error::InvalidStatusCode { .. } | Error::InvalidArgument => KvError::BadRequest,\n            Error::LimitExceeded { .. } => KvError::PayloadTooLarge,\n            _ => KvError::InternalError,\n        }\n    }\n}\n"
  },
  {
    "path": "src/component/compute/geo.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{geo, types},\n    crate::{error, linking::ComponentCtx},\n    std::net::IpAddr,\n};\n\nimpl geo::Host for ComponentCtx {\n    fn lookup(&mut self, addr: types::IpAddress, max_len: u64) -> Result<String, types::Error> {\n        let ip_addr: IpAddr = addr.into();\n\n        let json = self\n            .sandbox()\n            .geolocation_lookup(&ip_addr)\n            .ok_or(geo::Error::GenericError)?;\n\n        if json.len() > usize::try_from(max_len).unwrap() {\n            return Err(error::Error::BufferLengthError {\n                buf: \"geo_out\",\n                len: \"geo_max_len\",\n            }\n            .into());\n        }\n\n        Ok(json)\n    }\n}\n"
  },
  {
    "path": "src/component/compute/headers.rs",
    "content": "use crate::component::bindings::fastly::compute::types;\nuse crate::error::Error;\nuse http::{HeaderMap, HeaderName};\nuse std::cmp::min;\n\ntype MultiValueCursor = u32;\n\n/// How long is a `HeaderName` allowed to be?\nconst MAX_HEADER_NAME_LEN: usize = 1 << (16 - 1);\n\n/// Write multiple values out to a single buffer, until the iterator is exhausted, or `max_len`\n/// bytes have been written. In the case that there are still values remaining, the second value of\n/// the returned tuple will be `Some`.\n///\n/// If it's not possible to fit a single value inside a buffer of length `max_len`, an error will\n/// be returned with the size necessary for the first element of the collection.\nfn write_values<I, T>(\n    iter: I,\n    terminator: u8,\n    max_len: u64,\n    cursor_start: MultiValueCursor,\n) -> Result<(Vec<u8>, Option<MultiValueCursor>), types::Error>\nwhere\n    I: Iterator<Item = T>,\n    T: AsRef<[u8]>,\n{\n    // Reserve `max_len` bytes, unless it's unreasonably large.\n    let mut buf = Vec::with_capacity(min(max_len, 0x1_0000) as usize);\n\n    let mut cursor = cursor_start;\n    let mut finished = true;\n    let skip_amt = usize::try_from(cursor).expect(\"u32 can fit in usize\");\n    for item in iter.skip(skip_amt) {\n        let bytes = item.as_ref();\n\n        let needed = buf.len() as u64 + bytes.len() as u64 + 1;\n        if needed > max_len {\n            // If we haven't written a single entry yet, return an error indicating how much space\n            // we would need to write a single entry.\n            if cursor == cursor_start {\n                return Err(types::Error::BufferLen(needed));\n            }\n\n            finished = false;\n            break;\n        }\n\n        buf.extend(bytes);\n        buf.push(terminator);\n\n        cursor += 1\n    }\n\n    let cursor = if finished { None } else { Some(cursor) };\n\n    Ok((buf, cursor))\n}\n\n/// Similar to `write_values`, but works on strings instead of byte vectors.\nfn write_names<I, T>(\n    iter: I,\n    terminator: char,\n    max_len: u64,\n    cursor_start: MultiValueCursor,\n) -> Result<(String, Option<u32>), types::Error>\nwhere\n    I: Iterator<Item = T>,\n    T: AsRef<str>,\n{\n    // Reserve `max_len` bytes, unless it's unreasonably large.\n    let mut buf = String::with_capacity(min(max_len, 0x1_0000) as usize);\n\n    let mut cursor = cursor_start;\n    let mut finished = true;\n    let skip_amt = usize::try_from(cursor).expect(\"u32 can fit in usize\");\n    for item in iter.skip(skip_amt) {\n        let key = item.as_ref();\n\n        let needed = buf.len() as u64 + key.len() as u64 + 1;\n        if needed > max_len {\n            // If we haven't written a single entry yet, return an error indicating how much space\n            // we would need to write a single entry.\n            if cursor == cursor_start {\n                // Signal the number of bytes necessary to fit this method, to\n                // indicate an error condition.\n                return Err(types::Error::BufferLen(needed));\n            }\n\n            finished = false;\n            break;\n        }\n\n        buf += key;\n        buf.push(terminator);\n\n        cursor += 1\n    }\n\n    let cursor = if finished { None } else { Some(cursor) };\n\n    // At this point we know that the buffer being empty will also mean that there are no\n    // remaining entries to read.\n    debug_assert!(!buf.is_empty() || cursor.is_none());\n    Ok((buf, cursor))\n}\n\n/// Fetch all names from a `HeaderMap`.\npub fn get_names<Keys, T>(\n    keys: Keys,\n    max_len: u64,\n    cursor: u32,\n) -> Result<(String, Option<u32>), types::Error>\nwhere\n    Keys: Iterator<Item = T>,\n    T: AsRef<str>,\n{\n    write_names(keys, '\\0', max_len, cursor)\n}\n\n/// Fetch all values for a header from a `HeaderMap`.\npub fn get_values(\n    headers: &HeaderMap,\n    name: &str,\n    max_len: u64,\n    cursor: u32,\n) -> Result<(Vec<u8>, Option<u32>), types::Error> {\n    if name.len() > MAX_HEADER_NAME_LEN {\n        return Err(Error::InvalidArgument.into());\n    }\n\n    let values = headers.get_all(HeaderName::try_from(name)?);\n    write_values(values.into_iter(), b'\\0', max_len, cursor)\n}\n"
  },
  {
    "path": "src/component/compute/http_body.rs",
    "content": "use {\n    crate::component::{\n        bindings::fastly::compute::{http_body, types},\n        compute::headers,\n    },\n    crate::{\n        body::Body,\n        error::Error,\n        linking::{ComponentCtx, SandboxView},\n    },\n    http::header::{HeaderName, HeaderValue},\n    wasmtime::component::Resource,\n};\n\n/// This constant reflects a similar constant within Hyper, which will panic\n/// if given header names longer than this value.\npub const MAX_HEADER_NAME_LEN: usize = (1 << 16) - 1;\n\nimpl http_body::Host for ComponentCtx {\n    fn new(&mut self) -> Result<Resource<http_body::Body>, types::Error> {\n        Ok(self.sandbox_mut().insert_body(Body::empty()).into())\n    }\n\n    async fn write(\n        &mut self,\n        h: Resource<http_body::Body>,\n        buf: Vec<u8>,\n    ) -> Result<u32, types::Error> {\n        let h = h.into();\n\n        // Validate the body handle and the buffer.\n        let buf = buf.as_slice();\n\n        if self.sandbox().is_streaming_body(h) {\n            let body = self.sandbox_mut().streaming_body_mut(h)?;\n            body.send_chunk(buf).await?;\n        } else {\n            let body = self.sandbox_mut().body_mut(h)?;\n            body.push_back(buf);\n        }\n\n        // Finally, return the number of bytes written, which is _always_ the full buffer\n        Ok(buf\n            .len()\n            .try_into()\n            .expect(\"the buffer length must fit into a u32\"))\n    }\n\n    async fn write_front(\n        &mut self,\n        h: Resource<http_body::Body>,\n        buf: Vec<u8>,\n    ) -> Result<(), types::Error> {\n        let h = h.into();\n\n        // Validate the body handle and the buffer.\n        let buf = buf.as_slice();\n\n        // Only normal bodies can be front-written\n        if self.sandbox().is_streaming_body(h) {\n            return Err(Error::Unsupported {\n                msg: \"can only write to the end of a streaming body\",\n            }\n            .into());\n        }\n\n        let body = self.sandbox_mut().body_mut(h)?;\n        body.push_front(buf);\n\n        Ok(())\n    }\n\n    async fn append(\n        &mut self,\n        dest: Resource<http_body::Body>,\n        src: Resource<http_body::Body>,\n    ) -> Result<(), types::Error> {\n        // Take the `src` body out of the sandbox, and get a mutable reference\n        // to the `dest` body we will append to.\n        let src = self.sandbox_mut().take_body(src.into())?;\n\n        let dest = dest.into();\n        if self.sandbox().is_streaming_body(dest) {\n            let dest = self.sandbox_mut().streaming_body_mut(dest)?;\n            for chunk in src {\n                dest.send_chunk(chunk).await?;\n            }\n        } else {\n            let dest = self.sandbox_mut().body_mut(dest)?;\n            dest.append(src);\n        }\n        Ok(())\n    }\n\n    async fn read(\n        &mut self,\n        h: Resource<http_body::Body>,\n        chunk_size: u32,\n    ) -> Result<Vec<u8>, types::Error> {\n        let h = h.into();\n\n        // only normal bodies (not streaming bodies) can be read from\n        let body = self.sandbox_mut().body_mut(h)?;\n\n        let mut buffer = vec![0; chunk_size as usize];\n        let len = body.read(&mut buffer).await?;\n        buffer.truncate(len);\n        Ok(buffer)\n    }\n\n    fn close(&mut self, h: Resource<http_body::Body>) -> Result<(), types::Error> {\n        // Drop the body and pass up an error if the handle does not exist\n        let h = h.into();\n        if self.sandbox().is_streaming_body(h) {\n            // Make sure a streaming body gets a `finish` message\n            self.sandbox_mut().take_streaming_body(h)?.finish()?;\n            Ok(())\n        } else {\n            Ok(self.sandbox_mut().drop_body(h)?)\n        }\n    }\n\n    fn get_known_length(&mut self, h: Resource<http_body::Body>) -> Option<u64> {\n        let h = h.into();\n        if self.sandbox().is_streaming_body(h) {\n            None\n        } else {\n            self.sandbox_mut().body_mut(h).unwrap().len()\n        }\n    }\n\n    fn append_trailer(\n        &mut self,\n        h: Resource<http_body::Body>,\n        name: String,\n        value: Vec<u8>,\n    ) -> Result<(), types::Error> {\n        // Appending trailers is always allowed for bodies and streaming bodies.\n        let h = h.into();\n        if self.sandbox().is_streaming_body(h) {\n            let body = self.sandbox_mut().streaming_body_mut(h)?;\n            let name = HeaderName::from_bytes(name.as_bytes())?;\n            let value = HeaderValue::from_bytes(value.as_slice())?;\n            body.append_trailer(name, value);\n            Ok(())\n        } else {\n            let trailers = &mut self.sandbox_mut().body_mut(h)?.trailers;\n            if name.len() > MAX_HEADER_NAME_LEN {\n                return Err(Error::InvalidArgument.into());\n            }\n\n            let name = HeaderName::from_bytes(name.as_bytes())?;\n            let value = HeaderValue::from_bytes(value.as_slice())?;\n            trailers.append(name, value);\n            Ok(())\n        }\n    }\n\n    fn get_trailer_names(\n        &mut self,\n        h: Resource<http_body::Body>,\n        max_len: u64,\n        cursor: u32,\n    ) -> Result<(String, Option<u32>), http_body::TrailerError> {\n        let h = h.into();\n\n        // Read operations are not allowed on streaming bodies.\n        if self.sandbox().is_streaming_body(h) {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let body = self.sandbox_mut().body_mut(h)?;\n        if !body.trailers_ready {\n            return Err(http_body::TrailerError::NotAvailableYet);\n        }\n\n        let trailers = &body.trailers;\n        let (buf, next) = headers::get_names(trailers.keys(), max_len, cursor)?;\n\n        Ok((buf, next))\n    }\n\n    fn get_trailer_value(\n        &mut self,\n        h: Resource<http_body::Body>,\n        name: String,\n        max_len: u64,\n    ) -> Result<Option<Vec<u8>>, http_body::TrailerError> {\n        let h = h.into();\n\n        // Read operations are not allowed on streaming bodies.\n        if self.sandbox().is_streaming_body(h) {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let body = self.sandbox_mut().body_mut(h)?;\n        if !body.trailers_ready {\n            return Err(http_body::TrailerError::NotAvailableYet);\n        }\n\n        let trailers = &mut body.trailers;\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let value = {\n            let name = HeaderName::from_bytes(name.as_bytes())?;\n            if let Some(value) = trailers.get(&name) {\n                value\n            } else {\n                return Ok(None);\n            }\n        };\n\n        if value.len() > max_len as usize {\n            return Err(Error::BufferLengthError {\n                buf: \"value\",\n                len: \"value_max_len\",\n            }\n            .into());\n        }\n\n        Ok(Some(value.as_bytes().to_owned()))\n    }\n\n    fn get_trailer_values(\n        &mut self,\n        h: Resource<http_body::Body>,\n        name: String,\n        max_len: u64,\n        cursor: u32,\n    ) -> Result<(Vec<u8>, Option<u32>), http_body::TrailerError> {\n        let h = h.into();\n\n        // Read operations are not allowed on streaming bodies.\n        if self.sandbox().is_streaming_body(h) {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let body = self.sandbox_mut().body_mut(h).unwrap();\n        if !body.trailers_ready {\n            return Err(http_body::TrailerError::NotAvailableYet);\n        }\n\n        let trailers = &mut body.trailers;\n        let (buf, next) = headers::get_values(trailers, &name, max_len, cursor)?;\n\n        Ok((buf, next))\n    }\n}\n\nimpl<T: Into<types::Error>> From<T> for http_body::TrailerError {\n    fn from(err: T) -> Self {\n        Self::Error(err.into())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/http_cache.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{http_body, http_cache, types},\n    crate::{error::Error, linking::ComponentCtx},\n    wasmtime::component::Resource,\n};\n\nimpl http_cache::Host for ComponentCtx {\n    fn is_request_cacheable(\n        &mut self,\n        _req_handle: Resource<http_cache::Request>,\n    ) -> Result<bool, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_suggested_cache_key(\n        &mut self,\n        _req_handle: Resource<http_cache::Request>,\n        _max_len: u64,\n    ) -> Result<Vec<u8>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn close_entry(&mut self, _handle: Resource<http_cache::Entry>) -> Result<(), types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n}\n\nimpl http_cache::HostSuggestedWriteOptions for ComponentCtx {\n    fn get_max_age_ns(\n        &mut self,\n        _rep: Resource<http_cache::SuggestedWriteOptions>,\n    ) -> http_cache::DurationNs {\n        panic!(\"HTTP Cache API primitives not yet supported\")\n    }\n\n    fn get_vary_rule(\n        &mut self,\n        _rep: Resource<http_cache::SuggestedWriteOptions>,\n        _max_len: u64,\n    ) -> Result<String, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_initial_age_ns(\n        &mut self,\n        _rep: Resource<http_cache::SuggestedWriteOptions>,\n    ) -> http_cache::DurationNs {\n        panic!(\"HTTP Cache API primitives not yet supported\")\n    }\n\n    fn get_stale_while_revalidate_ns(\n        &mut self,\n        _rep: Resource<http_cache::SuggestedWriteOptions>,\n    ) -> http_cache::DurationNs {\n        panic!(\"HTTP Cache API primitives not yet supported\")\n    }\n\n    fn get_stale_if_error_ns(\n        &mut self,\n        _rep: Resource<http_cache::SuggestedWriteOptions>,\n    ) -> http_cache::DurationNs {\n        panic!(\"HTTP Cache API primitives not yet supported\")\n    }\n\n    fn get_surrogate_keys(\n        &mut self,\n        _rep: Resource<http_cache::SuggestedWriteOptions>,\n        _max_len: u64,\n    ) -> Result<String, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_length(\n        &mut self,\n        _rep: Resource<http_cache::SuggestedWriteOptions>,\n    ) -> Option<http_cache::ObjectLength> {\n        panic!(\"HTTP Cache API primitives not yet supported\")\n    }\n\n    fn get_sensitive_data(&mut self, _rep: Resource<http_cache::SuggestedWriteOptions>) -> bool {\n        panic!(\"HTTP Cache API primitives not yet supported\")\n    }\n\n    fn drop(&mut self, _rep: Resource<http_cache::SuggestedWriteOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl http_cache::HostExtraWriteOptions for ComponentCtx {\n    fn drop(&mut self, _h: Resource<http_cache::ExtraWriteOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl http_cache::HostExtraLookupOptions for ComponentCtx {\n    fn drop(&mut self, _h: Resource<http_cache::ExtraLookupOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl http_cache::HostEntry for ComponentCtx {\n    fn transaction_lookup(\n        &mut self,\n        _req_handle: Resource<http_cache::Request>,\n        _options: http_cache::LookupOptions,\n    ) -> Result<Resource<http_cache::Entry>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn transaction_insert(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _resp_handle: Resource<http_cache::Response>,\n        _options: http_cache::WriteOptions,\n    ) -> Result<Resource<http_cache::Body>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn transaction_insert_and_stream_back(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _resp_handle: Resource<http_cache::Response>,\n        _options: http_cache::WriteOptions,\n    ) -> Result<(Resource<http_cache::Body>, Resource<http_cache::Entry>), types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn transaction_update(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _resp_handle: Resource<http_cache::Response>,\n        _options: http_cache::WriteOptions,\n    ) -> Result<(), types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn transaction_update_and_return_fresh(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _resp_handle: Resource<http_cache::Response>,\n        _options: http_cache::WriteOptions,\n    ) -> Result<Resource<http_cache::Entry>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn transaction_record_not_cacheable(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _options: http_cache::WriteOptions,\n    ) -> Result<(), types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_suggested_backend_request(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<Resource<http_cache::Request>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_suggested_write_options(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _response: Resource<http_cache::Response>,\n    ) -> Result<Resource<http_cache::SuggestedWriteOptions>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn prepare_response_for_storage(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _response: Resource<http_cache::Response>,\n    ) -> Result<(http_cache::StorageAction, Resource<http_cache::Response>), types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_found_response(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _transform_for_client: u32,\n    ) -> Result<Option<(Resource<http_cache::Response>, Resource<http_body::Body>)>, types::Error>\n    {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_state(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<http_cache::LookupState, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_length(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<Option<http_cache::ObjectLength>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_max_age_ns(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<Option<http_cache::DurationNs>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_stale_while_revalidate_ns(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<Option<http_cache::DurationNs>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_stale_if_error_ns(\n        &mut self,\n        _rep: Resource<http_cache::Entry>,\n    ) -> Result<Option<http_cache::DurationNs>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_age_ns(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<Option<http_cache::DurationNs>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_hits(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<Option<http_cache::CacheHitCount>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_sensitive_data(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<Option<bool>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_surrogate_keys(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn get_vary_rule(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n        _max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn transaction_abandon(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<(), types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn transaction_choose_stale(\n        &mut self,\n        _handle: Resource<http_cache::Entry>,\n    ) -> Result<(), types::Error> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n\n    fn drop(&mut self, _handle: Resource<http_cache::Entry>) -> wasmtime::Result<()> {\n        Err(Error::Unsupported {\n            msg: \"HTTP Cache API primitives not yet supported\",\n        }\n        .into())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/http_downstream.rs",
    "content": "use std::time::Duration;\n\nuse crate::component::bindings::fastly::compute::{http_body, http_downstream, http_req, types};\nuse crate::component::compute::headers::get_names;\nuse crate::error::Error;\nuse crate::linking::{ComponentCtx, SandboxView};\nuse crate::sandbox::Sandbox;\nuse crate::wiggle_abi::types::RequestPromiseHandle;\nuse wasmtime::component::Resource;\nuse wasmtime_wasi_io::IoView;\n\nimpl http_downstream::Host for ComponentCtx {\n    async fn next_request(\n        &mut self,\n        options: http_downstream::NextRequestOptions,\n    ) -> Result<Resource<http_downstream::PendingRequest>, types::Error> {\n        let timeout = options.timeout_ms.map(Duration::from_millis);\n        let handle = self\n            .sandbox_mut()\n            .register_pending_downstream_req(timeout)\n            .await?;\n\n        let handle = RequestPromiseHandle::from(handle);\n\n        Ok(handle.into())\n    }\n\n    async fn await_request(\n        &mut self,\n        handle: Resource<http_downstream::PendingRequest>,\n    ) -> Result<Option<(Resource<http_req::Request>, Resource<http_body::Body>)>, types::Error>\n    {\n        let handle = RequestPromiseHandle::from(handle).into();\n        let Some((req, body)) = self.sandbox_mut().await_downstream_req(handle).await? else {\n            return Ok(None);\n        };\n\n        Ok(Some((req.into(), body.into())))\n    }\n\n    fn downstream_client_ip_addr(\n        &mut self,\n        h: Resource<http_req::Request>,\n    ) -> Option<types::IpAddress> {\n        self.sandbox()\n            .downstream_client_ip(h.into())\n            .ok()?\n            .map(|ip| ip.into())\n    }\n\n    fn downstream_server_ip_addr(\n        &mut self,\n        h: Resource<http_req::Request>,\n    ) -> Option<types::IpAddress> {\n        self.sandbox()\n            .downstream_server_ip(h.into())\n            .ok()?\n            .map(|ip| ip.into())\n    }\n\n    fn downstream_client_ddos_detected(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<bool, types::Error> {\n        Ok(false)\n    }\n\n    fn downstream_tls_cipher_openssl_name(\n        &mut self,\n        h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        Ok(self.sandbox().absent_metadata_value(h)?)\n    }\n\n    fn downstream_tls_protocol(\n        &mut self,\n        h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        Ok(self.sandbox().absent_metadata_value(h)?)\n    }\n\n    fn downstream_tls_client_servername(\n        &mut self,\n        h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        self.sandbox().absent_metadata_value(h).map_err(Into::into)\n    }\n\n    fn downstream_tls_client_hello(\n        &mut self,\n        h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        Ok(self.sandbox().absent_metadata_value(h)?)\n    }\n\n    fn downstream_tls_raw_client_certificate(\n        &mut self,\n        h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        Ok(self.sandbox().absent_metadata_value(h)?)\n    }\n\n    fn downstream_tls_client_cert_verify_result(\n        &mut self,\n        h: Resource<http_req::Request>,\n    ) -> Result<Option<http_req::ClientCertVerifyResult>, types::Error> {\n        Ok(self.sandbox().absent_metadata_value(h)?)\n    }\n\n    fn downstream_tls_ja3_md5(\n        &mut self,\n        h: Resource<http_req::Request>,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        Ok(self.sandbox().absent_metadata_value(h)?)\n    }\n\n    fn downstream_client_h2_fingerprint(\n        &mut self,\n        h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<String, types::Error> {\n        Ok(self\n            .sandbox()\n            .absent_metadata_value(h)?\n            .ok_or(Error::MissingDownstreamMetadata)?)\n    }\n\n    fn downstream_client_request_id(\n        &mut self,\n        h: Resource<http_req::Request>,\n        max_len: u64,\n    ) -> Result<String, types::Error> {\n        let reqid = self\n            .sandbox()\n            .downstream_request_id(h.into())?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n        let result = format!(\"{:032x}\", reqid);\n\n        if result.len() > usize::try_from(max_len).unwrap() {\n            return Err(types::Error::BufferLen(\n                u64::try_from(result.len()).unwrap(),\n            ));\n        }\n\n        Ok(result)\n    }\n\n    fn downstream_client_oh_fingerprint(\n        &mut self,\n        h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<String, types::Error> {\n        Ok(self\n            .sandbox()\n            .absent_metadata_value(h)?\n            .ok_or(Error::MissingDownstreamMetadata)?)\n    }\n\n    fn downstream_tls_ja4(\n        &mut self,\n        h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        Ok(self.sandbox().absent_metadata_value(h)?)\n    }\n\n    fn downstream_compliance_region(\n        &mut self,\n        h: Resource<http_req::Request>,\n        region_max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        let region = Sandbox::downstream_compliance_region(self.sandbox(), h.into())?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n        let region_len = region.len();\n\n        match u64::try_from(region_len) {\n            Ok(region_len) if region_len <= region_max_len => Ok(Some(region.to_owned())),\n            too_large => Err(types::Error::BufferLen(too_large.unwrap_or(0))),\n        }\n    }\n\n    fn downstream_original_header_names(\n        &mut self,\n        h: Resource<http_req::Request>,\n        max_len: u64,\n        cursor: u32,\n    ) -> Result<(String, Option<u32>), types::Error> {\n        let headers = self\n            .sandbox()\n            .downstream_original_headers(h.into())?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n        let res = get_names(headers.keys(), max_len, cursor)?;\n\n        Ok(res)\n    }\n\n    fn downstream_original_header_count(\n        &mut self,\n        h: Resource<http_req::Request>,\n    ) -> Result<u32, types::Error> {\n        Ok(self\n            .sandbox()\n            .downstream_original_headers(h.into())?\n            .ok_or(Error::MissingDownstreamMetadata)?\n            .len()\n            .try_into()\n            .expect(\"More than u32::MAX headers\"))\n    }\n\n    fn fastly_key_is_valid(\n        &mut self,\n        h: Resource<http_req::Request>,\n    ) -> Result<bool, types::Error> {\n        self.sandbox()\n            .check_fastly_key(h.into())\n            .map_err(Into::into)\n    }\n\n    fn downstream_bot_analyzed(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<bool, types::Error> {\n        Ok(false)\n    }\n\n    fn downstream_bot_detected(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<bool, types::Error> {\n        Ok(false)\n    }\n\n    fn downstream_bot_name(\n        &mut self,\n        _h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_bot_category(\n        &mut self,\n        _h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_bot_category_kind(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<http_downstream::BotCategory>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_bot_verified(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_anonymous(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_anonymous_vpn(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_hosting_provider(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_proxy_over_vpn(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_public_proxy(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_relay_proxy(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_residential_proxy(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_smart_dns_proxy(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_tor_exit_node(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_is_vpn_datacenter(\n        &mut self,\n        _h: Resource<http_req::Request>,\n    ) -> Result<Option<bool>, types::Error> {\n        Ok(None)\n    }\n\n    fn downstream_resvpnproxy_vpn_service_name(\n        &mut self,\n        _h: Resource<http_req::Request>,\n        _max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        Ok(None)\n    }\n}\n\nimpl http_downstream::HostExtraNextRequestOptions for ComponentCtx {\n    fn drop(\n        &mut self,\n        _options: Resource<http_downstream::ExtraNextRequestOptions>,\n    ) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\npub struct ExtraBotCategory {\n    raw: u32,\n}\n\nimpl http_downstream::HostExtraBotCategory for ComponentCtx {\n    fn as_raw(&mut self, h: wasmtime::component::Resource<ExtraBotCategory>) -> u32 {\n        self.table().get(&h).map(|r| r.raw).unwrap_or_default()\n    }\n\n    fn drop(&mut self, h: wasmtime::component::Resource<ExtraBotCategory>) -> wasmtime::Result<()> {\n        self.table().delete(h)?;\n        Ok(())\n    }\n}\n\npub(in super::super) trait MetadataView {\n    /// Stub for metadata that Viceroy does not support.\n    ///\n    /// Validates the handle normally, but always returns `Ok(None)` rather than a meaningful value.\n    fn absent_metadata_value<T>(\n        &self,\n        handle: Resource<http_req::Request>,\n    ) -> Result<Option<T>, Error>;\n}\nimpl MetadataView for Sandbox {\n    fn absent_metadata_value<T>(\n        &self,\n        handle: Resource<http_req::Request>,\n    ) -> Result<Option<T>, Error> {\n        let _ = self\n            .downstream_metadata(handle.into())?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n        Ok(None)\n    }\n}\n"
  },
  {
    "path": "src/component/compute/http_req.rs",
    "content": "use {\n    crate::{\n        component::{\n            bindings::fastly::compute::{http_body, http_req, http_resp, http_types, types},\n            compute::headers::{get_names, get_values},\n        },\n        error::Error,\n        linking::{ComponentCtx, SandboxView},\n        sandbox::ViceroyRequestMetadata,\n    },\n    http::{\n        Method, Uri,\n        header::{HeaderName, HeaderValue},\n        request::Request,\n    },\n    wasmtime::component::Resource,\n};\n\n// NOTE [error-detail]:\n//\n// The v2 apis return additional error through a send-error-detail outparam, but this is a little\n// bit awkward in the context of wit, which lacks the notion of an outparam. As the presence of\n// this value is optional, and only serves to augment additional error context, we instead\n// represent this as a different error result in compute.wit:\n//\n// ```\n// result<T, tuple<option<send-error-detail>, error>>\n// ```\n//\n// The effect of this is that we can no longer rely on the `trappable_error_types` option to\n// `component::bindgen!` to give us a type that represents both an error and a trap. Instead, we\n// get the following translated return type:\n//\n// ```\n// Result<Result<T, (Option<SendErrorDetail>, Error)>, anyhow::Error>\n// ```\n//\n// Where the outer result is for managing errors that should be considered traps, and the inner\n// result is for managing successful return values, or application-level errors that might include\n// additional details. We could wrap up the tuple into an additional error type and declare it as a\n// trappable error, but that's a bit more overhead for only four functions that currently don't\n// populate the send-error-detail.\n\nconst MAX_HEADER_NAME_LEN: usize = (1 << 16) - 1;\n\nimpl http_req::Host for ComponentCtx {\n    async fn send(\n        &mut self,\n        h: Resource<http_req::Request>,\n        b: Resource<http_body::Body>,\n        backend_name: Resource<String>,\n    ) -> Result<http_resp::ResponseWithBody, http_req::ErrorWithDetail> {\n        let backend_name = self.wasi_table.get(&backend_name).unwrap();\n\n        crate::component::http_req::send(&mut self.sandbox, h, b, backend_name).await\n    }\n\n    async fn send_uncached(\n        &mut self,\n        h: Resource<http_req::Request>,\n        b: Resource<http_body::Body>,\n        backend_name: Resource<String>,\n    ) -> Result<http_resp::ResponseWithBody, http_req::ErrorWithDetail> {\n        let backend_name = self.wasi_table.get(&backend_name).unwrap();\n\n        crate::component::http_req::send_uncached(&mut self.sandbox, h, b, backend_name).await\n    }\n\n    async fn send_async(\n        &mut self,\n        h: Resource<http_req::Request>,\n        b: Resource<http_body::Body>,\n        backend_name: Resource<String>,\n    ) -> Result<Resource<http_req::PendingRequest>, types::Error> {\n        let backend_name = self.wasi_table.get(&backend_name).unwrap();\n\n        crate::component::http_req::send_async(&mut self.sandbox, h, b, backend_name).await\n    }\n\n    async fn send_async_uncached(\n        &mut self,\n        h: Resource<http_req::Request>,\n        b: Resource<http_body::Body>,\n        backend_name: Resource<String>,\n    ) -> Result<Resource<http_req::PendingRequest>, types::Error> {\n        let backend_name = self.wasi_table.get(&backend_name).unwrap();\n\n        crate::component::http_req::send_async_uncached(&mut self.sandbox, h, b, backend_name).await\n    }\n\n    async fn send_async_uncached_streaming(\n        &mut self,\n        h: Resource<http_req::Request>,\n        b: Resource<http_body::Body>,\n        backend_name: Resource<String>,\n    ) -> Result<Resource<http_req::PendingRequest>, types::Error> {\n        let backend_name = self.wasi_table.get(&backend_name).unwrap();\n\n        crate::component::http_req::send_async_uncached_streaming(\n            &mut self.sandbox,\n            h,\n            b,\n            backend_name,\n        )\n        .await\n    }\n\n    async fn send_async_streaming(\n        &mut self,\n        h: Resource<http_req::Request>,\n        b: Resource<http_body::Body>,\n        backend_name: Resource<String>,\n    ) -> Result<Resource<http_req::PendingRequest>, types::Error> {\n        let backend_name = self.wasi_table.get(&backend_name).unwrap();\n\n        crate::component::http_req::send_async_streaming(&mut self.sandbox, h, b, backend_name)\n            .await\n    }\n\n    async fn await_response(\n        &mut self,\n        h: Resource<http_req::PendingRequest>,\n    ) -> Result<http_resp::ResponseWithBody, http_req::ErrorWithDetail> {\n        let pending_req = self\n            .sandbox_mut()\n            .take_pending_request(h.into())\n            .unwrap()\n            .recv()\n            .await\n            .map_err(Into::into)\n            .map_err(types::Error::with_empty_detail)?;\n        let (resp_handle, body_handle) = self.sandbox_mut().insert_response(pending_req);\n        Ok((resp_handle.into(), body_handle.into()))\n    }\n\n    fn close(&mut self, h: Resource<http_req::Request>) -> Result<(), types::Error> {\n        // We don't do anything with the parts, but we do pass the error up if\n        // the handle given doesn't exist\n        self.sandbox_mut().take_request_parts(h.into())?;\n        Ok(())\n    }\n\n    fn upgrade_websocket(&mut self, backend: Resource<String>) -> Result<(), types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::http_req::upgrade_websocket(&mut self.sandbox, backend)\n    }\n}\n\nimpl http_req::HostRequest for ComponentCtx {\n    fn get_method(\n        &mut self,\n        h: Resource<http_req::Request>,\n        max_len: u64,\n    ) -> Result<String, types::Error> {\n        let req = self.sandbox.request_parts(h.into())?;\n        let req_method = &req.method;\n\n        if req_method.as_str().len() > usize::try_from(max_len).unwrap() {\n            return Err(types::Error::BufferLen(\n                u64::try_from(req_method.as_str().len()).unwrap(),\n            ));\n        }\n\n        Ok(req_method.to_string())\n    }\n\n    fn get_uri(\n        &mut self,\n        h: Resource<http_req::Request>,\n        max_len: u64,\n    ) -> Result<String, types::Error> {\n        let req = self.sandbox().request_parts(h.into())?;\n        let req_uri = &req.uri;\n        let res = req_uri.to_string();\n\n        if res.len() > usize::try_from(max_len).unwrap() {\n            return Err(types::Error::BufferLen(u64::try_from(res.len()).unwrap()));\n        }\n\n        Ok(res)\n    }\n\n    fn set_cache_override(\n        &mut self,\n        _h: Resource<http_req::Request>,\n        _cache_override: http_req::CacheOverride,\n    ) -> Result<(), types::Error> {\n        // For now, we ignore caching directives because we never cache anything\n        Ok(())\n    }\n\n    fn new(&mut self) -> Result<Resource<http_req::Request>, types::Error> {\n        let (parts, _) = Request::new(()).into_parts();\n        Ok(self.sandbox_mut().insert_request_parts(parts).into())\n    }\n\n    fn get_header_names(\n        &mut self,\n        h: Resource<http_req::Request>,\n        max_len: u64,\n        cursor: u32,\n    ) -> Result<(String, Option<u32>), types::Error> {\n        let headers = &self.sandbox().request_parts(h.into())?.headers;\n\n        let (buf, next) = get_names(headers.keys(), max_len, cursor)?;\n\n        Ok((buf, next))\n    }\n\n    fn get_header_value(\n        &mut self,\n        h: Resource<http_req::Request>,\n        name: String,\n        max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &self.sandbox().request_parts(h.into())?.headers;\n        let value = if let Some(value) = headers.get(&name) {\n            value\n        } else {\n            return Ok(None);\n        };\n\n        if value.len() > usize::try_from(max_len).unwrap() {\n            return Err(types::Error::BufferLen(u64::try_from(value.len()).unwrap()));\n        }\n\n        Ok(Some(value.as_bytes().to_owned()))\n    }\n\n    fn get_header_values(\n        &mut self,\n        h: Resource<http_req::Request>,\n        name: String,\n        max_len: u64,\n        cursor: u32,\n    ) -> Result<(Vec<u8>, Option<u32>), types::Error> {\n        let headers = &self.sandbox().request_parts(h.into()).unwrap().headers;\n\n        let (buf, next) = get_values(headers, &name, max_len, cursor)?;\n\n        Ok((buf, next))\n    }\n\n    fn set_header_values(\n        &mut self,\n        h: Resource<http_req::Request>,\n        name: String,\n        values: Vec<u8>,\n    ) -> Result<(), types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &mut self.sandbox_mut().request_parts_mut(h.into())?.headers;\n\n        let name = HeaderName::from_bytes(name.as_bytes())?;\n        let values = {\n            // split slice along nul bytes\n            let mut iter = values.split(|b| *b == 0);\n            // drop the empty item at the end\n            iter.next_back();\n            iter.map(HeaderValue::from_bytes)\n                .collect::<Result<Vec<HeaderValue>, _>>()?\n        };\n\n        // Remove any values if they exist\n        if let http::header::Entry::Occupied(e) = headers.entry(&name) {\n            e.remove_entry_mult();\n        }\n\n        for value in values {\n            headers.append(&name, value);\n        }\n\n        Ok(())\n    }\n\n    fn insert_header(\n        &mut self,\n        h: Resource<http_req::Request>,\n        name: String,\n        value: Vec<u8>,\n    ) -> Result<(), types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &mut self.sandbox_mut().request_parts_mut(h.into())?.headers;\n        let name = HeaderName::from_bytes(name.as_bytes())?;\n        let value = HeaderValue::from_bytes(value.as_slice())?;\n        headers.insert(name, value);\n\n        Ok(())\n    }\n\n    fn append_header(\n        &mut self,\n        h: Resource<http_req::Request>,\n        name: String,\n        value: Vec<u8>,\n    ) -> Result<(), types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &mut self.sandbox_mut().request_parts_mut(h.into())?.headers;\n        let name = HeaderName::from_bytes(name.as_bytes())?;\n        let value = HeaderValue::from_bytes(value.as_slice())?;\n        headers.append(name, value);\n\n        Ok(())\n    }\n\n    fn remove_header(\n        &mut self,\n        h: Resource<http_req::Request>,\n        name: String,\n    ) -> Result<(), types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &mut self.sandbox_mut().request_parts_mut(h.into())?.headers;\n        let name = HeaderName::from_bytes(name.as_bytes())?;\n        headers.remove(name).ok_or(types::Error::InvalidArgument)?;\n\n        Ok(())\n    }\n\n    fn set_method(\n        &mut self,\n        h: Resource<http_req::Request>,\n        method: String,\n    ) -> Result<(), types::Error> {\n        let method_ref = &mut self.sandbox_mut().request_parts_mut(h.into())?.method;\n        *method_ref = Method::from_bytes(method.as_bytes())?;\n        Ok(())\n    }\n\n    fn set_uri(&mut self, h: Resource<http_req::Request>, uri: String) -> Result<(), types::Error> {\n        let uri_ref = &mut self.sandbox_mut().request_parts_mut(h.into())?.uri;\n        *uri_ref = Uri::try_from(uri.as_bytes())?;\n        Ok(())\n    }\n\n    fn get_version(\n        &mut self,\n        h: Resource<http_req::Request>,\n    ) -> Result<http_types::HttpVersion, types::Error> {\n        let req = self.sandbox().request_parts(h.into())?;\n        let version = http_types::HttpVersion::try_from(req.version)?;\n        Ok(version)\n    }\n\n    fn set_version(\n        &mut self,\n        h: Resource<http_req::Request>,\n        version: http_types::HttpVersion,\n    ) -> Result<(), types::Error> {\n        let req = self.sandbox_mut().request_parts_mut(h.into())?;\n        req.version = hyper::Version::from(version);\n        Ok(())\n    }\n\n    fn set_auto_decompress_response(\n        &mut self,\n        h: Resource<http_req::Request>,\n        encodings: http_types::ContentEncodings,\n    ) -> Result<(), types::Error> {\n        use crate::wiggle_abi::types;\n\n        // NOTE: We're going to hide this flag in the extensions of the request in order to decrease\n        // the book-keeping burden inside Sandbox. The flag will get picked up later, in `send_request`.\n        let extensions = &mut self.sandbox_mut().request_parts_mut(h.into())?.extensions;\n\n        let encodings = types::ContentEncodings::try_from(encodings.as_array()[0])?;\n\n        match extensions.get_mut::<ViceroyRequestMetadata>() {\n            None => {\n                extensions.insert(ViceroyRequestMetadata {\n                    auto_decompress_encodings: encodings,\n                    ..Default::default()\n                });\n            }\n            Some(vrm) => {\n                vrm.auto_decompress_encodings = encodings;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn redirect_to_websocket_proxy(\n        &mut self,\n        handle: Resource<http_req::Request>,\n        backend: Resource<String>,\n    ) -> Result<(), types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::http_req::redirect_to_websocket_proxy(&mut self.sandbox, handle, backend)\n    }\n\n    fn set_framing_headers_mode(\n        &mut self,\n        h: Resource<http_req::Request>,\n        mode: http_types::FramingHeadersMode,\n    ) -> Result<(), types::Error> {\n        let normalized_mode = match mode {\n            http_types::FramingHeadersMode::Automatic => {\n                crate::wiggle_abi::types::FramingHeadersMode::Automatic\n            }\n            http_types::FramingHeadersMode::ManuallyFromHeaders => {\n                crate::wiggle_abi::types::FramingHeadersMode::ManuallyFromHeaders\n            }\n        };\n\n        let extensions = &mut self.sandbox_mut().request_parts_mut(h.into())?.extensions;\n\n        match extensions.get_mut::<ViceroyRequestMetadata>() {\n            None => {\n                extensions.insert(ViceroyRequestMetadata {\n                    framing_headers_mode: normalized_mode,\n                    ..Default::default()\n                });\n            }\n            Some(vrm) => {\n                vrm.framing_headers_mode = normalized_mode;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn redirect_to_grip_proxy(\n        &mut self,\n        req_handle: Resource<http_req::Request>,\n        backend: Resource<String>,\n    ) -> Result<(), types::Error> {\n        let backend = self.wasi_table.get(&backend).unwrap();\n        crate::component::http_req::redirect_to_grip_proxy(&mut self.sandbox, req_handle, backend)\n    }\n\n    fn drop(&mut self, _request: Resource<http_req::Request>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl http_req::HostExtraCacheOverrideDetails for ComponentCtx {\n    fn drop(\n        &mut self,\n        _details: Resource<http_req::ExtraCacheOverrideDetails>,\n    ) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl http_req::HostExtraSendErrorDetail for ComponentCtx {\n    fn drop(&mut self, _details: Resource<http_req::ExtraSendErrorDetail>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/http_resp.rs",
    "content": "use {\n    crate::{\n        component::{\n            bindings::fastly::compute::{http_body, http_resp, http_types, types},\n            compute::headers::get_names,\n        },\n        error::Error,\n        linking::{ComponentCtx, SandboxView},\n        sandbox::ViceroyResponseMetadata,\n        upstream,\n    },\n    cfg_if::cfg_if,\n    http::{HeaderName, HeaderValue},\n    hyper::http::response::Response,\n    wasmtime::component::Resource,\n};\n\n// This is not used in the test-fatalerror-config configuration, so that configuration produces a\n// complaint if it is unnecessarily used.\n#[cfg(not(feature = \"test-fatalerror-config\"))]\nuse crate::component::compute::headers::get_values;\n\nconst MAX_HEADER_NAME_LEN: usize = (1 << 16) - 1;\n\nimpl http_resp::Host for ComponentCtx {\n    fn send_downstream(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        b: Resource<http_body::Body>,\n    ) -> Result<(), types::Error> {\n        let resp = {\n            // Take the response parts and body from the sandbox, and use them to build a response.\n            // Return an `FastlyStatus::Badf` error code if either of the given handles are invalid.\n            let resp_parts = self.sandbox_mut().take_response_parts(h.into())?;\n            let body = self.sandbox_mut().take_body(b.into())?;\n            Response::from_parts(resp_parts, body)\n        }; // Set the downstream response, and return.\n        self.sandbox_mut().send_downstream_response(resp)?;\n        Ok(())\n    }\n\n    fn send_downstream_streaming(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        b: Resource<http_body::Body>,\n    ) -> Result<(), types::Error> {\n        let resp = {\n            // Take the response parts and body from the sandbox, and use them to build a response.\n            // Return an `FastlyStatus::Badf` error code if either of the given handles are invalid.\n            let resp_parts = self.sandbox_mut().take_response_parts(h.into())?;\n            let body = self.sandbox_mut().begin_streaming(b.into())?;\n            Response::from_parts(resp_parts, body)\n        }; // Set the downstream response, and return.\n        self.sandbox_mut().send_downstream_response(resp)?;\n        Ok(())\n    }\n\n    fn close(&mut self, h: Resource<http_resp::Response>) -> Result<(), types::Error> {\n        // We don't do anything with the parts, but we do pass the error up if\n        // the handle given doesn't exist\n        self.sandbox_mut().take_response_parts(h.into())?;\n        Ok(())\n    }\n}\n\nimpl http_resp::HostResponse for ComponentCtx {\n    fn new(&mut self) -> Result<Resource<http_resp::Response>, types::Error> {\n        let (parts, _) = Response::new(()).into_parts();\n        Ok(self.sandbox_mut().insert_response_parts(parts).into())\n    }\n\n    fn get_status(\n        &mut self,\n        h: Resource<http_resp::Response>,\n    ) -> Result<http_types::HttpStatus, types::Error> {\n        let parts = self.sandbox().response_parts(h.into())?;\n        Ok(parts.status.as_u16())\n    }\n\n    fn set_status(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        status: http_types::HttpStatus,\n    ) -> Result<(), types::Error> {\n        let resp = self.sandbox_mut().response_parts_mut(h.into())?;\n        let status = hyper::StatusCode::from_u16(status)?;\n        resp.status = status;\n        Ok(())\n    }\n\n    fn append_header(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        name: String,\n        value: Vec<u8>,\n    ) -> Result<(), types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            Err(types::Error::InvalidArgument)?;\n        }\n\n        let headers = &mut self.sandbox_mut().response_parts_mut(h.into())?.headers;\n        let name = HeaderName::from_bytes(name.as_bytes())?;\n        let value = HeaderValue::from_bytes(value.as_slice())?;\n        headers.append(name, value);\n        Ok(())\n    }\n\n    fn get_header_names(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        max_len: u64,\n        cursor: u32,\n    ) -> Result<(String, Option<u32>), types::Error> {\n        let headers = &self.sandbox_mut().response_parts(h.into())?.headers;\n\n        let (buf, next) = get_names(headers.keys(), max_len, cursor)?;\n\n        Ok((buf, next))\n    }\n\n    fn get_header_value(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        name: String,\n        max_len: u64,\n    ) -> Result<Option<Vec<u8>>, types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &self.sandbox().response_parts(h.into())?.headers;\n        let value = if let Some(value) = headers.get(&name) {\n            value\n        } else {\n            return Ok(None);\n        };\n\n        if value.len() > usize::try_from(max_len).unwrap() {\n            return Err(types::Error::BufferLen(u64::try_from(value.len()).unwrap()));\n        }\n\n        Ok(Some(value.as_bytes().to_owned()))\n    }\n\n    // This function has an extra `wasmtime::Result` wrapped around its return\n    // type because it's marked as \"trappable\" in src/component.rs, in order\n    // to support the artificial trap used by the trap-test testcase.\n    fn get_header_values(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        name: String,\n        max_len: u64,\n        cursor: u32,\n    ) -> wasmtime::Result<Result<(Vec<u8>, Option<u32>), types::Error>> {\n        cfg_if! {\n            if #[cfg(feature = \"test-fatalerror-config\")] {\n                // Avoid warnings:\n                let _ = (h, name, max_len, cursor);\n                return Err(Error::FatalError(\"A fatal error occurred in the test-only implementation of header_values_get\".to_string()).into());\n            } else {\n                if name.len() > MAX_HEADER_NAME_LEN {\n                    return Ok(Err(Error::InvalidArgument.into()));\n                }\n\n                let headers = &self.sandbox().response_parts(h.into()).unwrap().headers;\n\n                let (buf, next) = match get_values(\n                    headers,\n                    &name,\n                    max_len,\n                    cursor,\n                ) {\n                    Ok(tuple) => tuple,\n                    Err(err) => return Ok(Err(err)),\n                };\n\n                Ok(Ok((buf, next)))\n            }\n        }\n    }\n\n    fn set_header_values(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        name: String,\n        values: Vec<u8>,\n    ) -> Result<(), types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &mut self.sandbox_mut().response_parts_mut(h.into())?.headers;\n\n        let name = HeaderName::from_bytes(name.as_bytes())?;\n        let values = {\n            // split slice along nul bytes\n            let mut iter = values.split(|b| *b == 0);\n            // drop the empty item at the end\n            iter.next_back();\n            iter.map(HeaderValue::from_bytes)\n                .collect::<Result<Vec<HeaderValue>, _>>()?\n        };\n\n        // Remove any values if they exist\n        if let http::header::Entry::Occupied(e) = headers.entry(&name) {\n            e.remove_entry_mult();\n        }\n\n        for value in values {\n            headers.append(&name, value);\n        }\n\n        Ok(())\n    }\n\n    fn insert_header(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        name: String,\n        value: Vec<u8>,\n    ) -> Result<(), types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &mut self.sandbox_mut().response_parts_mut(h.into())?.headers;\n        let name = HeaderName::from_bytes(name.as_bytes())?;\n        let value = HeaderValue::from_bytes(value.as_slice())?;\n        headers.insert(name, value);\n\n        Ok(())\n    }\n\n    fn remove_header(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        name: String,\n    ) -> Result<(), types::Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let headers = &mut self.sandbox_mut().response_parts_mut(h.into())?.headers;\n        let name = HeaderName::from_bytes(name.as_bytes())?;\n        headers.remove(name).ok_or(types::Error::InvalidArgument)?;\n\n        Ok(())\n    }\n\n    fn get_version(\n        &mut self,\n        h: Resource<http_resp::Response>,\n    ) -> Result<http_types::HttpVersion, types::Error> {\n        let req = self.sandbox().response_parts(h.into())?;\n        let version = http_types::HttpVersion::try_from(req.version)?;\n        Ok(version)\n    }\n\n    fn set_version(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        version: http_types::HttpVersion,\n    ) -> Result<(), types::Error> {\n        let req = self.sandbox_mut().response_parts_mut(h.into())?;\n        req.version = hyper::Version::from(version);\n        Ok(())\n    }\n\n    fn set_framing_headers_mode(\n        &mut self,\n        h: Resource<http_resp::Response>,\n        mode: http_types::FramingHeadersMode,\n    ) -> Result<(), types::Error> {\n        let normalized_mode = match mode {\n            http_types::FramingHeadersMode::Automatic => {\n                crate::wiggle_abi::types::FramingHeadersMode::Automatic\n            }\n            http_types::FramingHeadersMode::ManuallyFromHeaders => {\n                crate::wiggle_abi::types::FramingHeadersMode::ManuallyFromHeaders\n            }\n        };\n\n        let extensions = &mut self.sandbox_mut().response_parts_mut(h.into())?.extensions;\n\n        match extensions.get_mut::<ViceroyResponseMetadata>() {\n            None => {\n                extensions.insert(ViceroyResponseMetadata {\n                    framing_headers_mode: normalized_mode,\n                    // future note: at time of writing, this is the only field of\n                    // this structure, but there is an intention to add more fields.\n                    // When we do, and if/when an error appears, what you're looking\n                    // for is:\n                    // ..Default::default()\n                });\n            }\n            Some(vrm) => {\n                vrm.framing_headers_mode = normalized_mode;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn set_http_keepalive_mode(\n        &mut self,\n        _: Resource<http_resp::Response>,\n        mode: http_resp::KeepaliveMode,\n    ) -> Result<(), types::Error> {\n        match mode {\n            http_resp::KeepaliveMode::NoKeepalive => {\n                Err(Error::NotAvailable(\"No Keepalive\").into())\n            }\n            http_resp::KeepaliveMode::Automatic => Ok(()),\n        }\n    }\n\n    fn get_remote_ip_addr(\n        &mut self,\n        resp_handle: Resource<http_resp::Response>,\n    ) -> Option<http_resp::IpAddress> {\n        let resp = self.sandbox().response_parts(resp_handle.into()).unwrap();\n        let md = resp.extensions.get::<upstream::ConnMetadata>()?;\n\n        Some(md.remote_addr.ip().into())\n    }\n\n    fn get_remote_port(&mut self, resp_handle: Resource<http_resp::Response>) -> Option<u16> {\n        let resp = self.sandbox().response_parts(resp_handle.into()).unwrap();\n        let md = resp.extensions.get::<upstream::ConnMetadata>()?;\n        let port = md.remote_addr.port();\n        Some(port)\n    }\n\n    fn drop(&mut self, _response: Resource<http_resp::Response>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/http_types.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{http_types, types},\n    crate::linking::ComponentCtx,\n};\n\nimpl http_types::Host for ComponentCtx {}\n\n// The http crate's `Version` is a struct that has a bunch of\n// associated constants, not an enum; this is only a partial conversion.\nimpl TryFrom<http::version::Version> for http_types::HttpVersion {\n    type Error = types::Error;\n    fn try_from(v: http::version::Version) -> Result<Self, Self::Error> {\n        match v {\n            http::version::Version::HTTP_09 => Ok(http_types::HttpVersion::Http09),\n            http::version::Version::HTTP_10 => Ok(http_types::HttpVersion::Http10),\n            http::version::Version::HTTP_11 => Ok(http_types::HttpVersion::Http11),\n            http::version::Version::HTTP_2 => Ok(http_types::HttpVersion::H2),\n            http::version::Version::HTTP_3 => Ok(http_types::HttpVersion::H3),\n            _ => Err(types::Error::Unsupported),\n        }\n    }\n}\n\nimpl From<http_types::HttpVersion> for http::version::Version {\n    fn from(v: http_types::HttpVersion) -> http::version::Version {\n        match v {\n            http_types::HttpVersion::Http09 => http::version::Version::HTTP_09,\n            http_types::HttpVersion::Http10 => http::version::Version::HTTP_10,\n            http_types::HttpVersion::Http11 => http::version::Version::HTTP_11,\n            http_types::HttpVersion::H2 => http::version::Version::HTTP_2,\n            http_types::HttpVersion::H3 => http::version::Version::HTTP_3,\n        }\n    }\n}\n"
  },
  {
    "path": "src/component/compute/image_optimizer.rs",
    "content": "use crate::component::bindings::fastly::compute::{\n    http_body, http_req, http_resp, image_optimizer, types,\n};\nuse crate::linking::ComponentCtx;\nuse wasmtime::component::Resource;\n\nimpl image_optimizer::Host for ComponentCtx {\n    fn transform_image_optimizer_request(\n        &mut self,\n        origin_image_request: Resource<http_req::Request>,\n        origin_image_request_body: Option<Resource<http_body::Body>>,\n        origin_image_request_backend: Resource<String>,\n        io_transform_config: image_optimizer::ImageOptimizerTransformOptions,\n    ) -> Result<http_resp::ResponseWithBody, types::Error> {\n        let origin_image_request_backend =\n            self.wasi_table.get(&origin_image_request_backend).unwrap();\n        crate::component::image_optimizer::transform_image_optimizer_request(\n            &mut self.sandbox,\n            origin_image_request,\n            origin_image_request_body,\n            origin_image_request_backend,\n            io_transform_config,\n        )\n    }\n}\n\nimpl image_optimizer::HostExtraImageOptimizerTransformOptions for ComponentCtx {\n    fn drop(\n        &mut self,\n        _options: Resource<image_optimizer::ExtraImageOptimizerTransformOptions>,\n    ) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/kv_store.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{\n        http_body,\n        kv_store::{self, InsertMode},\n        types,\n    },\n    crate::{\n        error::Error,\n        linking::{ComponentCtx, SandboxView},\n        object_store::ObjectKey,\n        sandbox::{\n            PeekableTask, PendingKvDeleteTask, PendingKvInsertTask, PendingKvListTask,\n            PendingKvLookupTask,\n        },\n        wiggle_abi::types::{\n            KvInsertMode, KvStoreDeleteHandle, KvStoreInsertHandle, KvStoreListHandle,\n            KvStoreLookupHandle,\n        },\n    },\n    wasmtime::component::Resource,\n    wasmtime_wasi_io::IoView,\n};\n\npub struct Entry {\n    body: Option<Resource<http_body::Body>>,\n    metadata: Option<String>,\n    generation: u64,\n}\n\nimpl kv_store::HostEntry for ComponentCtx {\n    fn take_body(\n        &mut self,\n        rep: wasmtime::component::Resource<kv_store::Entry>,\n    ) -> Option<Resource<http_body::Body>> {\n        self.table().get_mut(&rep).unwrap().body.take()\n    }\n\n    fn metadata(\n        &mut self,\n        rep: wasmtime::component::Resource<kv_store::Entry>,\n        max_len: u64,\n    ) -> Result<Option<String>, types::Error> {\n        let res = self.table().get(&rep).unwrap();\n        let Some(md) = res.metadata.as_ref() else {\n            return Ok(None);\n        };\n\n        if md.len() > max_len as usize {\n            return Err(types::Error::BufferLen(md.len() as u64));\n        }\n\n        Ok(self.table().get_mut(&rep)?.metadata.take())\n    }\n\n    fn generation(&mut self, rep: wasmtime::component::Resource<kv_store::Entry>) -> u64 {\n        self.table().get(&rep).unwrap().generation\n    }\n\n    fn drop(\n        &mut self,\n        rep: wasmtime::component::Resource<kv_store::Entry>,\n    ) -> wasmtime::Result<()> {\n        self.table().delete(rep)?;\n        Ok(())\n    }\n}\n\nimpl kv_store::Host for ComponentCtx {\n    async fn await_lookup(\n        &mut self,\n        handle: Resource<kv_store::PendingLookup>,\n    ) -> wasmtime::Result<\n        Result<Option<wasmtime::component::Resource<kv_store::Entry>>, kv_store::KvError>,\n    > {\n        let handle = KvStoreLookupHandle::from(handle).into();\n        let resp = self\n            .sandbox_mut()\n            .take_pending_kv_lookup(handle)\n            .unwrap()\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(Some(value)) => {\n                let lr = kv_store::Entry {\n                    body: Some(self.sandbox_mut().insert_body(value.body.into()).into()),\n                    metadata: match value.metadata_len {\n                        0 => None,\n                        _ => Some(value.metadata),\n                    },\n                    generation: value.generation,\n                };\n\n                let res = self.table().push(lr)?;\n\n                Ok(Ok(Some(res)))\n            }\n            Ok(None) => Ok(Ok(None)),\n            Err(e) => Ok(Err(e.into())),\n        }\n    }\n\n    async fn await_insert(\n        &mut self,\n        handle: Resource<kv_store::PendingInsert>,\n    ) -> Result<(), kv_store::KvError> {\n        let handle = KvStoreInsertHandle::from(handle).into();\n        let resp = self\n            .sandbox_mut()\n            .take_pending_kv_insert(handle)\n            .unwrap()\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(()) => Ok(()),\n            Err(e) => Err(e.into()),\n        }\n    }\n\n    async fn await_delete(\n        &mut self,\n        handle: Resource<kv_store::PendingDelete>,\n    ) -> Result<bool, kv_store::KvError> {\n        let handle = KvStoreDeleteHandle::from(handle).into();\n        let resp = self\n            .sandbox_mut()\n            .take_pending_kv_delete(handle)\n            .unwrap()\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(res) => Ok(res),\n            Err(e) => Err(e.into()),\n        }\n    }\n\n    async fn await_list(\n        &mut self,\n        handle: Resource<kv_store::PendingList>,\n    ) -> Result<Resource<kv_store::Body>, kv_store::KvError> {\n        let handle = KvStoreListHandle::from(handle).into();\n        let resp = self\n            .sandbox_mut()\n            .take_pending_kv_list(handle)\n            .unwrap()\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(value) => Ok(self.sandbox_mut().insert_body(value.into()).into()),\n            Err(e) => Err(e.into()),\n        }\n    }\n}\n\nimpl kv_store::HostStore for ComponentCtx {\n    fn open(&mut self, name: String) -> Result<Resource<kv_store::Store>, types::OpenError> {\n        if self\n            .sandbox()\n            .kv_store()\n            .store_exists(&name)\n            .map_err(Error::ObjectStoreError)?\n        {\n            let h = self.sandbox_mut().kv_store_handle(&name);\n            Ok(h.into())\n        } else {\n            Err(types::OpenError::NotFound)\n        }\n    }\n\n    async fn lookup(\n        &mut self,\n        _store: Resource<kv_store::Store>,\n        _key: String,\n    ) -> Result<Option<Resource<kv_store::Entry>>, kv_store::KvError> {\n        Err(Error::Unsupported {\n            msg: \"kv-store.lookup is not supported in Viceroy\",\n        }\n        .into())\n    }\n\n    async fn lookup_async(\n        &mut self,\n        store: Resource<kv_store::Store>,\n        key: String,\n    ) -> Result<Resource<kv_store::PendingLookup>, types::Error> {\n        let store = self.sandbox.get_kv_store_key(store.into()).unwrap();\n        // just create a future that's already ready\n        let fut = futures::future::ok(self.sandbox.obj_lookup(store.clone(), ObjectKey::new(key)?));\n        let task = PeekableTask::spawn(fut).await;\n        let lh = self\n            .sandbox_mut()\n            .insert_pending_kv_lookup(PendingKvLookupTask::new(task));\n        Ok(KvStoreLookupHandle::from(lh).into())\n    }\n\n    async fn insert(\n        &mut self,\n        _store: Resource<kv_store::Store>,\n        _key: String,\n        _body_handle: Resource<kv_store::Body>,\n        _options: kv_store::InsertOptions,\n    ) -> Result<(), kv_store::KvError> {\n        Err(Error::Unsupported {\n            msg: \"kv-store.insert is not supported in Viceroy\",\n        }\n        .into())\n    }\n\n    async fn insert_async(\n        &mut self,\n        store: Resource<kv_store::Store>,\n        key: String,\n        body_handle: Resource<kv_store::Body>,\n        options: kv_store::InsertOptions,\n    ) -> Result<Resource<kv_store::PendingInsert>, types::Error> {\n        let body = self\n            .sandbox_mut()\n            .take_body(body_handle.into())?\n            .read_into_vec()\n            .await?;\n        let store = self.sandbox.get_kv_store_key(store.into()).unwrap();\n\n        let mode = match options.mode {\n            InsertMode::Overwrite => KvInsertMode::Overwrite,\n            InsertMode::Add => KvInsertMode::Add,\n            InsertMode::Append => KvInsertMode::Append,\n            InsertMode::Prepend => KvInsertMode::Prepend,\n        };\n\n        let meta = options.metadata;\n        let igm = options.if_generation_match;\n\n        let ttl = options\n            .time_to_live_sec\n            .map(|time_to_live_sec| std::time::Duration::from_secs(time_to_live_sec as u64));\n\n        let fut = futures::future::ok(self.sandbox.kv_insert(\n            store.clone(),\n            ObjectKey::new(key)?,\n            body,\n            Some(mode),\n            igm,\n            meta,\n            ttl,\n        ));\n        let task = PeekableTask::spawn(fut).await;\n        let handle = self\n            .sandbox\n            .insert_pending_kv_insert(PendingKvInsertTask::new(task));\n        Ok(handle.into())\n    }\n\n    async fn delete(\n        &mut self,\n        _store: Resource<kv_store::Store>,\n        _key: String,\n    ) -> Result<bool, kv_store::KvError> {\n        Err(Error::Unsupported {\n            msg: \"kv-store.delete is not supported in Viceroy\",\n        }\n        .into())\n    }\n\n    async fn delete_async(\n        &mut self,\n        store: Resource<kv_store::Store>,\n        key: String,\n    ) -> Result<Resource<kv_store::PendingDelete>, types::Error> {\n        let store = self.sandbox.get_kv_store_key(store.into()).unwrap();\n        // just create a future that's already ready\n        let fut = futures::future::ok(self.sandbox.kv_delete(store.clone(), ObjectKey::new(key)?));\n        let task = PeekableTask::spawn(fut).await;\n        let lh = self\n            .sandbox\n            .insert_pending_kv_delete(PendingKvDeleteTask::new(task));\n        Ok(KvStoreDeleteHandle::from(lh).into())\n    }\n\n    async fn list(\n        &mut self,\n        _store: Resource<kv_store::Store>,\n        _options: kv_store::ListOptions,\n    ) -> Result<Resource<kv_store::PendingList>, kv_store::KvError> {\n        Err(Error::Unsupported {\n            msg: \"kv-store.list is not supported in Viceroy\",\n        }\n        .into())\n    }\n\n    async fn list_async(\n        &mut self,\n        store: Resource<kv_store::Store>,\n        options: kv_store::ListOptions,\n    ) -> Result<Resource<kv_store::PendingList>, types::Error> {\n        let store = self.sandbox.get_kv_store_key(store.into()).unwrap();\n\n        let cursor = options.cursor;\n        let prefix = options.prefix;\n        let limit = options.limit;\n\n        let fut = futures::future::ok(self.sandbox.kv_list(store.clone(), cursor, prefix, limit));\n        let task = PeekableTask::spawn(fut).await;\n        let handle = self\n            .sandbox\n            .insert_pending_kv_list(PendingKvListTask::new(task));\n        Ok(KvStoreListHandle::from(handle).into())\n    }\n\n    fn drop(&mut self, _store: Resource<kv_store::Store>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl kv_store::HostExtraInsertOptions for ComponentCtx {\n    fn drop(&mut self, _options: Resource<kv_store::ExtraInsertOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl kv_store::HostExtraListOptions for ComponentCtx {\n    fn drop(&mut self, _options: Resource<kv_store::ExtraListOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl kv_store::HostExtraKvError for ComponentCtx {\n    fn drop(&mut self, _options: Resource<kv_store::ExtraKvError>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/log.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{log, types},\n    crate::linking::{ComponentCtx, SandboxView},\n    lazy_static::lazy_static,\n    wasmtime::component::Resource,\n};\n\nfn is_reserved_endpoint(name: &[u8]) -> bool {\n    use regex::bytes::{RegexSet, RegexSetBuilder};\n    const RESERVED_ENDPOINTS: &[&str] = &[\"^stdout$\", \"^stderr$\", \"^fst_managed_\"];\n    lazy_static! {\n        static ref RESERVED_ENDPOINT_RE: RegexSet = RegexSetBuilder::new(RESERVED_ENDPOINTS)\n            .case_insensitive(true)\n            .build()\n            .unwrap();\n    }\n    RESERVED_ENDPOINT_RE.is_match(name)\n}\n\nimpl log::Host for ComponentCtx {}\n\nimpl log::HostEndpoint for ComponentCtx {\n    fn open(&mut self, name: String) -> Result<Resource<log::Endpoint>, types::OpenError> {\n        let name = name.as_bytes();\n\n        if is_reserved_endpoint(name) {\n            return Err(types::OpenError::Reserved);\n        }\n\n        Ok(self.sandbox_mut().log_endpoint_handle(name).into())\n    }\n\n    fn write(&mut self, h: Resource<log::Endpoint>, msg: Vec<u8>) {\n        let endpoint = self.sandbox().log_endpoint(h.into()).unwrap();\n\n        // The log API is infallible, so if we get an error, warn about it\n        // rather than bubbling it up through the log API.\n        match endpoint.write_entry(&msg) {\n            Ok(()) => {}\n            Err(err) => tracing::error!(\"Error writing log message: {:?}\", err),\n        }\n    }\n\n    fn drop(&mut self, _endpoint: Resource<log::Endpoint>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/purge.rs",
    "content": "use {\n    crate::Error,\n    crate::component::bindings::fastly::compute::{purge, types},\n    crate::linking::ComponentCtx,\n    wasmtime::component::Resource,\n};\n\nimpl purge::Host for ComponentCtx {\n    fn purge_surrogate_key(\n        &mut self,\n        surrogate_key: String,\n        options: purge::PurgeOptions,\n    ) -> Result<(), types::Error> {\n        let soft_purge = options.soft_purge;\n        let surrogate_key = surrogate_key.parse()?;\n        let purged = self.sandbox().cache().purge(surrogate_key, soft_purge);\n        tracing::debug!(\"{purged} variants purged\");\n        Ok(())\n    }\n\n    fn purge_surrogate_key_verbose(\n        &mut self,\n        _surrogate_key: String,\n        _options: purge::PurgeOptions,\n        _max_len: u64,\n    ) -> Result<String, types::Error> {\n        Err(Error::Unsupported {\n            msg: \"purge.purge-surrogate-key-verbose is not supported in Viceroy\",\n        }\n        .into())\n    }\n}\n\nimpl purge::HostExtraPurgeOptions for ComponentCtx {\n    fn drop(&mut self, _options: Resource<purge::ExtraPurgeOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/secret_store.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{secret_store, types},\n    crate::{\n        error::Error,\n        linking::{ComponentCtx, SandboxView},\n        secret_store::SecretLookup,\n        wiggle_abi::SecretStoreError,\n    },\n    wasmtime::component::Resource,\n};\n\nimpl secret_store::Host for ComponentCtx {}\n\nimpl secret_store::HostStore for ComponentCtx {\n    fn open(&mut self, name: String) -> Result<Resource<secret_store::Store>, types::OpenError> {\n        let handle = self\n            .sandbox_mut()\n            .secret_store_handle(&name)\n            .ok_or(types::OpenError::NotFound)?;\n        Ok(handle.into())\n    }\n\n    fn get(\n        &mut self,\n        store: Resource<secret_store::Store>,\n        key: String,\n    ) -> Result<Option<Resource<secret_store::Secret>>, types::Error> {\n        let store = store.into();\n        let store_name = self\n            .sandbox()\n            .secret_store_name(store)\n            .ok_or_else(|| types::Error::from(SecretStoreError::InvalidSecretStoreHandle(store)))?;\n        Ok(self\n            .sandbox_mut()\n            .secret_handle(&store_name, &key)\n            .map(From::from))\n    }\n\n    fn drop(&mut self, _store: Resource<secret_store::Store>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n\nimpl secret_store::HostSecret for ComponentCtx {\n    fn plaintext(\n        &mut self,\n        secret: Resource<secret_store::Secret>,\n        max_len: u64,\n    ) -> Result<Vec<u8>, types::Error> {\n        let secret = secret.into();\n        let lookup = self\n            .sandbox()\n            .secret_lookup(secret)\n            .ok_or(Error::SecretStoreError(\n                SecretStoreError::InvalidSecretHandle(secret),\n            ))?;\n\n        let plaintext = match &lookup {\n            SecretLookup::Standard {\n                store_name,\n                secret_name,\n            } => self\n                .sandbox()\n                .secret_stores()\n                .get_store(store_name)\n                .ok_or(Error::SecretStoreError(\n                    SecretStoreError::InvalidSecretHandle(secret),\n                ))?\n                .get_secret(secret_name)\n                .ok_or(Error::SecretStoreError(\n                    SecretStoreError::InvalidSecretHandle(secret),\n                ))?\n                .plaintext(),\n\n            SecretLookup::Injected { plaintext } => plaintext,\n        };\n\n        if plaintext.len() > usize::try_from(max_len).unwrap() {\n            return Err(Error::BufferLengthError {\n                buf: \"plaintext\",\n                len: \"plaintext_max_len\",\n            }\n            .into());\n        }\n\n        Ok(plaintext.to_owned())\n    }\n\n    fn from_bytes(\n        &mut self,\n        plaintext: Vec<u8>,\n    ) -> Result<Resource<secret_store::Secret>, types::Error> {\n        Ok(self.sandbox_mut().add_secret(plaintext).into())\n    }\n\n    fn drop(&mut self, _secret: Resource<secret_store::Secret>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/security.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{http_body, http_req, security, types},\n    crate::{error::Error, linking::ComponentCtx},\n    wasmtime::component::Resource,\n};\n\nimpl security::Host for ComponentCtx {\n    fn inspect(\n        &mut self,\n        ds_req: Resource<http_req::Request>,\n        ds_body: Resource<http_body::Body>,\n        info: security::InspectOptions,\n        buf_max_len: u64,\n    ) -> Result<String, types::Error> {\n        // Make sure we're given valid handles, even though we won't use them.\n        let _ = self.sandbox().request_parts(ds_req.into())?;\n        let _ = self.sandbox().body(ds_body.into())?;\n\n        // For now, corp and workspace arguments are required to actually generate the hostname,\n        // but in the future, the lookaside service will be generated using the customer ID, and\n        // it will be okay for them to be unspecified or empty.\n        if info.corp.is_none() || info.workspace.is_none() {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        if info.corp.unwrap().is_empty() || info.workspace.unwrap().is_empty() {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        // Return the mock NGWAF response.\n        let ngwaf_resp = self.sandbox().ngwaf_response();\n        let ngwaf_resp_len = ngwaf_resp.len();\n\n        match u64::try_from(ngwaf_resp_len) {\n            Ok(ngwaf_resp_len) if ngwaf_resp_len <= buf_max_len => Ok(ngwaf_resp),\n            too_large => Err(types::Error::BufferLen(too_large.unwrap_or(0))),\n        }\n    }\n}\n\nimpl security::HostExtraInspectOptions for ComponentCtx {\n    fn drop(&mut self, _options: Resource<security::ExtraInspectOptions>) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/shielding.rs",
    "content": "use crate::component::bindings::fastly::compute::{shielding, types};\nuse crate::error::Error;\nuse crate::linking::ComponentCtx;\nuse wasmtime::component::Resource;\n\nimpl shielding::Host for ComponentCtx {\n    fn shield_info(&mut self, name: String, max_len: u64) -> Result<String, types::Error> {\n        let running_on = self.sandbox().shielding_sites().is_local(&name);\n        let unencrypted = self\n            .sandbox()\n            .shielding_sites()\n            .get_unencrypted(&name)\n            .map(|x| x.to_string())\n            .unwrap_or_default();\n        let encrypted = self\n            .sandbox()\n            .shielding_sites()\n            .get_encrypted(&name)\n            .map(|x| x.to_string())\n            .unwrap_or_default();\n\n        if !running_on && unencrypted.is_empty() {\n            return Err(Error::InvalidArgument.into());\n        }\n\n        let mut output = String::new();\n\n        output.push(if running_on { '\\x01' } else { '\\0' });\n        output += unencrypted.as_str();\n        output.push('\\0');\n        output += encrypted.as_str();\n        output.push('\\0');\n\n        let target_len = output.len() as u64;\n\n        if target_len > max_len {\n            return Err(Error::BufferLengthError {\n                buf: \"shielding_info\",\n                len: \"info.len()\",\n            }\n            .into());\n        }\n\n        Ok(output)\n    }\n\n    fn backend_for_shield(\n        &mut self,\n        target_shield: String,\n        options: Option<Resource<shielding::ShieldBackendOptions>>,\n    ) -> Result<Resource<String>, types::Error> {\n        // `u64::MAX` because we don't need to impose any extra constraints\n        // on the length of the backend name string here.\n        let name = crate::component::shielding::backend_for_shield(\n            &mut self.sandbox,\n            &mut self.wasi_table,\n            &target_shield,\n            options,\n            u64::MAX,\n        )?;\n\n        let res = self.wasi_table.push(name).unwrap();\n\n        Ok(res)\n    }\n}\n\nimpl shielding::HostShieldBackendOptions for ComponentCtx {\n    fn set_first_byte_timeout(\n        &mut self,\n        _resource: Resource<shielding::ShieldBackendOptions>,\n        _timeout_ms: u32,\n    ) {\n    }\n\n    fn set_cache_key(\n        &mut self,\n        _resource: Resource<shielding::ShieldBackendOptions>,\n        _cache_key: String,\n    ) {\n    }\n\n    fn new(&mut self) -> Result<Resource<shielding::ShieldBackendOptions>, anyhow::Error> {\n        Err(Error::Unsupported {\n            msg: \"Shield backend options not yet supported in Viceroy\",\n        }\n        .into())\n    }\n\n    fn drop(\n        &mut self,\n        _options: Resource<shielding::ShieldBackendOptions>,\n    ) -> wasmtime::Result<()> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/component/compute/types.rs",
    "content": "use {crate::component::bindings::fastly::compute::types, crate::linking::ComponentCtx};\n\nimpl types::Host for ComponentCtx {}\n\nimpl From<std::net::IpAddr> for types::IpAddress {\n    fn from(addr: std::net::IpAddr) -> Self {\n        match addr {\n            std::net::IpAddr::V4(addr) => types::IpAddress::Ipv4(addr.octets().into()),\n            std::net::IpAddr::V6(addr) => types::IpAddress::Ipv6(addr.segments().into()),\n        }\n    }\n}\n\nimpl From<types::IpAddress> for std::net::IpAddr {\n    fn from(addr: types::IpAddress) -> Self {\n        match addr {\n            types::IpAddress::Ipv4(tuple) => <[u8; 4]>::from(tuple).into(),\n            types::IpAddress::Ipv6(tuple) => <[u16; 8]>::from(tuple).into(),\n        }\n    }\n}\n"
  },
  {
    "path": "src/component/compute.rs",
    "content": "//! Implementations for `fastly:compute` interfaces.\n\npub mod acl;\npub mod async_io;\npub mod backend;\npub mod cache;\npub mod compute_runtime;\npub mod config_store;\npub mod device_detection;\npub mod dictionary;\npub mod erl;\npub mod geo;\npub mod headers;\npub mod http_body;\npub mod http_cache;\npub mod http_downstream;\npub mod http_req;\npub mod http_resp;\npub mod http_types;\npub mod image_optimizer;\npub mod kv_store;\npub mod log;\npub mod purge;\npub mod secret_store;\npub mod security;\npub mod shielding;\npub mod types;\n\nmod error;\n"
  },
  {
    "path": "src/component/erl.rs",
    "content": "use {crate::component::bindings::fastly::compute::types, crate::sandbox::Sandbox};\n\n#[allow(clippy::too_many_arguments)]\npub(crate) fn check_rate(\n    _sandbox: &mut Sandbox,\n    _rc: &str,\n    _entry: String,\n    _delta: u32,\n    _window: u32,\n    _limit: u32,\n    _pb: &str,\n    _ttl: u32,\n) -> Result<bool, types::Error> {\n    Ok(false)\n}\n\npub(crate) fn ratecounter_increment(\n    _sandbox: &mut Sandbox,\n    _rc: &str,\n    _entry: String,\n    _delta: u32,\n) -> Result<(), types::Error> {\n    Ok(())\n}\n\npub(crate) fn ratecounter_lookup_rate(\n    _sandbox: &mut Sandbox,\n    _rc: &str,\n    _entry: String,\n    _window: u32,\n) -> Result<u32, types::Error> {\n    Ok(0)\n}\n\npub(crate) fn ratecounter_lookup_count(\n    _sandbox: &mut Sandbox,\n    _rc: &str,\n    _entry: String,\n    _duration: u32,\n) -> Result<u32, types::Error> {\n    Ok(0)\n}\n\npub(crate) fn penaltybox_add(\n    _sandbox: &mut Sandbox,\n    _pb: &str,\n    _entry: String,\n    _ttl: u32,\n) -> Result<(), types::Error> {\n    Ok(())\n}\n\npub(crate) fn penaltybox_has(\n    _sandbox: &mut Sandbox,\n    _pb: &str,\n    _entry: String,\n) -> Result<bool, types::Error> {\n    Ok(false)\n}\n"
  },
  {
    "path": "src/component/handles.rs",
    "content": "use crate::wiggle_abi::types::*;\n\n/// Macro which provides the common implementation of a resource type.\nmacro_rules! resource_impl {\n    ($entity:ident, $bindings_type:path) => {\n        // Add convenient functions for converting to and from `Resource`s.\n        impl From<$crate::component::component::Resource<$bindings_type>> for $entity {\n            fn from(resource: $crate::component::component::Resource<$bindings_type>) -> Self {\n                Self::from(resource.rep())\n            }\n        }\n\n        impl From<$entity> for $crate::component::component::Resource<$bindings_type> {\n            fn from(entity: $entity) -> $crate::component::component::Resource<$bindings_type> {\n                crate::component::component::Resource::new_own(entity.into())\n            }\n        }\n    };\n}\n\nresource_impl!(\n    ConfigStoreHandle,\n    crate::component::bindings::fastly::compute::config_store::Store\n);\n\nresource_impl!(\n    DictionaryHandle,\n    crate::component::bindings::fastly::compute::dictionary::Dictionary\n);\n\nresource_impl!(\n    AsyncItemHandle,\n    crate::component::bindings::fastly::compute::async_io::Pollable\n);\n\nresource_impl!(\n    RequestHandle,\n    crate::component::bindings::fastly::compute::http_req::Request\n);\n\nresource_impl!(\n    ResponseHandle,\n    crate::component::bindings::fastly::compute::http_resp::Response\n);\n\nresource_impl!(\n    BodyHandle,\n    crate::component::bindings::fastly::compute::http_body::Body\n);\n\nresource_impl!(\n    PendingRequestHandle,\n    crate::component::bindings::fastly::compute::http_req::PendingRequest\n);\n\nresource_impl!(\n    EndpointHandle,\n    crate::component::bindings::fastly::compute::log::Endpoint\n);\n\nresource_impl!(\n    KvStoreLookupHandle,\n    crate::component::bindings::fastly::compute::kv_store::PendingLookup\n);\n\nresource_impl!(\n    KvStoreInsertHandle,\n    crate::component::bindings::fastly::compute::kv_store::PendingInsert\n);\n\nresource_impl!(\n    KvStoreDeleteHandle,\n    crate::component::bindings::fastly::compute::kv_store::PendingDelete\n);\n\nresource_impl!(\n    KvStoreListHandle,\n    crate::component::bindings::fastly::compute::kv_store::PendingList\n);\n\nresource_impl!(\n    KvStoreHandle,\n    crate::component::bindings::fastly::compute::kv_store::Store\n);\n\nresource_impl!(\n    SecretStoreHandle,\n    crate::component::bindings::fastly::compute::secret_store::Store\n);\n\nresource_impl!(\n    SecretHandle,\n    crate::component::bindings::fastly::compute::secret_store::Secret\n);\n\nresource_impl!(\n    CacheHandle,\n    crate::component::bindings::fastly::compute::cache::Entry\n);\n\nresource_impl!(\n    CacheBusyHandle,\n    crate::component::bindings::fastly::compute::cache::PendingEntry\n);\n\nresource_impl!(\n    CacheReplaceHandle,\n    crate::component::bindings::fastly::compute::cache::ReplaceEntry\n);\n\nresource_impl!(\n    HttpCacheHandle,\n    crate::component::bindings::fastly::compute::http_cache::Entry\n);\n\nresource_impl!(\n    AclHandle,\n    crate::component::bindings::fastly::compute::acl::Acl\n);\n\nresource_impl!(\n    RequestPromiseHandle,\n    crate::component::bindings::fastly::compute::http_downstream::PendingRequest\n);\n"
  },
  {
    "path": "src/component/http_req.rs",
    "content": "use {\n    crate::component::bindings::fastly::compute::{http_body, http_req, http_resp, types},\n    crate::handoff::{HandoffInfo, HandoffRequestInfo},\n    crate::{error::Error, sandbox::PeekableTask, sandbox::Sandbox, upstream},\n    http::request::Request,\n    wasmtime::component::Resource,\n};\n\npub(crate) fn redirect_to_websocket_proxy(\n    sandbox: &mut Sandbox,\n    req_handle: Resource<http_req::Request>,\n    backend_name: &str,\n) -> Result<(), types::Error> {\n    let request_info = match sandbox.request_parts(req_handle.into()) {\n        Ok(req) => Some(HandoffRequestInfo::from_parts(req)),\n        Err(_) => {\n            // This function can legitimately be called with an invalid request handle;\n            // this may happen when the guest uses a legacy API for pushpin redirection.\n            // The legacy behavior is equivalent to simply using None.\n            None\n        }\n    };\n\n    let redirect_info = HandoffInfo {\n        backend_name: backend_name.to_owned(),\n        request_info,\n    };\n\n    sandbox.redirect_downstream_to_backend(redirect_info)?;\n    Ok(())\n}\n\npub(crate) fn redirect_to_grip_proxy(\n    sandbox: &mut Sandbox,\n    req_handle: Resource<http_req::Request>,\n    backend_name: &str,\n) -> Result<(), types::Error> {\n    let request_info = match sandbox.request_parts(req_handle.into()) {\n        Ok(req) => Some(HandoffRequestInfo::from_parts(req)),\n        Err(_) => {\n            // This function can legitimately be called with an invalid request handle;\n            // this may happen when the guest uses a legacy API for pushpin redirection.\n            // The legacy behavior is equivalent to simply using None.\n            None\n        }\n    };\n\n    let redirect_info = HandoffInfo {\n        backend_name: backend_name.to_owned(),\n        request_info,\n    };\n\n    sandbox.redirect_downstream_to_pushpin(redirect_info)?;\n    Ok(())\n}\n\npub(crate) fn upgrade_websocket(\n    _sandbox: &mut Sandbox,\n    _backend: &str,\n) -> Result<(), types::Error> {\n    Err(Error::NotAvailable(\"WebSocket upgrade\").into())\n}\n\npub(crate) async fn send(\n    sandbox: &mut Sandbox,\n    h: Resource<http_req::Request>,\n    b: Resource<http_body::Body>,\n    backend_name: &str,\n) -> Result<http_resp::ResponseWithBody, http_req::ErrorWithDetail> {\n    // prepare the request\n    let req_parts = sandbox.take_request_parts(h.into()).unwrap();\n    let req_body = sandbox.take_body(b.into()).unwrap();\n    let req = Request::from_parts(req_parts, req_body);\n    let backend = sandbox\n        .backend(backend_name)\n        .ok_or_else(|| Error::UnknownBackend(backend_name.to_owned()))\n        .map_err(Into::into)\n        .map_err(types::Error::with_empty_detail)?;\n\n    // synchronously send the request\n    // This initial implementation ignores the error detail field\n    let tls_config = sandbox.tls_config();\n    let resp = upstream::send_request(req, backend, backend_name, tls_config)\n        .await\n        .map_err(Into::into)\n        .map_err(types::Error::with_empty_detail)?;\n    let (resp_handle, body_handle) = sandbox.insert_response(resp);\n    Ok((resp_handle.into(), body_handle.into()))\n}\n\npub(crate) async fn send_uncached(\n    sandbox: &mut Sandbox,\n    h: Resource<http_req::Request>,\n    b: Resource<http_body::Body>,\n    backend_name: &str,\n) -> Result<http_resp::ResponseWithBody, http_req::ErrorWithDetail> {\n    // This initial implementation ignores the error detail field\n    send(sandbox, h, b, backend_name).await\n}\n\npub(crate) async fn send_async(\n    sandbox: &mut Sandbox,\n    h: Resource<http_req::Request>,\n    b: Resource<http_body::Body>,\n    backend_name: &str,\n) -> Result<Resource<http_req::PendingRequest>, types::Error> {\n    // prepare the request\n    let req_parts = sandbox.take_request_parts(h.into())?;\n    let req_body = sandbox.take_body(b.into())?;\n    let req = Request::from_parts(req_parts, req_body);\n    let backend = sandbox\n        .backend(backend_name)\n        .ok_or(types::Error::GenericError)?;\n\n    // asynchronously send the request\n    let tls_config = sandbox.tls_config();\n    let task = PeekableTask::spawn(upstream::send_request(\n        req,\n        backend,\n        backend_name,\n        tls_config,\n    ))\n    .await;\n\n    // return a handle to the pending request\n    Ok(sandbox.insert_pending_request(task).into())\n}\n\npub(crate) async fn send_async_uncached(\n    sandbox: &mut Sandbox,\n    h: Resource<http_req::Request>,\n    b: Resource<http_body::Body>,\n    backend_name: &str,\n) -> Result<Resource<http_req::PendingRequest>, types::Error> {\n    send_async(sandbox, h, b, backend_name).await\n}\n\npub(crate) async fn send_async_uncached_streaming(\n    sandbox: &mut Sandbox,\n    h: Resource<http_req::Request>,\n    b: Resource<http_body::Body>,\n    backend_name: &str,\n) -> Result<Resource<http_req::PendingRequest>, types::Error> {\n    send_async_streaming(sandbox, h, b, backend_name).await\n}\n\npub(crate) async fn send_async_streaming(\n    sandbox: &mut Sandbox,\n    h: Resource<http_req::Request>,\n    b: Resource<http_body::Body>,\n    backend_name: &str,\n) -> Result<Resource<http_req::PendingRequest>, types::Error> {\n    // prepare the request\n    let req_parts = sandbox.take_request_parts(h.into())?;\n    let req_body = sandbox.begin_streaming(b.into())?;\n    let req = Request::from_parts(req_parts, req_body);\n    let backend = sandbox\n        .backend(backend_name)\n        .ok_or(types::Error::GenericError)?;\n\n    // asynchronously send the request\n    let tls_config = sandbox.tls_config();\n    let task = PeekableTask::spawn(upstream::send_request(\n        req,\n        backend,\n        backend_name,\n        tls_config,\n    ))\n    .await;\n\n    // return a handle to the pending request\n    Ok(sandbox.insert_pending_request(task).into())\n}\n"
  },
  {
    "path": "src/component/image_optimizer.rs",
    "content": "use crate::component::bindings::fastly::compute::{\n    http_body, http_req, http_resp, image_optimizer, types,\n};\nuse crate::sandbox::Sandbox;\nuse wasmtime::component::Resource;\n\npub(crate) fn transform_image_optimizer_request(\n    _sandbox: &mut Sandbox,\n    _origin_image_request: Resource<http_req::Request>,\n    _origin_image_request_body: Option<Resource<http_body::Body>>,\n    _origin_image_request_backend: &str,\n    _io_transform_config: image_optimizer::ImageOptimizerTransformOptions,\n) -> Result<http_resp::ResponseWithBody, types::Error> {\n    Err(types::Error::Unsupported)\n}\n"
  },
  {
    "path": "src/component/shielding.rs",
    "content": "use crate::component::bindings::fastly::compute::{shielding, types};\nuse crate::config::Backend;\nuse crate::error::Error;\nuse crate::sandbox::Sandbox;\nuse http::Uri;\nuse std::str::FromStr;\nuse wasmtime::component::{Resource, ResourceTable};\n\npub(crate) fn backend_for_shield(\n    sandbox: &mut Sandbox,\n    _table: &mut ResourceTable,\n    name: &str,\n    _options: Option<Resource<shielding::ShieldBackendOptions>>,\n    max_len: u64,\n) -> Result<String, types::Error> {\n    let shield_uri = name;\n\n    let Ok(uri) = Uri::from_str(shield_uri) else {\n        return Err(Error::InvalidArgument.into());\n    };\n\n    let new_name = format!(\"******{uri}*****\");\n    let new_backend = Backend {\n        uri,\n        override_host: None,\n        cert_host: None,\n        use_sni: false,\n        grpc: false,\n        client_cert: None,\n        ca_certs: Vec::new(),\n        health: crate::config::BackendHealth::Unknown,\n    };\n\n    if !sandbox.add_backend(&new_name, new_backend) {\n        return Err(Error::BackendNameRegistryError(new_name).into());\n    }\n\n    let target_len = new_name.len() as u64;\n\n    if target_len > max_len {\n        return Err(Error::BufferLengthError {\n            buf: \"shielding_backend\",\n            len: \"name.len()\",\n        }\n        .into());\n    }\n\n    Ok(new_name)\n}\n"
  },
  {
    "path": "src/component/wasi.rs",
    "content": "//! Trivial wrappers around wasmtime-wasi implementations.\n//!\n//! This exists because Wasmtime's bindgen system doesn't gracefully handle\n//! component implementations that are split between multiple crates.\n\nuse crate::component::bindings::wasi;\nuse crate::linking::ComponentCtx;\nuse wasmtime::component::Resource;\nuse wasmtime_wasi::cli::WasiCliView;\nuse wasmtime_wasi::clocks::WasiClocksView;\nuse wasmtime_wasi_io::IoView;\n\nimpl wasi::clocks::wall_clock::Host for ComponentCtx {\n    fn now(&mut self) -> wasi::clocks::wall_clock::Datetime {\n        let x =\n            wasmtime_wasi::p2::bindings::sync::clocks::wall_clock::Host::now(&mut self.clocks())\n                .unwrap();\n        wasi::clocks::wall_clock::Datetime {\n            seconds: x.seconds,\n            nanoseconds: x.nanoseconds,\n        }\n    }\n\n    fn resolution(&mut self) -> wasi::clocks::wall_clock::Datetime {\n        let x = wasmtime_wasi::p2::bindings::sync::clocks::wall_clock::Host::resolution(\n            &mut self.clocks(),\n        )\n        .unwrap();\n        wasi::clocks::wall_clock::Datetime {\n            seconds: x.seconds,\n            nanoseconds: x.nanoseconds,\n        }\n    }\n}\n\nimpl wasi::clocks::monotonic_clock::Host for ComponentCtx {\n    fn now(&mut self) -> wasi::clocks::monotonic_clock::Instant {\n        wasmtime_wasi::p2::bindings::sync::clocks::monotonic_clock::Host::now(&mut self.clocks())\n            .unwrap()\n    }\n\n    fn resolution(&mut self) -> wasi::clocks::monotonic_clock::Duration {\n        wasmtime_wasi::p2::bindings::sync::clocks::monotonic_clock::Host::resolution(\n            &mut self.clocks(),\n        )\n        .unwrap()\n    }\n\n    fn subscribe_instant(\n        &mut self,\n        when: wasi::clocks::monotonic_clock::Instant,\n    ) -> Resource<wasi::clocks::monotonic_clock::Pollable> {\n        wasmtime_wasi::p2::bindings::sync::clocks::monotonic_clock::Host::subscribe_instant(\n            &mut self.clocks(),\n            when,\n        )\n        .unwrap()\n    }\n\n    fn subscribe_duration(\n        &mut self,\n        when: wasi::clocks::monotonic_clock::Duration,\n    ) -> Resource<wasi::clocks::monotonic_clock::Pollable> {\n        wasmtime_wasi::p2::bindings::sync::clocks::monotonic_clock::Host::subscribe_duration(\n            &mut self.clocks(),\n            when,\n        )\n        .unwrap()\n    }\n}\n\nimpl wasi::io::poll::Host for ComponentCtx {\n    async fn poll(&mut self, pollables: Vec<Resource<wasi::io::poll::Pollable>>) -> Vec<u32> {\n        wasmtime_wasi::p2::bindings::io::poll::Host::poll(&mut self.table(), pollables)\n            .await\n            .unwrap()\n    }\n}\n\nimpl wasi::io::poll::HostPollable for ComponentCtx {\n    fn ready(&mut self, pollable: Resource<wasi::io::poll::Pollable>) -> bool {\n        wasmtime_wasi::p2::bindings::sync::io::poll::HostPollable::ready(\n            &mut self.table(),\n            pollable,\n        )\n        .unwrap()\n    }\n    async fn block(&mut self, pollable: Resource<wasi::io::poll::Pollable>) {\n        wasmtime_wasi::p2::bindings::io::poll::HostPollable::block(&mut self.table(), pollable)\n            .await\n            .unwrap()\n    }\n    fn drop(&mut self, pollable: Resource<wasi::io::poll::Pollable>) -> wasmtime::Result<()> {\n        wasmtime_wasi::p2::bindings::sync::io::poll::HostPollable::drop(&mut self.table(), pollable)\n    }\n}\n\nimpl wasi::io::error::Host for ComponentCtx {}\n\nimpl wasi::io::error::HostError for ComponentCtx {\n    fn to_debug_string(&mut self, self_: Resource<wasi::io::error::Error>) -> String {\n        wasmtime_wasi::p2::bindings::sync::io::error::HostError::to_debug_string(\n            &mut self.table(),\n            self_,\n        )\n        .unwrap()\n    }\n\n    fn drop(&mut self, rep: Resource<wasi::io::error::Error>) -> wasmtime::Result<()> {\n        wasmtime_wasi::p2::bindings::sync::io::error::HostError::drop(&mut self.table(), rep)\n    }\n}\n\nimpl wasi::io::streams::Host for ComponentCtx {\n    fn convert_stream_error(\n        &mut self,\n        err: wasmtime_wasi::p2::StreamError,\n    ) -> wasmtime::Result<wasi::io::streams::StreamError> {\n        match err {\n            wasmtime_wasi::p2::StreamError::Closed => Ok(wasi::io::streams::StreamError::Closed),\n            wasmtime_wasi::p2::StreamError::LastOperationFailed(e) => Ok(\n                wasi::io::streams::StreamError::LastOperationFailed(self.wasi_table.push(e)?),\n            ),\n            wasmtime_wasi::p2::StreamError::Trap(e) => Err(e),\n        }\n    }\n}\n\nimpl wasi::io::streams::HostOutputStream for ComponentCtx {\n    fn write(\n        &mut self,\n        stream: Resource<wasi::io::streams::OutputStream>,\n        contents: Vec<u8>,\n    ) -> Result<(), wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostOutputStream::write(\n            &mut self.table(),\n            stream,\n            contents,\n        )\n    }\n\n    async fn blocking_write_and_flush(\n        &mut self,\n        stream: Resource<wasi::io::streams::OutputStream>,\n        contents: Vec<u8>,\n    ) -> Result<(), wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::io::streams::HostOutputStream::blocking_write_and_flush(\n            &mut self.table(),\n            stream,\n            contents,\n        )\n        .await\n    }\n\n    fn flush(\n        &mut self,\n        stream: Resource<wasi::io::streams::OutputStream>,\n    ) -> Result<(), wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostOutputStream::flush(\n            &mut self.table(),\n            stream,\n        )\n    }\n\n    async fn blocking_flush(\n        &mut self,\n        stream: Resource<wasi::io::streams::OutputStream>,\n    ) -> Result<(), wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::io::streams::HostOutputStream::blocking_flush(\n            &mut self.table(),\n            stream,\n        )\n        .await\n    }\n\n    fn check_write(\n        &mut self,\n        stream: Resource<wasi::io::streams::OutputStream>,\n    ) -> Result<u64, wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostOutputStream::check_write(\n            &mut self.table(),\n            stream,\n        )\n    }\n\n    fn subscribe(\n        &mut self,\n        self_: Resource<wasi::io::streams::OutputStream>,\n    ) -> Resource<wasi::io::streams::Pollable> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostOutputStream::subscribe(\n            &mut self.table(),\n            self_,\n        )\n        .unwrap()\n    }\n\n    fn write_zeroes(\n        &mut self,\n        self_: Resource<wasi::io::streams::OutputStream>,\n        len: u64,\n    ) -> Result<(), wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostOutputStream::write_zeroes(\n            &mut self.table(),\n            self_,\n            len,\n        )\n    }\n\n    async fn blocking_write_zeroes_and_flush(\n        &mut self,\n        self_: Resource<wasi::io::streams::OutputStream>,\n        len: u64,\n    ) -> Result<(), wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::io::streams::HostOutputStream::blocking_write_zeroes_and_flush(\n            &mut self.table(),\n            self_,\n            len,\n        )\n        .await\n    }\n\n    fn splice(\n        &mut self,\n        self_: Resource<wasi::io::streams::OutputStream>,\n        src: Resource<wasi::io::streams::InputStream>,\n        len: u64,\n    ) -> Result<u64, wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostOutputStream::splice(\n            &mut self.table(),\n            self_,\n            src,\n            len,\n        )\n    }\n\n    async fn blocking_splice(\n        &mut self,\n        self_: Resource<wasi::io::streams::OutputStream>,\n        src: Resource<wasi::io::streams::InputStream>,\n        len: u64,\n    ) -> Result<u64, wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::io::streams::HostOutputStream::blocking_splice(\n            &mut self.table(),\n            self_,\n            src,\n            len,\n        )\n        .await\n    }\n\n    fn drop(&mut self, rep: Resource<wasi::io::streams::OutputStream>) -> wasmtime::Result<()> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostOutputStream::drop(\n            &mut self.table(),\n            rep,\n        )\n    }\n}\n\nimpl wasi::io::streams::HostInputStream for ComponentCtx {\n    fn read(\n        &mut self,\n        self_: Resource<wasi::io::streams::InputStream>,\n        len: u64,\n    ) -> Result<Vec<u8>, wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostInputStream::read(\n            &mut self.table(),\n            self_,\n            len,\n        )\n    }\n\n    async fn blocking_read(\n        &mut self,\n        self_: Resource<wasi::io::streams::InputStream>,\n        len: u64,\n    ) -> Result<Vec<u8>, wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::io::streams::HostInputStream::blocking_read(\n            &mut self.table(),\n            self_,\n            len,\n        )\n        .await\n    }\n\n    fn skip(\n        &mut self,\n        self_: Resource<wasi::io::streams::InputStream>,\n        len: u64,\n    ) -> Result<u64, wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostInputStream::skip(\n            &mut self.table(),\n            self_,\n            len,\n        )\n    }\n\n    async fn blocking_skip(\n        &mut self,\n        self_: Resource<wasi::io::streams::InputStream>,\n        len: u64,\n    ) -> Result<u64, wasmtime_wasi::p2::StreamError> {\n        wasmtime_wasi::p2::bindings::io::streams::HostInputStream::blocking_skip(\n            &mut self.table(),\n            self_,\n            len,\n        )\n        .await\n    }\n\n    fn subscribe(\n        &mut self,\n        self_: Resource<wasi::io::streams::InputStream>,\n    ) -> Resource<wasi::io::streams::Pollable> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostInputStream::subscribe(\n            &mut self.table(),\n            self_,\n        )\n        .unwrap()\n    }\n\n    fn drop(&mut self, rep: Resource<wasi::io::streams::InputStream>) -> wasmtime::Result<()> {\n        wasmtime_wasi::p2::bindings::sync::io::streams::HostInputStream::drop(\n            &mut self.table(),\n            rep,\n        )\n    }\n}\n\nimpl wasi::random::random::Host for ComponentCtx {\n    fn get_random_bytes(&mut self, len: u64) -> Vec<u8> {\n        wasmtime_wasi::p2::bindings::random::random::Host::get_random_bytes(\n            &mut self.wasi_random,\n            len,\n        )\n        .unwrap()\n    }\n\n    fn get_random_u64(&mut self) -> u64 {\n        wasmtime_wasi::p2::bindings::random::random::Host::get_random_u64(&mut self.wasi_random)\n            .unwrap()\n    }\n}\n\nimpl wasi::random::insecure::Host for ComponentCtx {\n    fn get_insecure_random_bytes(&mut self, len: u64) -> Vec<u8> {\n        wasmtime_wasi::p2::bindings::random::insecure::Host::get_insecure_random_bytes(\n            &mut self.wasi_random,\n            len,\n        )\n        .unwrap()\n    }\n\n    fn get_insecure_random_u64(&mut self) -> u64 {\n        wasmtime_wasi::p2::bindings::random::insecure::Host::get_insecure_random_u64(\n            &mut self.wasi_random,\n        )\n        .unwrap()\n    }\n}\n\nimpl wasi::random::insecure_seed::Host for ComponentCtx {\n    fn insecure_seed(&mut self) -> (u64, u64) {\n        wasmtime_wasi::p2::bindings::random::insecure_seed::Host::insecure_seed(\n            &mut self.wasi_random,\n        )\n        .unwrap()\n    }\n}\n\nimpl wasi::cli::environment::Host for ComponentCtx {\n    fn get_environment(&mut self) -> Vec<(String, String)> {\n        wasmtime_wasi::p2::bindings::cli::environment::Host::get_environment(&mut self.cli())\n            .unwrap()\n    }\n\n    fn get_arguments(&mut self) -> Vec<String> {\n        wasmtime_wasi::p2::bindings::cli::environment::Host::get_arguments(&mut self.cli()).unwrap()\n    }\n\n    fn initial_cwd(&mut self) -> Option<String> {\n        wasmtime_wasi::p2::bindings::cli::environment::Host::initial_cwd(&mut self.cli()).unwrap()\n    }\n}\n\nimpl wasi::cli::stdin::Host for ComponentCtx {\n    fn get_stdin(&mut self) -> Resource<wasi::cli::stdin::InputStream> {\n        wasmtime_wasi::p2::bindings::cli::stdin::Host::get_stdin(&mut self.cli()).unwrap()\n    }\n}\n\nimpl wasi::cli::stdout::Host for ComponentCtx {\n    fn get_stdout(&mut self) -> Resource<wasi::cli::stdout::OutputStream> {\n        wasmtime_wasi::p2::bindings::cli::stdout::Host::get_stdout(&mut self.cli()).unwrap()\n    }\n}\n\nimpl wasi::cli::stderr::Host for ComponentCtx {\n    fn get_stderr(&mut self) -> Resource<wasi::cli::stderr::OutputStream> {\n        wasmtime_wasi::p2::bindings::cli::stderr::Host::get_stderr(&mut self.cli()).unwrap()\n    }\n}\n\nimpl wasi::cli::exit::Host for ComponentCtx {\n    fn exit(&mut self, status: Result<(), ()>) -> wasmtime::Result<()> {\n        wasmtime_wasi::p2::bindings::cli::exit::Host::exit(&mut self.cli(), status)\n    }\n\n    fn exit_with_code(&mut self, status_code: u8) -> wasmtime::Result<()> {\n        wasmtime_wasi::p2::bindings::cli::exit::Host::exit_with_code(&mut self.cli(), status_code)\n    }\n}\n"
  },
  {
    "path": "src/component.rs",
    "content": "use {\n    crate::linking::ComponentCtx,\n    wasmtime::component::{self, HasSelf},\n};\n\npub(crate) mod bindings {\n    wasmtime::component::bindgen!({\n        path: \"wasm_abi/wit\",\n        world: \"fastly:adapter/adapter-service\",\n        imports: {\n            default: tracing,\n\n            \"fastly:compute/backend.[constructor]dynamic-backend-options\": tracing | trappable,\n            \"fastly:compute/shielding.[constructor]shield-backend-options\": tracing | trappable,\n            \"fastly:compute/cache.[constructor]extra-lookup-options\": tracing | trappable,\n            \"fastly:compute/cache.[constructor]extra-replace-options\": tracing | trappable,\n            \"fastly:compute/cache.[constructor]extra-write-options\": tracing | trappable,\n\n            // The trap-test test depends on being able to induce an artificial\n            // trap in `get-header-values`.\n            \"fastly:compute/http-resp.[method]response.get-header-values\": tracing | trappable,\n\n            \"fastly:compute/http-body.append\": async | tracing,\n            \"fastly:compute/kv-store.await-delete\": async | tracing,\n            \"fastly:compute/cache.await-entry\": async | tracing,\n            \"fastly:compute/kv-store.await-insert\": async | tracing,\n            \"fastly:compute/kv-store.await-list\": async | tracing,\n            \"fastly:compute/kv-store.await-lookup\": async | tracing | trappable,\n            \"fastly:compute/http-downstream.await-request\": async | tracing,\n            \"fastly:compute/http-req.await-response\": async | tracing,\n            \"fastly:compute/cache.close-entry\": async | tracing,\n            \"fastly:compute/cache.close-replace-entry\": async | tracing,\n            \"fastly:compute/cache.insert\": async | tracing,\n            \"fastly:compute/cache.[static]replace-entry.replace\": async | tracing,\n            \"fastly:compute/cache.[method]replace-entry.get-age-ns\": async | tracing,\n            \"fastly:compute/cache.[method]replace-entry.get-body\": async | tracing,\n            \"fastly:compute/cache.[method]replace-entry.get-hits\": async | tracing,\n            \"fastly:compute/cache.[method]replace-entry.get-length\": async | tracing,\n            \"fastly:compute/cache.[method]replace-entry.get-max-age-ns\": async | tracing,\n            \"fastly:compute/cache.[method]replace-entry.get-stale-while-revalidate-ns\": async | tracing,\n            \"fastly:compute/cache.[method]replace-entry.get-state\": async | tracing,\n            \"fastly:compute/cache.[method]replace-entry.get-user-metadata\": async | tracing,\n            \"fastly:compute/cache.replace-insert\": async | tracing,\n            \"fastly:compute/cache.[method]entry.get-age-ns\": async | tracing,\n            \"fastly:compute/cache.[method]entry.get-body\": async | tracing,\n            \"fastly:compute/cache.[method]entry.get-hits\": async | tracing,\n            \"fastly:compute/cache.[method]entry.get-length\": async | tracing,\n            \"fastly:compute/cache.[method]entry.get-max-age-ns\": async | tracing,\n            \"fastly:compute/cache.[method]entry.get-stale-while-revalidate-ns\": async | tracing,\n            \"fastly:compute/cache.[method]entry.get-state\": async | tracing,\n            \"fastly:compute/cache.[method]entry.get-user-metadata\": async | tracing,\n            \"fastly:compute/cache.[method]entry.transaction-cancel\": async | tracing,\n            \"fastly:compute/cache.[method]entry.transaction-insert\": async | tracing,\n            \"fastly:compute/cache.[method]entry.transaction-insert-and-stream-back\": async | tracing,\n            \"fastly:compute/cache.[method]entry.transaction-update\": async | tracing,\n            \"fastly:compute/kv-store.[method]store.delete\": async | tracing,\n            \"fastly:compute/kv-store.[method]store.delete-async\": async | tracing,\n            \"fastly:compute/kv-store.[method]store.insert\": async | tracing,\n            \"fastly:compute/kv-store.[method]store.insert-async\": async | tracing,\n            \"fastly:compute/kv-store.[method]store.list\": async | tracing,\n            \"fastly:compute/kv-store.[method]store.list-async\": async | tracing,\n            \"fastly:compute/kv-store.[method]store.lookup\": async | tracing,\n            \"fastly:compute/kv-store.[method]store.lookup-async\": async | tracing,\n            \"fastly:compute/http-downstream.next-request\": async | tracing,\n            \"fastly:compute/http-body.read\": async | tracing,\n            \"fastly:compute/backend.register-dynamic-backend\": async | tracing,\n            \"fastly:compute/async-io.select\": async | tracing | trappable,\n            \"fastly:compute/async-io.select-with-timeout\": async | tracing,\n            \"fastly:compute/http-req.send\": async | tracing,\n            \"fastly:compute/http-req.send-async\": async | tracing,\n            \"fastly:compute/http-req.send-async-streaming\": async | tracing,\n            \"fastly:compute/http-req.send-async-uncached\": async | tracing,\n            \"fastly:compute/http-req.send-async-uncached-streaming\": async | tracing,\n            \"fastly:compute/http-req.send-uncached\": async | tracing,\n            \"fastly:compute/cache.[static]entry.lookup\": async | tracing,\n            \"fastly:compute/cache.[static]entry.transaction-lookup\": async | tracing,\n            \"fastly:compute/cache.[static]entry.transaction-lookup-async\": async | tracing,\n            \"fastly:compute/http-body.write\": async | tracing,\n            \"fastly:compute/http-body.write-front\": async | tracing,\n\n            // Match the `wasmtime-wasi` crate's bindings.\n            \"wasi:io/streams.[method]output-stream.write\": tracing | trappable,\n            \"wasi:io/streams.[method]output-stream.blocking-write-and-flush\": async | tracing | trappable,\n            \"wasi:io/streams.[method]output-stream.flush\": tracing | trappable,\n            \"wasi:io/streams.[method]output-stream.blocking-flush\": async | tracing | trappable,\n            \"wasi:io/streams.[method]output-stream.check-write\": tracing | trappable,\n            \"wasi:io/streams.[method]output-stream.write-zeroes\": tracing | trappable,\n            \"wasi:io/streams.[method]output-stream.blocking-write-zeroes-and-flush\": async | tracing | trappable,\n            \"wasi:io/streams.[method]output-stream.splice\": tracing | trappable,\n            \"wasi:io/streams.[method]output-stream.blocking-splice\": async | tracing | trappable,\n            \"wasi:io/streams.[method]input-stream.read\": tracing | trappable,\n            \"wasi:io/streams.[method]input-stream.blocking-read\": async | tracing | trappable,\n            \"wasi:io/streams.[method]input-stream.skip\": tracing | trappable,\n            \"wasi:io/streams.[method]input-stream.blocking-skip\": async | tracing | trappable,\n            \"wasi:io/poll.poll\": async | tracing,\n            \"wasi:io/poll.[method]pollable.block\": async | tracing,\n            \"wasi:cli/exit.exit\": tracing | trappable,\n            \"wasi:cli/exit.exit-with-code\": tracing | trappable,\n        },\n        exports: {\n            default: tracing,\n            \"fastly:compute/http-incoming.handle\": async | tracing,\n        },\n        with: {\n            // Match the `wasmtime-wasi` crate's bindings.\n            \"wasi:io/poll.pollable\": wasmtime_wasi::p2::DynPollable,\n            \"wasi:io/streams.input-stream\": wasmtime_wasi::p2::DynInputStream,\n            \"wasi:io/streams.output-stream\": wasmtime_wasi::p2::DynOutputStream,\n            \"wasi:io/error.error\": wasmtime_wasi_io::streams::Error,\n\n            \"fastly:compute/kv-store.entry\": super::compute::kv_store::Entry,\n            \"fastly:compute/backend.dynamic-backend-options\": super::compute::backend::BackendBuilder,\n            \"fastly:compute/backend.backend\": String,\n            \"fastly:compute/erl.rate-counter\": String,\n            \"fastly:compute/erl.penalty-box\": String,\n            \"fastly:compute/http-downstream.extra-bot-category\": super::compute::http_downstream::ExtraBotCategory,\n        },\n\n        trappable_error_type: {\n            // Match the wasmtime-wasi crate's bindings.\n            \"wasi:io/streams.stream-error\" => wasmtime_wasi::p2::StreamError,\n        },\n    });\n}\n\npub fn link_host_functions(linker: &mut component::Linker<ComponentCtx>) -> anyhow::Result<()> {\n    let options = bindings::LinkOptions::default();\n\n    // Add the Viceroy host implementations.\n    bindings::AdapterService::add_to_linker::<_, HasSelf<_>>(linker, &options, |x| x)?;\n\n    Ok(())\n}\n\npub mod adapter;\npub mod backend;\npub mod compute;\npub mod erl;\npub mod handles;\npub mod http_req;\npub mod image_optimizer;\npub mod shielding;\npub mod wasi;\n"
  },
  {
    "path": "src/config/acl.rs",
    "content": "use crate::acl;\n\n#[derive(Clone, Debug, Default)]\npub struct AclConfig(pub(crate) acl::Acls);\n\nmod deserialization {\n    use {\n        super::AclConfig,\n        crate::acl,\n        crate::error::{AclConfigError, FastlyConfigError},\n        std::path::PathBuf,\n        std::{convert::TryFrom, fs},\n        toml::value::Table,\n    };\n\n    impl TryFrom<Table> for AclConfig {\n        type Error = FastlyConfigError;\n        fn try_from(toml: Table) -> Result<Self, Self::Error> {\n            let mut acls = acl::Acls::new();\n\n            for (name, value) in toml.iter() {\n                // Here we allow each table entry to be either a:\n                //  - string: path to JSON file\n                //  - table: must have a 'file' entry, which is the path to JSON file\n                let path = if let Some(path) = value.as_str() {\n                    path\n                } else if let Some(tbl) = value.as_table() {\n                    tbl.get(\"file\")\n                        .ok_or(FastlyConfigError::InvalidAclDefinition {\n                            name: name.to_string(),\n                            err: AclConfigError::MissingFile,\n                        })?\n                        .as_str()\n                        .ok_or(FastlyConfigError::InvalidAclDefinition {\n                            name: name.to_string(),\n                            err: AclConfigError::MissingFile,\n                        })?\n                } else {\n                    return Err(FastlyConfigError::InvalidAclDefinition {\n                        name: name.to_string(),\n                        err: AclConfigError::InvalidType,\n                    });\n                };\n\n                let acl: acl::Acl = {\n                    let path = PathBuf::from(path);\n                    let fd = fs::File::open(path).map_err(|err| {\n                        FastlyConfigError::InvalidAclDefinition {\n                            name: name.to_string(),\n                            err: AclConfigError::IoError(err),\n                        }\n                    })?;\n                    serde_json::from_reader(fd).map_err(|err| {\n                        FastlyConfigError::InvalidAclDefinition {\n                            name: name.to_string(),\n                            err: AclConfigError::JsonError(err),\n                        }\n                    })?\n                };\n\n                acls.insert(name.to_string(), acl);\n            }\n\n            Ok(Self(acls))\n        }\n    }\n}\n"
  },
  {
    "path": "src/config/backends/client_cert_info.rs",
    "content": "use rustls::{Certificate, PrivateKey};\nuse std::fmt;\nuse std::io::{BufReader, Cursor};\n\n#[derive(Clone, PartialEq)]\npub struct ClientCertInfo {\n    certificates: Vec<Certificate>,\n    key: PrivateKey,\n}\n\nimpl fmt::Debug for ClientCertInfo {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        self.certs().fmt(f)\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum ClientCertError {\n    #[error(\"Certificate/key read error: {0}\")]\n    CertificateRead(#[from] std::io::Error),\n    #[error(\"No keys found for client certificate\")]\n    NoKeysFound,\n    #[error(\"Too many keys found for client certificate (found {0})\")]\n    TooManyKeys(usize),\n    #[error(\"Expected a TOML table, found something else\")]\n    InvalidToml,\n    #[error(\"No certificates found in client cert definition\")]\n    NoCertsFound,\n    #[error(\"Invalid certificate data: {0}\")]\n    InvalidCertificateData(String),\n    #[error(\"Expected a string value for key {0}, got something else\")]\n    InvalidTomlData(&'static str),\n}\n\nimpl ClientCertInfo {\n    pub fn new(certificate_bytes: &[u8], certificate_key: &[u8]) -> Result<Self, ClientCertError> {\n        let mut certificate_bytes_reader = Cursor::new(certificate_bytes);\n        let mut key_bytes_reader = Cursor::new(certificate_key);\n        let cert_info = rustls_pemfile::read_all(&mut certificate_bytes_reader)?;\n        let key_info = rustls_pemfile::read_all(&mut key_bytes_reader)?;\n\n        let mut certificates = Vec::new();\n        let mut keys = Vec::new();\n\n        for item in cert_info.into_iter().chain(key_info) {\n            match item {\n                rustls_pemfile::Item::X509Certificate(x) => {\n                    // Basic validation of certificate data\n                    if x.is_empty() {\n                        return Err(ClientCertError::InvalidCertificateData(\n                            \"Empty certificate data\".to_string(),\n                        ));\n                    }\n                    certificates.push(Certificate(x))\n                }\n                rustls_pemfile::Item::RSAKey(x) => keys.push(PrivateKey(x)),\n                rustls_pemfile::Item::PKCS8Key(x) => keys.push(PrivateKey(x)),\n                rustls_pemfile::Item::ECKey(x) => keys.push(PrivateKey(x)),\n                _ => {}\n            }\n        }\n\n        // Ensure certificates were found\n        if certificates.is_empty() {\n            return Err(ClientCertError::NoCertsFound);\n        }\n\n        let key = if keys.is_empty() {\n            return Err(ClientCertError::NoKeysFound);\n        } else if keys.len() > 1 {\n            return Err(ClientCertError::TooManyKeys(keys.len()));\n        } else {\n            keys.remove(0)\n        };\n\n        Ok(ClientCertInfo { certificates, key })\n    }\n\n    pub fn certs(&self) -> Vec<Certificate> {\n        self.certificates.clone()\n    }\n\n    pub fn key(&self) -> PrivateKey {\n        self.key.clone()\n    }\n}\n\nfn inline_reader_for_field<'a>(\n    table: &'a toml::value::Table,\n    key: &'static str,\n) -> Result<Option<Cursor<&'a [u8]>>, ClientCertError> {\n    if let Some(base_field) = table.get(key) {\n        match base_field {\n            toml::Value::String(s) => Ok(Some(Cursor::new(s.as_bytes()))),\n            _ => Err(ClientCertError::InvalidTomlData(key)),\n        }\n    } else {\n        Ok(None)\n    }\n}\n\nfn file_reader_for_field(\n    table: &toml::value::Table,\n    key: &'static str,\n) -> Result<Option<BufReader<std::fs::File>>, ClientCertError> {\n    if let Some(base_field) = table.get(key) {\n        match base_field {\n            toml::Value::String(s) => {\n                let file = std::fs::File::open(s)?;\n                Ok(Some(BufReader::new(file)))\n            }\n            _ => Err(ClientCertError::InvalidTomlData(key)),\n        }\n    } else {\n        Ok(None)\n    }\n}\n\nfn read_certificates<R: std::io::BufRead>(\n    reader: &mut R,\n) -> Result<Vec<Certificate>, ClientCertError> {\n    rustls_pemfile::certs(reader)\n        .map(|mut x| x.drain(..).map(Certificate).collect::<Vec<Certificate>>())\n        .map_err(Into::into)\n}\n\nfn read_key<R: std::io::BufRead>(reader: &mut R) -> Result<PrivateKey, ClientCertError> {\n    for item in rustls_pemfile::read_all(reader)? {\n        match item {\n            rustls_pemfile::Item::RSAKey(x) => return Ok(PrivateKey(x)),\n            rustls_pemfile::Item::PKCS8Key(x) => return Ok(PrivateKey(x)),\n            rustls_pemfile::Item::ECKey(x) => return Ok(PrivateKey(x)),\n            _ => {}\n        }\n    }\n    Err(ClientCertError::NoKeysFound)\n}\n\nimpl TryFrom<toml::Value> for ClientCertInfo {\n    type Error = ClientCertError;\n\n    fn try_from(value: toml::Value) -> Result<Self, Self::Error> {\n        match value {\n            toml::Value::Table(t) => {\n                let mut found_cert = None;\n                let mut found_key = None;\n\n                if let Some(mut reader) = inline_reader_for_field(&t, \"certificate\")? {\n                    found_cert = Some(read_certificates(&mut reader)?);\n                }\n\n                if let Some(mut reader) = file_reader_for_field(&t, \"certificate_file\")? {\n                    found_cert = Some(read_certificates(&mut reader)?);\n                }\n\n                if let Some(mut reader) = inline_reader_for_field(&t, \"key\")? {\n                    found_key = Some(read_key(&mut reader)?);\n                }\n\n                if let Some(mut reader) = file_reader_for_field(&t, \"key_file\")? {\n                    found_key = Some(read_key(&mut reader)?);\n                }\n\n                match (found_cert, found_key) {\n                    (None, _) => Err(ClientCertError::NoCertsFound),\n                    (_, None) => Err(ClientCertError::NoKeysFound),\n                    (Some(certificates), Some(key)) => Ok(ClientCertInfo { certificates, key }),\n                }\n            }\n            _ => Err(ClientCertError::InvalidToml),\n        }\n    }\n}\n\n#[test]\nfn client_certs_parse() {\n    let basic = r#\"\ndescription = \"a test case\"\nlanguage = \"foul\"\nmanifest_version = 2\n\n[local_server]\n[local_server.backends]\n[local_server.backends.origin]\nurl = \"https://127.0.0.1:443\"\n\"#;\n\n    let basic_parsed = crate::config::FastlyConfig::from_str(basic).unwrap();\n    let basic_origin = basic_parsed.local_server.backends.0.get(\"origin\").unwrap();\n    assert!(basic_origin.client_cert.is_none());\n\n    let files = r#\"\ndescription = \"a test case\"\nlanguage = \"foul\"\nmanifest_version = 2\n\n[local_server]\n[local_server.backends]\n[local_server.backends.origin]\nurl = \"https://127.0.0.1:443\"\n[local_server.backends.origin.client_certificate]\ncertificate_file = \"test-fixtures/data/client.crt\"\nkey_file = \"test-fixtures/data/client.key\"\n\"#;\n\n    let files_parsed = crate::config::FastlyConfig::from_str(files).unwrap();\n    let files_origin = files_parsed.local_server.backends.0.get(\"origin\").unwrap();\n    assert!(files_origin.client_cert.is_some());\n\n    let inline = r#\"\ndescription = \"a test case\"\nlanguage = \"foul\"\nmanifest_version = 2\n\n[local_server]\n[local_server.backends]\n[local_server.backends.origin]\nurl = \"https://127.0.0.1:443\"\n[local_server.backends.origin.client_certificate]\nkey = \"\"\"\n-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAz27x1GpD46K6b9/3PNyZYKgTL9GBbpLAVF8Uebd34ftUfnWZ\n3ER+x6A1YbacHnL112diPPevyYkpXuiujwCeswYNrZHEtiRfAvrzBRhnhL8owQTx\njOcG4EOzR7Je556FTq8kNth5iHckORjmXiV9ZahbLv/zBFpkXpDeze62zd8y9chP\nNEqcrLZBOb4UoKXmOt1lIdeo23nysR4rC6XemWNSFcZv9zagUzliMeca3XN2RIUA\nFZv4o+gYPqqXQi+0a+OOq0jnKpawW+avn2UG7wzXGlLcVOvLe5BOCA1RfWtR8w03\nMFdvoBAesXJ4xGX1ROUzelldedmpqtvORdhmGQIDAQABAoIBAQCsbu6KhDehMDHJ\nNCWjK0I4zh78/iyZDVbiDBPKRpBag4GuifX329yD95LIgnNvAGOKxz8rrT4sy19f\nrQ8Ggx5pdVvDcExUmRF+Obvw/WN4PywSoBhn59iYbs7Gh+lKo0Tvrrns+bC1l0y+\nRguiMYn3CqeZ/1w1vyp2TflYuNqvcR4zMzJ4dN474CCLPIUX9OfK21Lbv/UMdguF\nRs/BuStucqaCzEtTLyZYlxQc1i8S8Uy2yukXR6TYWJOsWZj0KIgH/YI7ZgzvTIxL\nax4Hn4jIHPFSJ+vl2ehDKffkQQ0lzm60ASkjaJY6GsFoTQzsmuafpLIAoJbDbZR1\ntxPSFC+BAoGBAPbp6+LsXoEY+4RfStg4c/oLWmK3aTxzQzMY90vxnMm6SJTwTPAm\npO+Pp2UGyEGHV7hg3d+ItWpM9QGVmsjm+punIfc0W/0+AVUonjPLfv44dz7+geYt\n/oeMv4RTqCclROvtQTqV6hHn4E3Xg061miEe6OxYmqfZuLD2nv2VlsQRAoGBANcR\nGAqeClQtraTnu+yU9U+FJZfvSxs1yHr7XItCMtwxeU6+nipa+3pXNnKu0dKKekUG\nPCdUipXgggA6OUm2YFKPUhiXJUNoHCj45Tkv2NshGplW33U3NcCkDqL7vvZoBBfP\nOPxEVRVEIlwp/WzEambs9MjWoecEaOe7/3UCVumJAoGANlfVquQLCK7O7JtshZon\nLGlDQ2bKqptTtvNPuk87CssNHnqk9FYNBwy+8uVDPejjzZjEPGaCRxsY8XhT0NPF\nZGysdRP5CwuSj4OZDh1DngAffqXVQSvuUTcRD7a506PIP4TATnygP8ChBYDhTXl6\nqr961EnMABVTKN+eroE15YECgYEAv+YLyqV71+KuNx9i6lV7kcnfYnNtU8koqruQ\ntt2Jnjoy4JVrcaWfEGmzNp9Qr4lKUj6e/AUOZ29c8DEDnwcxaVliynhLEptZzSFQ\n/zb3S4d9QWdnmiJ6Pvrj6H+yxBDJ3ijT0xxxwrj547y/2QZlXpN+U5pX+ldP974i\n0dgVjukCgYEArxv0dO2VEguWLx5YijHiN72nDDI+skbfkQkvWQjA7x8R9Xx1SWUl\nWeyeaaV5rqfJZF1wBCK5VJndjbOGhPh6u/0mpeYw4Ty3+CKN2WoikQO27qYfMZW5\nvvT7m9ZR+gkm2TjZ+pZuilz2gqu/yMJKl8Fi8Q7dsb8eWedWQXjbUZg=\n-----END RSA PRIVATE KEY-----\n\"\"\"\ncertificate = \"\"\"\n-----BEGIN CERTIFICATE-----\nMIIDvjCCAqagAwIBAgIUOp97gvMlYdBYI/3yrpDeHbdx5RgwDQYJKoZIhvcNAQEL\nBQAwZDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk9yZWdvbjERMA8GA1UEBwwIUG9y\ndGxhbmQxEDAOBgNVBAoMB1ZpY2Vyb3kxHzAdBgkqhkiG9w0BCQEWEGF3aWNrQGZh\nc3RseS5jb20wHhcNMjMwNzI3MDAxOTU0WhcNMzMwNzI0MDAxOTU0WjB1MQswCQYD\nVQQGEwJVUzEPMA0GA1UECAwGT3JlZ29uMREwDwYDVQQHDAhQb3J0bGFuZDEQMA4G\nA1UECgwHVmljZXJveTEPMA0GA1UECwwGQ2xpZW50MR8wHQYJKoZIhvcNAQkBFhBh\nd2lja0BmYXN0bHkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nz27x1GpD46K6b9/3PNyZYKgTL9GBbpLAVF8Uebd34ftUfnWZ3ER+x6A1YbacHnL1\n12diPPevyYkpXuiujwCeswYNrZHEtiRfAvrzBRhnhL8owQTxjOcG4EOzR7Je556F\nTq8kNth5iHckORjmXiV9ZahbLv/zBFpkXpDeze62zd8y9chPNEqcrLZBOb4UoKXm\nOt1lIdeo23nysR4rC6XemWNSFcZv9zagUzliMeca3XN2RIUAFZv4o+gYPqqXQi+0\na+OOq0jnKpawW+avn2UG7wzXGlLcVOvLe5BOCA1RfWtR8w03MFdvoBAesXJ4xGX1\nROUzelldedmpqtvORdhmGQIDAQABo1cwVTAfBgNVHSMEGDAWgBRmDOh4T/Mmde3l\n8OZzn0Pe9btZfTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAaBgNVHREEEzARggls\nb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBAJ84GzmmqsmmtqXcmZIH\ni644p8wIc/DXPqb7zzAVm9FXpFgW3mN4xu1JYWu+rb1sge8uIm7Vt5Isd4CZ89XI\nF2Q2DS/rKMQmjgSDReWm9G+qZROwuhNDzK85e73Rw2EdX6cXtAGR1h3IdOTIv1FC\nUElFER31U8i4J9pxUZF/FTzlPEA1agqMsO6hQlj/A9B6TtzL7SSxCFBBaFbNCLMC\nD/WCrIoklNV5TwutYG80EYZhJlfUJPDQBphkcetDBI0L/KL/n20bg8OR/epGD5++\nqKIulxf9iUR5QHm2fWKdTLOuADmV+lc925gIqGhFhjVvpNPOcdckecQUp3vCNu2/\nHrM=\n-----END CERTIFICATE-----\n\"\"\"\n\"#;\n\n    let inline_parsed = crate::config::FastlyConfig::from_str(inline).unwrap();\n    let inline_origin = inline_parsed.local_server.backends.0.get(\"origin\").unwrap();\n    assert!(inline_origin.client_cert.is_some());\n\n    assert_eq!(files_origin.client_cert, inline_origin.client_cert);\n}\n"
  },
  {
    "path": "src/config/backends.rs",
    "content": "mod client_cert_info;\n\nuse {\n    hyper::{Uri, header::HeaderValue},\n    std::{collections::HashMap, sync::Arc},\n};\n\npub use self::client_cert_info::{ClientCertError, ClientCertInfo};\n\n/// Backend health status for testing purposes.\n#[derive(Clone, Debug, Default, PartialEq, Eq)]\npub enum BackendHealth {\n    /// Health status is unknown (default).\n    #[default]\n    Unknown,\n    /// Backend is healthy.\n    Healthy,\n    /// Backend is unhealthy.\n    Unhealthy,\n}\n\n/// A single backend definition.\n#[derive(Clone, Debug)]\npub struct Backend {\n    pub uri: Uri,\n    pub override_host: Option<HeaderValue>,\n    pub cert_host: Option<String>,\n    pub use_sni: bool,\n    pub grpc: bool,\n    pub client_cert: Option<ClientCertInfo>,\n    pub ca_certs: Vec<rustls::Certificate>,\n    pub health: BackendHealth,\n}\n\n/// A map of [`Backend`] definitions, keyed by their name.\n#[derive(Clone, Debug, Default)]\npub struct BackendsConfig(pub HashMap<String, Arc<Backend>>);\n\n/// This module contains [`TryFrom`] implementations used when deserializing a `fastly.toml`.\n///\n/// These implementations are called indirectly by [`FastlyConfig::from_file`][super::FastlyConfig],\n/// and help validate that we have been given an appropriate TOML schema. If the configuration is\n/// not valid, a [`FastlyConfigError`] will be returned.\nmod deserialization {\n    use {\n        super::{Backend, BackendsConfig},\n        crate::error::{BackendConfigError, FastlyConfigError},\n        hyper::{Uri, header::HeaderValue},\n        std::sync::Arc,\n        toml::value::{Table, Value},\n    };\n\n    /// Helper function for converting a TOML [`Value`] into a [`Table`].\n    ///\n    /// This function checks that a value is a [`Value::Table`] variant and returns the underlying\n    /// [`Table`], or returns an error if the given value was not of the right type — e.g., a\n    /// [`Boolean`][Value::Boolean] or a [`String`][Value::String]).\n    fn into_table(value: Value) -> Result<Table, BackendConfigError> {\n        match value {\n            Value::Table(table) => Ok(table),\n            _ => Err(BackendConfigError::InvalidEntryType),\n        }\n    }\n\n    /// Return an [`BackendConfigError::UnrecognizedKey`] error if any unrecognized keys are found.\n    ///\n    /// This should be called after we have removed and validated the keys we expect in a [`Table`].\n    fn check_for_unrecognized_keys(table: &Table) -> Result<(), BackendConfigError> {\n        if let Some(key) = table.keys().next() {\n            // While other keys might still exist, we can at least return a helpful error including\n            // the name of *one* unrecognized keys we found.\n            Err(BackendConfigError::UnrecognizedKey(key.to_owned()))\n        } else {\n            Ok(())\n        }\n    }\n\n    impl TryFrom<Table> for BackendsConfig {\n        type Error = FastlyConfigError;\n        fn try_from(toml: Table) -> Result<Self, Self::Error> {\n            /// Process a backend's definitions, or return a [`FastlyConfigError`].\n            fn process_entry(\n                (name, defs): (String, Value),\n            ) -> Result<(String, Arc<Backend>), FastlyConfigError> {\n                into_table(defs)\n                    .and_then(Backend::try_from)\n                    .map_err(|err| FastlyConfigError::InvalidBackendDefinition {\n                        name: name.clone(),\n                        err,\n                    })\n                    .map(|def| (name, Arc::new(def)))\n            }\n\n            toml.into_iter()\n                .map(process_entry)\n                .collect::<Result<_, _>>()\n                .map(Self)\n        }\n    }\n\n    impl TryFrom<Table> for Backend {\n        type Error = BackendConfigError;\n        fn try_from(mut toml: Table) -> Result<Self, Self::Error> {\n            let uri = toml\n                .remove(\"url\")\n                .ok_or(BackendConfigError::MissingUrl)\n                .and_then(|url| match url {\n                    Value::String(url) => url.parse::<Uri>().map_err(BackendConfigError::from),\n                    _ => Err(BackendConfigError::InvalidUrlEntry),\n                })?;\n\n            let override_host = toml\n                .remove(\"override_host\")\n                .map(|override_host| match override_host {\n                    Value::String(override_host) if !override_host.trim().is_empty() => {\n                        HeaderValue::from_str(&override_host).map_err(BackendConfigError::from)\n                    }\n                    Value::String(_) => Err(BackendConfigError::EmptyOverrideHost),\n                    _ => Err(BackendConfigError::InvalidOverrideHostEntry),\n                })\n                .transpose()?;\n\n            let cert_host = toml\n                .remove(\"cert_host\")\n                .map(|cert_host| match cert_host {\n                    Value::String(cert_host) if !cert_host.trim().is_empty() => Ok(cert_host),\n                    Value::String(_) => Err(BackendConfigError::EmptyCertHost),\n                    _ => Err(BackendConfigError::InvalidCertHostEntry),\n                })\n                .transpose()?;\n\n            let use_sni = toml\n                .remove(\"use_sni\")\n                .map(|use_sni| {\n                    if let Value::Boolean(use_sni) = use_sni {\n                        Ok(use_sni)\n                    } else {\n                        Err(BackendConfigError::InvalidUseSniEntry)\n                    }\n                })\n                .transpose()?\n                .unwrap_or(true);\n\n            let client_cert = toml\n                .remove(\"client_certificate\")\n                .map(TryFrom::try_from)\n                .transpose()?;\n            let ca_certs = toml\n                .remove(\"ca_certificate\")\n                .map(parse_ca_cert_section)\n                .unwrap_or_else(|| Ok(vec![]))?;\n\n            let grpc = toml\n                .remove(\"grpc\")\n                .map(|grpc| {\n                    if let Value::Boolean(grpc) = grpc {\n                        Ok(grpc)\n                    } else {\n                        Err(BackendConfigError::InvalidGrpcEntry)\n                    }\n                })\n                .transpose()?\n                .unwrap_or(false);\n\n            let health = toml\n                .remove(\"health\")\n                .map(|health| match health {\n                    Value::String(health) => {\n                        let health_lower = health.to_lowercase();\n                        match health_lower.as_str() {\n                            \"unknown\" => Ok(super::BackendHealth::Unknown),\n                            \"healthy\" => Ok(super::BackendHealth::Healthy),\n                            \"unhealthy\" => Ok(super::BackendHealth::Unhealthy),\n                            _ => Err(BackendConfigError::InvalidHealthEntry(health)),\n                        }\n                    }\n                    _ => Err(BackendConfigError::InvalidHealthEntry(\n                        \"not a string\".to_string(),\n                    )),\n                })\n                .transpose()?\n                .unwrap_or_default();\n\n            check_for_unrecognized_keys(&toml)?;\n\n            Ok(Self {\n                uri,\n                override_host,\n                cert_host,\n                use_sni,\n                client_cert,\n                grpc,\n                ca_certs,\n                health,\n            })\n        }\n    }\n\n    fn parse_ca_cert_section(\n        ca_cert: Value,\n    ) -> Result<Vec<rustls::Certificate>, BackendConfigError> {\n        match ca_cert {\n            Value::String(ca_cert) if !ca_cert.trim().is_empty() => {\n                let mut cursor = std::io::Cursor::new(ca_cert);\n                rustls_pemfile::certs(&mut cursor)\n                    .map_err(|e| BackendConfigError::InvalidCACertEntry(format!(\"Couldn't process certificate: {}\", e)))\n                    .map(|mut x| {\n                        x.drain(..)\n                            .map(rustls::Certificate)\n                            .collect::<Vec<rustls::Certificate>>()\n                    })\n            }\n            Value::String(_) => Err(BackendConfigError::EmptyCACert),\n\n            Value::Array(array) => {\n                let mut result = vec![];\n\n                for item in array.into_iter() {\n                    let mut current = parse_ca_cert_section(item)?;\n                    result.append(&mut current);\n                }\n\n                Ok(result)\n            }\n\n            Value::Table(mut table) => {\n                match table.remove(\"file\") {\n                    None => match table.remove(\"value\") {\n                        None => Err(BackendConfigError::InvalidCACertEntry(\"'ca_certificate' was a dictionary without a 'file' or 'value' field\".to_string())),\n                        Some(strval @ Value::String(_)) => parse_ca_cert_section(strval),\n                        Some(_) => Err(BackendConfigError::InvalidCACertEntry(\"invalid format for 'value' field\".to_string())),\n                    },\n                    Some(Value::String(x)) => {\n                        if !table.is_empty() {\n                            return Err(BackendConfigError::InvalidCACertEntry(format!(\"unknown ca_certificate keys: {:?}\", table.keys().collect::<Vec<_>>())));\n                        }\n\n                        let data = std::fs::read_to_string(&x)\n                            .map_err(|e| BackendConfigError::InvalidCACertEntry(format!(\"{}\", e)))?;\n                        parse_ca_cert_section(Value::String(data))\n                    }\n\n                    Some(_) => Err(BackendConfigError::InvalidCACertEntry(\"invalid format for file reference\".to_string())),\n                }\n            }\n\n            _ => Err(BackendConfigError::InvalidCACertEntry(\"unknown format for 'ca_certificates' field; should be a certificate string, a dictionary with a file reference, or an array of the previous\".to_string())),\n        }\n    }\n}\n"
  },
  {
    "path": "src/config/device_detection.rs",
    "content": "use {\n    crate::error::DeviceDetectionConfigError,\n    serde_json::{Map, Value as SerdeValue},\n    std::{collections::HashMap, fs, iter::FromIterator, path::Path, path::PathBuf},\n};\n\n#[derive(Clone, Debug, Default)]\npub struct DeviceDetection {\n    mapping: DeviceDetectionMapping,\n}\n\n#[derive(Clone, Debug, Default)]\npub enum DeviceDetectionMapping {\n    #[default]\n    Empty,\n    InlineToml {\n        user_agents: HashMap<String, DeviceDetectionData>,\n    },\n    Json {\n        file: PathBuf,\n    },\n}\n\n#[derive(Clone, Debug)]\npub struct DeviceDetectionData {\n    data: Map<String, SerdeValue>,\n}\n\nimpl DeviceDetection {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn lookup(&self, user_agent: &str) -> Option<DeviceDetectionData> {\n        self.mapping.get(user_agent).or(None)\n    }\n}\n\nmod deserialization {\n    use serde_json::{Map, Number};\n\n    use {\n        super::{DeviceDetection, DeviceDetectionData, DeviceDetectionMapping},\n        crate::error::{DeviceDetectionConfigError, FastlyConfigError},\n        serde_json::Value as SerdeValue,\n        std::path::PathBuf,\n        std::{collections::HashMap, convert::TryFrom},\n        toml::value::{Table, Value},\n    };\n\n    impl TryFrom<Table> for DeviceDetection {\n        type Error = FastlyConfigError;\n\n        fn try_from(toml: Table) -> Result<Self, Self::Error> {\n            fn process_config(\n                mut toml: Table,\n            ) -> Result<DeviceDetection, DeviceDetectionConfigError> {\n                let mapping = match toml.remove(\"format\") {\n                    Some(Value::String(value)) => match value.as_str() {\n                        \"inline-toml\" => process_inline_toml_dictionary(&mut toml)?,\n                        \"json\" => process_json_entries(&mut toml)?,\n                        \"\" => return Err(DeviceDetectionConfigError::EmptyFormatEntry),\n                        format => {\n                            return Err(\n                                DeviceDetectionConfigError::InvalidDeviceDetectionMappingFormat(\n                                    format.to_string(),\n                                ),\n                            );\n                        }\n                    },\n                    Some(_) => return Err(DeviceDetectionConfigError::InvalidFormatEntry),\n                    None => DeviceDetectionMapping::Empty,\n                };\n\n                Ok(DeviceDetection { mapping })\n            }\n\n            process_config(toml).map_err(|err| {\n                FastlyConfigError::InvalidDeviceDetectionDefinition {\n                    name: \"device_detection_mapping\".to_string(),\n                    err,\n                }\n            })\n        }\n    }\n\n    fn process_inline_toml_dictionary(\n        toml: &mut Table,\n    ) -> Result<DeviceDetectionMapping, DeviceDetectionConfigError> {\n        fn convert_value_to_json(value: Value) -> Option<SerdeValue> {\n            match value {\n                Value::String(value) => Some(SerdeValue::String(value)),\n                Value::Integer(value) => Some(SerdeValue::Number(Number::from(value))),\n                Value::Float(value) => Number::from_f64(value).map(SerdeValue::Number),\n                Value::Boolean(value) => Some(SerdeValue::Bool(value)),\n                Value::Table(value) => {\n                    let mut map = Map::new();\n                    for (k, v) in value {\n                        map.insert(k, convert_value_to_json(v)?);\n                    }\n                    Some(SerdeValue::Object(map))\n                }\n                _ => None,\n            }\n        }\n\n        // Take the `user_agents` field from the provided TOML table.\n        let toml = match toml\n            .remove(\"user_agents\")\n            .ok_or(DeviceDetectionConfigError::MissingUserAgents)?\n        {\n            Value::Table(table) => table,\n            _ => return Err(DeviceDetectionConfigError::InvalidUserAgentsType),\n        };\n\n        let mut user_agents = HashMap::<String, DeviceDetectionData>::with_capacity(toml.len());\n\n        for (user_agent, value) in toml {\n            let user_agent = user_agent.to_string();\n            let table = value\n                .as_table()\n                .ok_or(DeviceDetectionConfigError::InvalidInlineEntryType)?\n                .to_owned();\n\n            let mut device_detection_data = DeviceDetectionData::new();\n\n            for (field, value) in table {\n                let value = convert_value_to_json(value)\n                    .ok_or(DeviceDetectionConfigError::InvalidInlineEntryType)?;\n                device_detection_data.insert(field, value);\n            }\n\n            user_agents.insert(user_agent, device_detection_data);\n        }\n\n        Ok(DeviceDetectionMapping::InlineToml { user_agents })\n    }\n\n    fn process_json_entries(\n        toml: &mut Table,\n    ) -> Result<DeviceDetectionMapping, DeviceDetectionConfigError> {\n        let file: PathBuf = match toml\n            .remove(\"file\")\n            .ok_or(DeviceDetectionConfigError::MissingFile)?\n        {\n            Value::String(file) => {\n                if file.is_empty() {\n                    return Err(DeviceDetectionConfigError::EmptyFileEntry);\n                } else {\n                    file.into()\n                }\n            }\n            _ => return Err(DeviceDetectionConfigError::InvalidFileEntry),\n        };\n\n        DeviceDetectionMapping::read_json_contents(&file)?;\n\n        Ok(DeviceDetectionMapping::Json { file })\n    }\n}\n\nimpl DeviceDetectionMapping {\n    pub fn get(&self, user_agent: &str) -> Option<DeviceDetectionData> {\n        match self {\n            Self::Empty => None,\n            Self::InlineToml { user_agents } => user_agents\n                .get(user_agent)\n                .map(|device_detection_data| device_detection_data.to_owned()),\n            Self::Json { file } => Self::read_json_contents(file)\n                .ok()\n                .map(|user_agents| {\n                    user_agents\n                        .get(user_agent)\n                        .map(|device_detection_data| device_detection_data.to_owned())\n                })\n                .unwrap(),\n        }\n    }\n\n    pub fn read_json_contents(\n        file: &Path,\n    ) -> Result<HashMap<String, DeviceDetectionData>, DeviceDetectionConfigError> {\n        let data = fs::read_to_string(file).map_err(DeviceDetectionConfigError::IoError)?;\n\n        // Deserialize the contents of the given JSON file.\n        let json = match serde_json::from_str(&data)\n            .map_err(|_| DeviceDetectionConfigError::DeviceDetectionFileWrongFormat)?\n        {\n            // Check that we were given an object.\n            serde_json::Value::Object(obj) => obj,\n            _ => {\n                return Err(DeviceDetectionConfigError::DeviceDetectionFileWrongFormat);\n            }\n        };\n\n        let mut user_agents = HashMap::<String, DeviceDetectionData>::with_capacity(json.len());\n\n        for (user_agent, value) in json {\n            let user_agent = user_agent.to_string();\n            let table = value\n                .as_object()\n                .ok_or(DeviceDetectionConfigError::InvalidInlineEntryType)?\n                .to_owned();\n\n            let device_detection_data = DeviceDetectionData::from(&table);\n\n            user_agents.insert(user_agent, device_detection_data);\n        }\n\n        Ok(user_agents)\n    }\n}\n\nimpl Default for DeviceDetectionData {\n    fn default() -> Self {\n        Self::from(HashMap::new())\n    }\n}\n\nimpl From<HashMap<&str, SerdeValue>> for DeviceDetectionData {\n    fn from(value: HashMap<&str, SerdeValue>) -> Self {\n        let entries = value\n            .iter()\n            .map(|(&field, value)| (field.to_string(), value.to_owned()));\n\n        Self {\n            data: Map::from_iter(entries),\n        }\n    }\n}\n\nimpl From<&Map<String, SerdeValue>> for DeviceDetectionData {\n    fn from(data: &Map<String, SerdeValue>) -> Self {\n        Self {\n            data: data.to_owned(),\n        }\n    }\n}\n\nimpl DeviceDetectionData {\n    pub fn new() -> Self {\n        Self { data: Map::new() }\n    }\n\n    pub fn insert(&mut self, field: String, value: SerdeValue) {\n        self.data.insert(field, value);\n    }\n}\n\nimpl std::fmt::Display for DeviceDetectionData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match serde_json::to_string(&self.data) {\n            Ok(s) => write!(f, \"{}\", s),\n            Err(_) => Ok(()),\n        }\n    }\n}\n"
  },
  {
    "path": "src/config/dictionaries.rs",
    "content": "use {\n    crate::error::DictionaryConfigError,\n    std::{\n        collections::HashMap,\n        fs,\n        path::{Path, PathBuf},\n        sync::Arc,\n    },\n};\n\n/// A single Dictionary definition.\n///\n/// A Dictionary consists of a file and format, but more fields may be added in the future.\n#[derive(Clone, Debug)]\npub enum Dictionary {\n    InlineToml {\n        contents: Arc<HashMap<String, String>>,\n    },\n    Json {\n        file: PathBuf,\n    },\n}\n\nimpl Dictionary {\n    /// Returns `true` if this is dictionary uses an external JSON file.\n    pub fn is_json(&self) -> bool {\n        matches!(self, Self::Json { .. })\n    }\n\n    /// Returns the [`Path`] of the backing file storage, if applicable.\n    pub fn file_path(&self) -> Option<&Path> {\n        match self {\n            Self::InlineToml { .. } => None,\n            Self::Json { file, .. } => Some(file.as_path()),\n        }\n    }\n\n    /// Reads the contents of a JSON dictionary file.\n    fn read_json_contents(file: &Path) -> Result<HashMap<String, String>, DictionaryConfigError> {\n        // Read the contents of the given file.\n        let data = fs::read_to_string(file).map_err(DictionaryConfigError::IoError)?;\n\n        // Deserialize the contents of the given JSON file.\n        let json = match serde_json::from_str(&data)\n            .map_err(|_| DictionaryConfigError::DictionaryFileWrongFormat)?\n        {\n            // Check that we were given an object.\n            serde_json::Value::Object(obj) => obj,\n            _ => {\n                return Err(DictionaryConfigError::DictionaryFileWrongFormat);\n            }\n        };\n\n        // Check that each dictionary entry has a string value.\n        let mut contents = HashMap::with_capacity(json.len());\n        for (key, value) in json {\n            let value = value\n                .as_str()\n                .ok_or_else(|| DictionaryConfigError::DictionaryItemValueWrongFormat {\n                    key: key.clone(),\n                })?\n                .to_owned();\n            contents.insert(key, value);\n        }\n\n        // Validate that the dictionary adheres to Fastly's API.\n        deserialization::validate_dictionary_contents(&contents)?;\n\n        Ok(contents)\n    }\n\n    pub fn load(&self) -> Result<LoadedDictionary, DictionaryConfigError> {\n        let contents = match self {\n            Dictionary::InlineToml { contents } => Arc::clone(contents),\n            Dictionary::Json { file } => {\n                let contents = Self::read_json_contents(file)?;\n                Arc::new(contents)\n            }\n        };\n\n        Ok(LoadedDictionary { contents })\n    }\n}\n\n#[derive(Clone)]\npub struct LoadedDictionary {\n    pub contents: Arc<HashMap<String, String>>,\n}\n\n/// A map of [`Dictionary`] definitions, keyed by their name.\n#[derive(Clone, Debug, Default)]\npub struct DictionariesConfig(pub HashMap<String, Dictionary>);\n\n/// This module contains [`TryFrom`] implementations used when deserializing a `fastly.toml`.\n///\n/// These implementations are called indirectly by [`FastlyConfig::from_file`][super::FastlyConfig],\n/// and help validate that we have been given an appropriate TOML schema. If the configuration is\n/// not valid, a [`FastlyConfigError`] will be returned.\nmod deserialization {\n    use {\n        super::{DictionariesConfig, Dictionary},\n        crate::{\n            config::limits::{DICTIONARY_ITEM_KEY_MAX_LEN, DICTIONARY_ITEM_VALUE_MAX_LEN},\n            error::{DictionaryConfigError, FastlyConfigError},\n        },\n        std::{collections::HashMap, path::PathBuf, sync::Arc},\n        toml::value::{Table, Value},\n        tracing::info,\n    };\n\n    /// Return an [`DictionaryConfigError::UnrecognizedKey`] error if any unrecognized keys are found.\n    ///\n    /// This should be called after we have removed and validated the keys we expect in a [`Table`].\n    fn check_for_unrecognized_keys(table: &Table) -> Result<(), DictionaryConfigError> {\n        if let Some(key) = table.keys().next() {\n            // While other keys might still exist, we can at least return a helpful error including\n            // the name of *one* unrecognized keys we found.\n            Err(DictionaryConfigError::UnrecognizedKey(key.to_owned()))\n        } else {\n            Ok(())\n        }\n    }\n\n    impl TryFrom<Table> for DictionariesConfig {\n        type Error = FastlyConfigError;\n        fn try_from(toml: Table) -> Result<Self, Self::Error> {\n            /// Process a dictionary's definitions, or return a [`FastlyConfigError`].\n            fn process_entry(\n                name: &str,\n                entry: Value,\n            ) -> Result<(String, Dictionary), DictionaryConfigError> {\n                let mut toml = match entry {\n                    Value::Table(table) => table,\n                    _ => return Err(DictionaryConfigError::InvalidEntryType),\n                };\n\n                let format = toml\n                    .remove(\"format\")\n                    .ok_or(DictionaryConfigError::MissingFormat)\n                    .and_then(|format| match format {\n                        Value::String(format) => Ok(format),\n                        _ => Err(DictionaryConfigError::InvalidFormatEntry),\n                    })?;\n\n                let dictionary = match format.as_str() {\n                    \"inline-toml\" => process_inline_toml_dictionary(&mut toml)?,\n                    \"json\" => process_json_dictionary(&mut toml)?,\n                    \"\" => return Err(DictionaryConfigError::EmptyFormatEntry),\n                    _ => {\n                        return Err(DictionaryConfigError::InvalidDictionaryFormat(\n                            format.to_owned(),\n                        ));\n                    }\n                };\n\n                check_for_unrecognized_keys(&toml)?;\n\n                Ok((name.to_string(), dictionary))\n            }\n\n            toml.into_iter()\n                .map(|(name, defs)| {\n                    process_entry(&name, defs)\n                        .map_err(|err| FastlyConfigError::InvalidDictionaryDefinition { name, err })\n                })\n                .collect::<Result<_, _>>()\n                .map(Self)\n        }\n    }\n\n    fn process_inline_toml_dictionary(\n        toml: &mut Table,\n    ) -> Result<Dictionary, DictionaryConfigError> {\n        // Take the `contents` field from the provided TOML table.\n        let toml = match toml\n            .remove(\"contents\")\n            .ok_or(DictionaryConfigError::MissingContents)?\n        {\n            Value::Table(table) => table,\n            _ => return Err(DictionaryConfigError::InvalidContentsType),\n        };\n\n        // Check that each dictionary entry has a string value.\n        let mut contents = HashMap::with_capacity(toml.len());\n        for (key, value) in toml {\n            let value = value\n                .as_str()\n                .ok_or(DictionaryConfigError::InvalidInlineEntryType)?\n                .to_owned();\n            contents.insert(key, value);\n        }\n\n        // Validate that the dictionary adheres to Fastly's API.\n        validate_dictionary_contents(&contents)?;\n\n        Ok(Dictionary::InlineToml {\n            contents: Arc::new(contents),\n        })\n    }\n\n    fn process_json_dictionary(toml: &mut Table) -> Result<Dictionary, DictionaryConfigError> {\n        // Take the `file` field from the provided TOML table.\n        let file: PathBuf = match toml\n            .remove(\"file\")\n            .ok_or(DictionaryConfigError::MissingFile)?\n        {\n            Value::String(file) => {\n                if file.is_empty() {\n                    return Err(DictionaryConfigError::EmptyFileEntry);\n                } else {\n                    file.into()\n                }\n            }\n            _ => return Err(DictionaryConfigError::InvalidFileEntry),\n        };\n\n        Dictionary::read_json_contents(&file)?;\n\n        Ok(Dictionary::Json { file })\n    }\n\n    pub(super) fn validate_dictionary_contents(\n        dict: &HashMap<String, String>,\n    ) -> Result<(), DictionaryConfigError> {\n        info!(\"checking if dictionary adheres to Fastly's API\",);\n\n        for (key, value) in dict.iter() {\n            if key.chars().count() > DICTIONARY_ITEM_KEY_MAX_LEN {\n                return Err(DictionaryConfigError::DictionaryItemKeyTooLong {\n                    key: key.clone(),\n                    size: DICTIONARY_ITEM_KEY_MAX_LEN.try_into().unwrap(),\n                });\n            }\n            if value.chars().count() > DICTIONARY_ITEM_VALUE_MAX_LEN {\n                return Err(DictionaryConfigError::DictionaryItemValueTooLong {\n                    key: key.clone(),\n                    size: DICTIONARY_ITEM_VALUE_MAX_LEN.try_into().unwrap(),\n                });\n            }\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/config/geolocation.rs",
    "content": "use {\n    crate::error::GeolocationConfigError,\n    serde_json::{\n        Map, Number, Value as SerdeValue, Value::Number as SerdeNumber,\n        Value::String as SerdeString,\n    },\n    std::{collections::HashMap, fs, iter::FromIterator, net::IpAddr, path::Path, path::PathBuf},\n};\n\n#[derive(Clone, Debug)]\npub struct Geolocation {\n    mapping: GeolocationMapping,\n    use_default_loopback: bool,\n}\n\n#[derive(Clone, Debug, Default)]\npub enum GeolocationMapping {\n    #[default]\n    Empty,\n    InlineToml {\n        addresses: HashMap<IpAddr, GeolocationData>,\n    },\n    Json {\n        file: PathBuf,\n    },\n}\n\n#[derive(Clone, Debug)]\npub struct GeolocationData {\n    data: Map<String, SerdeValue>,\n}\n\nimpl Default for Geolocation {\n    fn default() -> Self {\n        Self {\n            mapping: GeolocationMapping::default(),\n            use_default_loopback: true,\n        }\n    }\n}\n\nimpl Geolocation {\n    pub fn new() -> Self {\n        Self::default()\n    }\n\n    pub fn lookup(&self, addr: &IpAddr) -> Option<GeolocationData> {\n        self.mapping.get(addr).or_else(|| {\n            if self.use_default_loopback && addr.is_loopback() {\n                Some(GeolocationData::default())\n            } else {\n                None\n            }\n        })\n    }\n}\nmod deserialization {\n    use std::{net::IpAddr, str::FromStr};\n\n    use serde_json::Number;\n\n    use {\n        super::{Geolocation, GeolocationData, GeolocationMapping},\n        crate::error::{FastlyConfigError, GeolocationConfigError},\n        serde_json::Value as SerdeValue,\n        std::path::PathBuf,\n        std::{collections::HashMap, convert::TryFrom},\n        toml::value::{Table, Value},\n    };\n\n    impl TryFrom<Table> for Geolocation {\n        type Error = FastlyConfigError;\n\n        fn try_from(toml: Table) -> Result<Self, Self::Error> {\n            fn process_config(mut toml: Table) -> Result<Geolocation, GeolocationConfigError> {\n                let use_default_loopback = toml.remove(\"use_default_loopback\").map_or(\n                    Ok(true),\n                    |use_default_loopback| match use_default_loopback {\n                        Value::Boolean(use_default_loopback) => Ok(use_default_loopback),\n                        _ => Err(GeolocationConfigError::InvalidEntryType),\n                    },\n                )?;\n\n                let mapping = match toml.remove(\"format\") {\n                    Some(Value::String(value)) => match value.as_str() {\n                        \"inline-toml\" => process_inline_toml_dictionary(&mut toml)?,\n                        \"json\" => process_json_entries(&mut toml)?,\n                        \"\" => return Err(GeolocationConfigError::EmptyFormatEntry),\n                        format => {\n                            return Err(GeolocationConfigError::InvalidGeolocationMappingFormat(\n                                format.to_string(),\n                            ));\n                        }\n                    },\n                    Some(_) => return Err(GeolocationConfigError::InvalidFormatEntry),\n                    None => GeolocationMapping::Empty,\n                };\n\n                Ok(Geolocation {\n                    mapping,\n                    use_default_loopback,\n                })\n            }\n\n            process_config(toml).map_err(|err| FastlyConfigError::InvalidGeolocationDefinition {\n                name: \"geolocation_mapping\".to_string(),\n                err,\n            })\n        }\n    }\n\n    pub fn parse_ip_address(address: &str) -> Result<IpAddr, GeolocationConfigError> {\n        IpAddr::from_str(address)\n            .map_err(|err| GeolocationConfigError::InvalidAddressEntry(err.to_string()))\n    }\n\n    fn process_inline_toml_dictionary(\n        toml: &mut Table,\n    ) -> Result<GeolocationMapping, GeolocationConfigError> {\n        fn convert_value_to_json(value: Value) -> Option<SerdeValue> {\n            match value {\n                Value::String(value) => Some(SerdeValue::String(value)),\n                Value::Integer(value) => Some(SerdeValue::Number(Number::from(value))),\n                Value::Float(value) => Number::from_f64(value).map(SerdeValue::Number),\n                Value::Boolean(value) => Some(SerdeValue::Bool(value)),\n                _ => None,\n            }\n        }\n\n        // Take the `addresses` field from the provided TOML table.\n        let toml = match toml\n            .remove(\"addresses\")\n            .ok_or(GeolocationConfigError::MissingAddresses)?\n        {\n            Value::Table(table) => table,\n            _ => return Err(GeolocationConfigError::InvalidAddressesType),\n        };\n\n        let mut addresses = HashMap::<IpAddr, GeolocationData>::with_capacity(toml.len());\n\n        for (address, value) in toml {\n            let address = parse_ip_address(address.as_str())?;\n            let table = value\n                .as_table()\n                .ok_or(GeolocationConfigError::InvalidInlineEntryType)?\n                .to_owned();\n\n            let mut geolocation_data = GeolocationData::new();\n\n            for (field, value) in table {\n                let value = convert_value_to_json(value)\n                    .ok_or(GeolocationConfigError::InvalidInlineEntryType)?;\n                geolocation_data.insert(field, value);\n            }\n\n            addresses.insert(address, geolocation_data);\n        }\n\n        Ok(GeolocationMapping::InlineToml { addresses })\n    }\n\n    fn process_json_entries(\n        toml: &mut Table,\n    ) -> Result<GeolocationMapping, GeolocationConfigError> {\n        let file: PathBuf = match toml\n            .remove(\"file\")\n            .ok_or(GeolocationConfigError::MissingFile)?\n        {\n            Value::String(file) => {\n                if file.is_empty() {\n                    return Err(GeolocationConfigError::EmptyFileEntry);\n                } else {\n                    file.into()\n                }\n            }\n            _ => return Err(GeolocationConfigError::InvalidFileEntry),\n        };\n\n        GeolocationMapping::read_json_contents(&file)?;\n\n        Ok(GeolocationMapping::Json { file })\n    }\n}\n\nimpl GeolocationMapping {\n    pub fn get(&self, address: &IpAddr) -> Option<GeolocationData> {\n        match self {\n            Self::Empty => None,\n            Self::InlineToml { addresses } => addresses\n                .get(address)\n                .map(|geolocation_data| geolocation_data.to_owned()),\n            Self::Json { file } => Self::read_json_contents(file)\n                .ok()\n                .map(|addresses| {\n                    addresses\n                        .get(address)\n                        .map(|geolocation_data| geolocation_data.to_owned())\n                })\n                .unwrap(),\n        }\n    }\n\n    pub fn read_json_contents(\n        file: &Path,\n    ) -> Result<HashMap<IpAddr, GeolocationData>, GeolocationConfigError> {\n        let data = fs::read_to_string(file).map_err(GeolocationConfigError::IoError)?;\n\n        // Deserialize the contents of the given JSON file.\n        let json = match serde_json::from_str(&data)\n            .map_err(|_| GeolocationConfigError::GeolocationFileWrongFormat)?\n        {\n            // Check that we were given an object.\n            serde_json::Value::Object(obj) => obj,\n            _ => {\n                return Err(GeolocationConfigError::GeolocationFileWrongFormat);\n            }\n        };\n\n        let mut addresses = HashMap::<IpAddr, GeolocationData>::with_capacity(json.len());\n\n        for (address, value) in json {\n            let address = deserialization::parse_ip_address(address.as_str())?;\n            let table = value\n                .as_object()\n                .ok_or(GeolocationConfigError::InvalidInlineEntryType)?\n                .to_owned();\n\n            let geolocation_data = GeolocationData::from(&table);\n\n            addresses.insert(address, geolocation_data);\n        }\n\n        Ok(addresses)\n    }\n}\n\nimpl Default for GeolocationData {\n    fn default() -> Self {\n        let default_entries = HashMap::<&str, SerdeValue>::from([\n            (\"as_name\", SerdeString(String::from(\"Fastly, Inc\"))),\n            (\"as_number\", SerdeNumber(Number::from(54113))),\n            (\"area_code\", SerdeNumber(Number::from(415))),\n            (\"city\", SerdeString(String::from(\"San Francisco\"))),\n            (\"conn_speed\", SerdeString(String::from(\"broadband\"))),\n            (\"conn_type\", SerdeString(String::from(\"wired\"))),\n            (\"continent\", SerdeString(String::from(\"NA\"))),\n            (\"country_code\", SerdeString(String::from(\"US\"))),\n            (\"country_code3\", SerdeString(String::from(\"USA\"))),\n            (\n                \"country_name\",\n                SerdeString(String::from(\"United States of America\")),\n            ),\n            (\"latitude\", SerdeNumber(Number::from_f64(37.77869).unwrap())),\n            (\n                \"longitude\",\n                SerdeNumber(Number::from_f64(-122.39557).unwrap()),\n            ),\n            (\"metro_code\", SerdeNumber(Number::from(0))),\n            (\"postal_code\", SerdeString(String::from(\"94107\"))),\n            (\"proxy_description\", SerdeString(String::from(\"?\"))),\n            (\"proxy_type\", SerdeString(String::from(\"?\"))),\n            (\"region\", SerdeString(String::from(\"CA\"))),\n            (\"utc_offset\", SerdeNumber(Number::from(-700))),\n        ]);\n\n        Self::from(default_entries)\n    }\n}\n\nimpl From<HashMap<&str, SerdeValue>> for GeolocationData {\n    fn from(value: HashMap<&str, SerdeValue>) -> Self {\n        let entries = value\n            .iter()\n            .map(|(&field, value)| (field.to_string(), value.to_owned()));\n\n        Self {\n            data: Map::from_iter(entries),\n        }\n    }\n}\n\nimpl From<&Map<String, SerdeValue>> for GeolocationData {\n    fn from(data: &Map<String, SerdeValue>) -> Self {\n        Self {\n            data: data.to_owned(),\n        }\n    }\n}\n\nimpl GeolocationData {\n    pub fn new() -> Self {\n        Self { data: Map::new() }\n    }\n\n    pub fn insert(&mut self, field: String, value: SerdeValue) {\n        self.data.insert(field, value);\n    }\n}\n\nimpl std::fmt::Display for GeolocationData {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match serde_json::to_string(&self.data) {\n            Ok(s) => write!(f, \"{}\", s),\n            Err(_) => Ok(()),\n        }\n    }\n}\n"
  },
  {
    "path": "src/config/limits.rs",
    "content": "// From https://docs.fastly.com/en/guides/resource-limits#vcl-and-configuration-limits\npub const DICTIONARY_ITEM_KEY_MAX_LEN: usize = 256;\npub const DICTIONARY_ITEM_VALUE_MAX_LEN: usize = 8000;\n"
  },
  {
    "path": "src/config/object_store.rs",
    "content": "use std::collections::HashMap;\nuse std::path::{Path, PathBuf};\nuse toml::Value;\nuse {\n    crate::{\n        error::{FastlyConfigError, ObjectStoreConfigError},\n        object_store::{ObjectKey, ObjectStoreKey, ObjectStores},\n        wiggle_abi::types::KvInsertMode,\n    },\n    std::fs,\n    toml::value::Table,\n};\n\n#[derive(Debug)]\nstruct ObjectStoreEntry {\n    data: Option<String>,\n    file: Option<String>,\n    metadata: Option<String>,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct ObjectStoreConfig(pub(crate) ObjectStores);\n\nimpl TryFrom<Table> for ObjectStoreConfig {\n    type Error = FastlyConfigError;\n    fn try_from(toml: Table) -> Result<Self, Self::Error> {\n        let obj_store = ObjectStores::new();\n        for (store, items) in toml.iter() {\n            // Either the items here is from a top-level file with \"file\" and \"format\" keys\n            // or it's an inline array.\n            // We try to parse either one of them to the same Vec<toml::Value>\n            // to allow them to run through the same validation path further down\n            let file_path = items\n                .as_table()\n                .and_then(|table| table.get(\"file\"))\n                .and_then(|file| file.as_str());\n            let file_format = items\n                .as_table()\n                .and_then(|table| table.get(\"format\"))\n                .and_then(|format| format.as_str());\n\n            let items: Vec<toml::Value> = match (file_path, file_format) {\n                (Some(file_path), Some(file_type)) => {\n                    if file_type != \"json\" {\n                        return Err(FastlyConfigError::InvalidObjectStoreDefinition {\n                            name: store.to_string(),\n                            err: ObjectStoreConfigError::InvalidFileFormat(file_type.to_string()),\n                        });\n                    }\n\n                    let path = PathBuf::from(&file_path);\n\n                    let json = read_json_contents(&path).map_err(|err| {\n                        FastlyConfigError::InvalidObjectStoreDefinition {\n                            name: store.to_string(),\n                            err,\n                        }\n                    })?;\n\n                    let toml: Vec<Value> = json\n                        .into_iter()\n                        .map(|(key, entry)| {\n                            let mut table = toml::value::Table::new();\n                            table.insert(\"key\".to_string(), toml::Value::String(key));\n                            if let Some(data) = entry.data {\n                                table.insert(\"data\".to_string(), toml::Value::String(data));\n                            }\n                            if let Some(file) = entry.file {\n                                table.insert(\"file\".to_string(), toml::Value::String(file));\n                            }\n                            if let Some(meta) = entry.metadata {\n                                table.insert(\"metadata\".to_string(), toml::Value::String(meta));\n                            }\n                            toml::Value::Table(table)\n                        })\n                        .collect();\n\n                    toml\n                }\n                (None, None) => {\n                    // No file or format specified, parse the TOML as an array\n                    items\n                        .as_array()\n                        .ok_or_else(|| FastlyConfigError::InvalidObjectStoreDefinition {\n                            name: store.to_string(),\n                            err: ObjectStoreConfigError::NotAnArray,\n                        })?\n                        .clone()\n                }\n                // This means that *either* `format` or `file` is set, which isn't allowed\n                // we need both or neither.\n                (_, _) => {\n                    return Err(FastlyConfigError::InvalidObjectStoreDefinition {\n                        name: store.to_string(),\n                        err: ObjectStoreConfigError::OnlyOneFormatOrFileSet,\n                    });\n                }\n            };\n\n            // Handle the case where there are no items to insert, but the store\n            // exists and needs to be in the ObjectStore\n            if items.is_empty() {\n                obj_store\n                    .insert_empty_store(ObjectStoreKey::new(store))\n                    .map_err(|err| FastlyConfigError::InvalidObjectStoreDefinition {\n                        name: store.to_string(),\n                        err: err.into(),\n                    })?;\n                continue;\n            }\n            for item in items.iter() {\n                let item = item.as_table().ok_or_else(|| {\n                    FastlyConfigError::InvalidObjectStoreDefinition {\n                        name: store.to_string(),\n                        err: ObjectStoreConfigError::NotATable,\n                    }\n                })?;\n\n                let key = item\n                    .get(\"key\")\n                    .ok_or_else(|| FastlyConfigError::InvalidObjectStoreDefinition {\n                        name: store.to_string(),\n                        err: ObjectStoreConfigError::NoKey,\n                    })?\n                    .as_str()\n                    .ok_or_else(|| FastlyConfigError::InvalidObjectStoreDefinition {\n                        name: store.to_string(),\n                        err: ObjectStoreConfigError::KeyNotAString,\n                    })?;\n\n                // Previously the \"file\" key was named \"path\".  We want\n                // to continue supporting the old name.\n                let file = match (item.get(\"file\"), item.get(\"path\")) {\n                    (None, None) => None,\n                    (Some(file), _) => Some(file),\n                    (None, Some(path)) => Some(path),\n                };\n\n                let bytes = match (file, item.get(\"data\")) {\n                    (None, None) => {\n                        return Err(FastlyConfigError::InvalidObjectStoreDefinition {\n                            name: store.to_string(),\n                            err: ObjectStoreConfigError::NoFileOrData(key.to_string()),\n                        });\n                    }\n                    (Some(_), Some(_)) => {\n                        return Err(FastlyConfigError::InvalidObjectStoreDefinition {\n                            name: store.to_string(),\n                            err: ObjectStoreConfigError::FileAndData(key.to_string()),\n                        });\n                    }\n                    (Some(path), None) => {\n                        let path = path.as_str().ok_or_else(|| {\n                            FastlyConfigError::InvalidObjectStoreDefinition {\n                                name: store.to_string(),\n                                err: ObjectStoreConfigError::FileNotAString(key.to_string()),\n                            }\n                        })?;\n                        fs::read(path).map_err(|e| {\n                            FastlyConfigError::InvalidObjectStoreDefinition {\n                                name: store.to_string(),\n                                err: ObjectStoreConfigError::IoError(e),\n                            }\n                        })?\n                    }\n                    (None, Some(data)) => data\n                        .as_str()\n                        .ok_or_else(|| FastlyConfigError::InvalidObjectStoreDefinition {\n                            name: store.to_string(),\n                            err: ObjectStoreConfigError::DataNotAString(key.to_string()),\n                        })?\n                        .as_bytes()\n                        .to_vec(),\n                };\n\n                let metadata_bytes = match item.get(\"metadata\") {\n                    Some(metadata) => Some(\n                        metadata\n                            .as_str()\n                            .ok_or_else(|| FastlyConfigError::InvalidObjectStoreDefinition {\n                                name: store.to_string(),\n                                err: ObjectStoreConfigError::MetadataNotAString(key.to_string()),\n                            })?\n                            .to_owned(),\n                    ),\n                    None => None,\n                };\n\n                obj_store\n                    .insert(\n                        ObjectStoreKey::new(store),\n                        ObjectKey::new(key).map_err(|err| {\n                            FastlyConfigError::InvalidObjectStoreDefinition {\n                                name: store.to_string(),\n                                err: err.into(),\n                            }\n                        })?,\n                        bytes,\n                        KvInsertMode::Overwrite,\n                        None,\n                        metadata_bytes,\n                        None,\n                    )\n                    .expect(\"Lock was not poisoned\");\n            }\n        }\n\n        Ok(ObjectStoreConfig(obj_store))\n    }\n}\n\nfn read_json_contents(\n    file: &Path,\n) -> Result<HashMap<String, ObjectStoreEntry>, ObjectStoreConfigError> {\n    // Read the contents of the given file.\n    let data = fs::read_to_string(file).map_err(ObjectStoreConfigError::IoError)?;\n\n    // Deserialize the contents of the given JSON file.\n    let json =\n        match serde_json::from_str(&data).map_err(|_| ObjectStoreConfigError::FileWrongFormat)? {\n            // Check that we were given an object.\n            serde_json::Value::Object(obj) => obj,\n            _ => {\n                return Err(ObjectStoreConfigError::FileWrongFormat);\n            }\n        };\n\n    let mut contents = HashMap::with_capacity(json.len());\n\n    // Check that each KV Store entry has either a string value or an object with data and metadata\n    // fields.\n    for (key, value) in json {\n        let entry = match value {\n            serde_json::Value::String(s) => ObjectStoreEntry {\n                data: Some(s),\n                file: None,\n                metadata: None,\n            },\n            serde_json::Value::Object(obj) => {\n                let data = match obj.get(\"data\") {\n                    Some(val) => Some(\n                        val.as_str()\n                            .ok_or_else(|| ObjectStoreConfigError::FileValueWrongFormat {\n                                key: key.clone(),\n                            })?\n                            .to_string(),\n                    ),\n                    None => None,\n                };\n\n                let file = match obj.get(\"file\") {\n                    Some(val) => Some(\n                        val.as_str()\n                            .ok_or_else(|| ObjectStoreConfigError::FileValueWrongFormat {\n                                key: key.clone(),\n                            })?\n                            .to_string(),\n                    ),\n                    None => None,\n                };\n\n                let metadata = match obj.get(\"metadata\") {\n                    Some(val) => Some(\n                        val.as_str()\n                            .ok_or_else(|| ObjectStoreConfigError::FileValueWrongFormat {\n                                key: key.clone(),\n                            })?\n                            .to_string(),\n                    ),\n                    None => None,\n                };\n\n                // Now validate: exactly one of `data` or `file` must be set\n                match (&data, &file) {\n                    (Some(_), Some(_)) => {\n                        return Err(ObjectStoreConfigError::BothDataAndFilePresent {\n                            key: key.clone(),\n                        });\n                    }\n                    (None, None) => {\n                        return Err(ObjectStoreConfigError::MissingDataOrFile { key: key.clone() });\n                    }\n                    _ => {} // all good\n                }\n\n                ObjectStoreEntry {\n                    data,\n                    file,\n                    metadata,\n                }\n            }\n            _ => return Err(ObjectStoreConfigError::FileValueWrongFormat { key: key.clone() }),\n        };\n\n        contents.insert(key, entry);\n    }\n\n    Ok(contents)\n}\n"
  },
  {
    "path": "src/config/secret_store.rs",
    "content": "use {\n    crate::{\n        error::{FastlyConfigError, SecretStoreConfigError},\n        secret_store::{SecretStore, SecretStores},\n    },\n    std::{collections::HashMap, convert::TryFrom, fs},\n    toml::value::Table,\n};\n\n#[derive(Clone, Debug, Default)]\npub struct SecretStoreConfig(pub(crate) SecretStores);\n\nimpl TryFrom<Table> for SecretStoreConfig {\n    type Error = FastlyConfigError;\n    fn try_from(toml: Table) -> Result<Self, Self::Error> {\n        let mut stores = SecretStores::new();\n\n        for (store_name, items) in toml.iter() {\n            if !is_valid_name(store_name) {\n                return Err(FastlyConfigError::InvalidSecretStoreDefinition {\n                    name: store_name.to_string(),\n                    err: SecretStoreConfigError::InvalidSecretStoreName(store_name.to_string()),\n                });\n            }\n\n            // Either the items here are from a top-level file with\n            // \"file\" and \"format\" keys or it's an inline array.\n            // We try to parse either one of them to the same Vec<toml::Value>\n            // to allow them to run through the same validation path further down.\n            let file_path = items\n                .as_table()\n                .and_then(|table| table.get(\"file\"))\n                .and_then(|file| file.as_str());\n            let file_format = items\n                .as_table()\n                .and_then(|table| table.get(\"format\"))\n                .and_then(|format| format.as_str());\n\n            let items: Vec<toml::Value> = match (file_path, file_format) {\n                (Some(file_path), Some(file_type)) => {\n                    if file_type != \"json\" {\n                        return Err(FastlyConfigError::InvalidSecretStoreDefinition {\n                            name: store_name.to_string(),\n                            err: SecretStoreConfigError::InvalidFileFormat(file_type.to_string()),\n                        });\n                    }\n\n                    let json = read_json_contents(file_path).map_err(|e| {\n                        FastlyConfigError::InvalidSecretStoreDefinition {\n                            name: store_name.to_string(),\n                            err: e,\n                        }\n                    })?;\n\n                    let toml: Vec<toml::Value> = json\n                        .into_iter()\n                        .map(|(key, value)| {\n                            toml::toml! {\n                                key = key\n                                data = value\n                            }\n                        })\n                        .collect();\n\n                    toml\n                }\n                (None, None) => {\n                    // No file or format specified, parse the TOML as an array\n                    items\n                        .as_array()\n                        .ok_or_else(|| FastlyConfigError::InvalidSecretStoreDefinition {\n                            name: store_name.to_string(),\n                            err: SecretStoreConfigError::NotAnArray,\n                        })?\n                        .clone()\n                }\n                // This means that *either* `format` or `file` is set, which isn't allowed\n                // we need both or neither.\n                (_, _) => {\n                    return Err(FastlyConfigError::InvalidSecretStoreDefinition {\n                        name: store_name.to_string(),\n                        err: SecretStoreConfigError::OnlyOneFormatOrFileSet,\n                    });\n                }\n            };\n\n            let mut secret_store = SecretStore::new();\n            for item in items.iter() {\n                let item = item.as_table().ok_or_else(|| {\n                    FastlyConfigError::InvalidSecretStoreDefinition {\n                        name: store_name.to_string(),\n                        err: SecretStoreConfigError::NotATable,\n                    }\n                })?;\n\n                let key = item\n                    .get(\"key\")\n                    .ok_or_else(|| FastlyConfigError::InvalidSecretStoreDefinition {\n                        name: store_name.to_string(),\n                        err: SecretStoreConfigError::NoKey,\n                    })?\n                    .as_str()\n                    .ok_or_else(|| FastlyConfigError::InvalidSecretStoreDefinition {\n                        name: store_name.to_string(),\n                        err: SecretStoreConfigError::KeyNotAString,\n                    })?;\n\n                if !is_valid_name(key) {\n                    return Err(FastlyConfigError::InvalidSecretStoreDefinition {\n                        name: store_name.to_string(),\n                        err: SecretStoreConfigError::InvalidSecretName(key.to_string()),\n                    });\n                }\n\n                let file = item.get(\"file\");\n                let data = item.get(\"data\");\n                let env = item.get(\"env\");\n\n                let sources = [file.is_some(), data.is_some(), env.is_some()];\n                let count = sources.iter().filter(|&&b| b).count();\n\n                let bytes = match count {\n                    0 => {\n                        return Err(FastlyConfigError::InvalidSecretStoreDefinition {\n                            name: store_name.to_string(),\n                            err: SecretStoreConfigError::FileDataEnvNotSet(key.to_string()),\n                        });\n                    }\n                    1 => {\n                        if let Some(path) = file {\n                            let path = path.as_str().ok_or_else(|| {\n                                FastlyConfigError::InvalidSecretStoreDefinition {\n                                    name: store_name.to_string(),\n                                    err: SecretStoreConfigError::FileNotAString(key.to_string()),\n                                }\n                            })?;\n                            fs::read(path).map_err(|e| {\n                                FastlyConfigError::InvalidSecretStoreDefinition {\n                                    name: store_name.to_string(),\n                                    err: SecretStoreConfigError::IoError(e),\n                                }\n                            })?\n                        } else if let Some(data) = data {\n                            data.as_str()\n                                .ok_or_else(|| FastlyConfigError::InvalidSecretStoreDefinition {\n                                    name: store_name.to_string(),\n                                    err: SecretStoreConfigError::DataNotAString(key.to_string()),\n                                })?\n                                .to_owned()\n                                .into()\n                        } else if let Some(env) = env {\n                            // env branch\n                            let var = env.as_str().ok_or_else(|| {\n                                FastlyConfigError::InvalidSecretStoreDefinition {\n                                    name: store_name.to_string(),\n                                    err: SecretStoreConfigError::EnvNotAString(key.to_string()),\n                                }\n                            })?;\n                            std::env::var(var)\n                                .unwrap_or_else(|_| String::new())\n                                .into_bytes()\n                        } else {\n                            unreachable!()\n                        }\n                    }\n                    _ => {\n                        return Err(FastlyConfigError::InvalidSecretStoreDefinition {\n                            name: store_name.to_string(),\n                            err: SecretStoreConfigError::FileDataEnvExclusive(key.to_string()),\n                        });\n                    }\n                };\n                secret_store.add_secret(key.to_string(), bytes.into());\n            }\n            stores.add_store(store_name.clone(), secret_store);\n        }\n        Ok(SecretStoreConfig(stores))\n    }\n}\n\n/// Human-readable names for Secret Stores and Secrets \"must contain\n/// only letters, numbers, dashes (-), underscores (_), and periods (.)\"\n/// They also have a maximum length of 255 bytes.\nfn is_valid_name(name: &str) -> bool {\n    !name.is_empty()\n        && name.len() <= 255\n        && name\n            .chars()\n            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.')\n}\n\nfn read_json_contents(filename: &str) -> Result<HashMap<String, String>, SecretStoreConfigError> {\n    let data = fs::read_to_string(filename).map_err(SecretStoreConfigError::IoError)?;\n    let map: HashMap<String, String> =\n        serde_json::from_str(&data).map_err(|_| SecretStoreConfigError::FileWrongFormat)?;\n    Ok(map)\n}\n"
  },
  {
    "path": "src/config/unit_tests.rs",
    "content": "use {\n    super::{FastlyConfig, LocalServerConfig, RawLocalServerConfig},\n    crate::error::FastlyConfigError,\n    std::{convert::TryInto, fs::File, io::Write},\n    tempfile::tempdir,\n};\n\n/// A test helper used to read the `local_server` section of a config file.\n///\n/// In the interest of brevity, this section works with TOML data that would be placed beneath the\n/// `local_server` key, rather than an entire package manifest as in the tests above.\nfn read_local_server_config(toml: &str) -> Result<LocalServerConfig, FastlyConfigError> {\n    toml::from_str::<'_, RawLocalServerConfig>(toml)\n        .expect(\"valid toml data\")\n        .try_into()\n}\n\n#[test]\nfn error_when_fastly_toml_files_cannot_be_read() {\n    match FastlyConfig::from_file(\"nonexistent.toml\") {\n        Err(FastlyConfigError::IoError { path, .. }) if path == \"nonexistent.toml\" => {}\n        res => panic!(\"unexpected result: {:?}\", res),\n    }\n}\n\n#[test]\nfn fastly_toml_files_can_be_read() {\n    // Parse a valid `fastly.toml`, check that it succeeds.\n    let config = FastlyConfig::from_str(\n        r#\"\n        name = \"simple-toml-example\"\n        description = \"a simple toml example\"\n        authors = [\"Jill Bryson <jbryson@fastly.com>\", \"Rose McDowall <rmcdowall@fastly.com>\"]\n        language = \"rust\"\n    \"#,\n    )\n    .expect(\"can read toml data\");\n\n    // Check that the name, description, authors, and language fields were parsed correctly.\n    assert_eq!(config.name(), \"simple-toml-example\");\n    assert_eq!(config.description(), \"a simple toml example\");\n    assert_eq!(\n        config.authors(),\n        [\n            \"Jill Bryson <jbryson@fastly.com>\",\n            \"Rose McDowall <rmcdowall@fastly.com>\"\n        ]\n    );\n    assert_eq!(config.language(), \"rust\");\n}\n\n/// Show that we can successfully parse a `fastly.toml` with backend configurations.\n///\n/// This provides an example `fastly.toml` file including a `#[local_server.backends]` section. This\n/// includes various backend definitions, that may or may not include an environment key.\n#[test]\nfn fastly_toml_files_with_simple_backend_configurations_can_be_read() {\n    let config = FastlyConfig::from_str(\n        r#\"\n            manifest_version = 3\n            name = \"backend-config-example\"\n            description = \"a toml example with backend configuration\"\n            authors = [\n                \"Amelia Watson <awatson@fastly.com>\",\n                \"Inugami Korone <kinugami@fastly.com>\",\n            ]\n            language = \"rust\"\n\n            [local_server]\n              [local_server.backends]\n                [local_server.backends.dog]\n                url = \"http://localhost:7676/dog-mocks\"\n\n                [local_server.backends.\"shark.server\"]\n                url = \"http://localhost:7676/shark-mocks\"\n                override_host = \"somehost.com\"\n\n                [local_server.backends.detective]\n                url = \"http://www.elementary.org/\"\n    \"#,\n    )\n    .expect(\"can read toml data containing backend configurations\");\n\n    let backend = config\n        .backends()\n        .get(\"dog\")\n        .expect(\"backend configurations can be accessed\");\n    assert_eq!(backend.uri, \"http://localhost:7676/dog-mocks\");\n    assert_eq!(backend.override_host, None);\n\n    let backend = config\n        .backends()\n        .get(\"shark.server\")\n        .expect(\"backend configurations can be accessed\");\n    assert_eq!(backend.uri, \"http://localhost:7676/shark-mocks\");\n    assert_eq!(\n        backend.override_host,\n        Some(\"somehost.com\".parse().expect(\"can parse override_host\"))\n    );\n}\n\n/// Show that we can successfully parse a `fastly.toml` with local_server.dictionaries configurations.\n///\n/// This provides an example `fastly.toml` file including a `#[local_server.dictionaries]` section.\n#[test]\nfn fastly_toml_files_with_simple_dictionary_configurations_can_be_read() {\n    let dir = tempdir().unwrap();\n\n    let file_path = dir.path().join(\"a.json\");\n    let mut file = File::create(&file_path).unwrap();\n    writeln!(file, \"{{}}\").unwrap();\n    let config = FastlyConfig::from_str(format!(\n        r#\"\n            manifest_version = 3\n            name = \"dictionary-config-example\"\n            description = \"a toml example with dictionary configuration\"\n            authors = [\n                \"Amelia Watson <awatson@fastly.com>\",\n                \"Inugami Korone <kinugami@fastly.com>\",\n            ]\n            language = \"rust\"\n\n            [local_server]\n                [local_server.dictionaries]\n                    [local_server.dictionaries.a]\n                    file='{}'\n                    format = \"json\"\n    \"#,\n        &file_path.to_str().unwrap()\n    ))\n    .expect(\"can read toml data containing local dictionary configurations\");\n\n    let dictionary = config\n        .dictionaries()\n        .get(\"a\")\n        .expect(\"dictionary configurations can be accessed\");\n    assert_eq!(dictionary.file_path().unwrap(), file_path);\n    assert!(dictionary.is_json());\n}\n\n/// Show that we can successfully parse a `fastly.toml` with local_server.config_stores configurations.\n///\n/// This provides an example `fastly.toml` file including a `#[local_server.config_stores]` section.\n#[test]\nfn fastly_toml_files_with_simple_config_store_configurations_can_be_read() {\n    let dir = tempdir().unwrap();\n\n    let file_path = dir.path().join(\"a.json\");\n    let mut file = File::create(&file_path).unwrap();\n    writeln!(file, \"{{}}\").unwrap();\n    let config = FastlyConfig::from_str(format!(\n        r#\"\n            manifest_version = 3\n            name = \"dictionary-config-example\"\n            description = \"a toml example with config store configuration\"\n            authors = [\n                \"Amelia Watson <awatson@fastly.com>\",\n                \"Inugami Korone <kinugami@fastly.com>\",\n            ]\n            language = \"rust\"\n\n            [local_server]\n                [local_server.config_stores]\n                    [local_server.config_stores.a]\n                    file='{}'\n                    format = \"json\"\n    \"#,\n        &file_path.to_str().unwrap()\n    ))\n    .expect(\"can read toml data containing local dictionary configurations\");\n\n    let dictionary = config\n        .dictionaries()\n        .get(\"a\")\n        .expect(\"dictionary configurations can be accessed\");\n    assert_eq!(dictionary.file_path().unwrap(), file_path);\n    assert!(dictionary.is_json());\n}\n\n/// Check that the `local_server` section can be deserialized.\n// This case is technically redundant, but it is nice to have a unit test that demonstrates the\n// happy path for this group of unit tests.\n#[test]\nfn local_server_configs_can_be_deserialized() {\n    let dir = tempdir().unwrap();\n    let file_path = dir.path().join(\"secrets.json\");\n    let mut file = File::create(&file_path).unwrap();\n    writeln!(file, \"{{}}\").unwrap();\n\n    let local_server = format!(\n        r#\"\n        [backends]\n          [backends.dog]\n          url = \"http://localhost:7676/dog-mocks\"\n        [dictionaries]\n          [dictionaries.secrets]\n          file = '{}'\n          format = \"json\"\n    \"#,\n        file_path.to_str().unwrap()\n    );\n    match read_local_server_config(&local_server) {\n        Ok(_) => {}\n        res => panic!(\"unexpected result: {:?}\", res),\n    }\n}\n\n/// Unit tests for backends in the `local_server` section of a `fastly.toml` package manifest.\n///\n/// In particular, these tests check that we deserialize and validate the backend configurations\n/// section of the TOML data properly.\nmod backend_config_tests {\n    use {\n        super::read_local_server_config,\n        crate::error::{BackendConfigError, FastlyConfigError::InvalidBackendDefinition},\n    };\n\n    /// Check that backend definitions must be given as TOML tables.\n    #[test]\n    fn backend_configs_must_use_toml_tables() {\n        use BackendConfigError::InvalidEntryType;\n        static BAD_DEF: &str = r#\"\n            [backends]\n            \"shark\" = \"https://a.com\"\n        \"#;\n        match read_local_server_config(BAD_DEF) {\n            Err(InvalidBackendDefinition {\n                err: InvalidEntryType,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that backend definitions cannot contain unrecognized keys.\n    #[test]\n    fn backend_configs_cannot_contain_unrecognized_keys() {\n        use BackendConfigError::UnrecognizedKey;\n        static BAD_DEFAULT: &str = r#\"\n            [backends]\n            shark = { url = \"https://a.com\", shrimp = true }\n        \"#;\n        match read_local_server_config(BAD_DEFAULT) {\n            Err(InvalidBackendDefinition {\n                err: UnrecognizedKey(key),\n                ..\n            }) if key == \"shrimp\" => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that backend definitions *must* include a `url` field.\n    #[test]\n    fn backend_configs_must_provide_a_url() {\n        use BackendConfigError::MissingUrl;\n        static NO_URL: &str = r#\"\n            [backends]\n            \"shark\" = {}\n        \"#;\n        match read_local_server_config(NO_URL) {\n            Err(InvalidBackendDefinition {\n                err: MissingUrl, ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that backend definitions *must* include a `url` field.\n    #[test]\n    fn backend_configs_must_provide_urls_as_a_string() {\n        use BackendConfigError::InvalidUrlEntry;\n        static BAD_URL_FIELD: &str = r#\"\n            [backends]\n            \"shark\" = { url = 3 }\n        \"#;\n        match read_local_server_config(BAD_URL_FIELD) {\n            Err(InvalidBackendDefinition {\n                err: InvalidUrlEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n    /// Check that backend definitions must include a *valid* `url` field.\n    #[test]\n    fn backend_configs_must_provide_a_valid_url() {\n        use BackendConfigError::InvalidUrl;\n        static BAD_URL_FIELD: &str = r#\"\n            [backends]\n            \"shark\" = { url = \"http:://[:::1]\" }\n        \"#;\n        match read_local_server_config(BAD_URL_FIELD) {\n            Err(InvalidBackendDefinition {\n                err: InvalidUrl(_), ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n    /// Check that override_host field is a string.\n    #[test]\n    fn backend_configs_must_provide_override_host_as_a_string() {\n        use BackendConfigError::InvalidOverrideHostEntry;\n        static BAD_OVERRIDE_HOST_FIELD: &str = r#\"\n            [backends]\n            \"shark\" = { url = \"http://a.com\", override_host = 3 }\n        \"#;\n        match read_local_server_config(BAD_OVERRIDE_HOST_FIELD) {\n            Err(InvalidBackendDefinition {\n                err: InvalidOverrideHostEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n    /// Check that override_host field is non empty.\n    #[test]\n    fn backend_configs_must_provide_a_non_empty_override_host() {\n        use BackendConfigError::EmptyOverrideHost;\n        static EMPTY_OVERRIDE_HOST_FIELD: &str = r#\"\n            [backends]\n            \"shark\" = { url = \"http://a.com\", override_host = \"\" }\n        \"#;\n        match read_local_server_config(EMPTY_OVERRIDE_HOST_FIELD) {\n            Err(InvalidBackendDefinition {\n                err: EmptyOverrideHost,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n    /// Check that override_host field is valid.\n    #[test]\n    fn backend_configs_must_provide_a_valid_override_host() {\n        use BackendConfigError::InvalidOverrideHost;\n        static BAD_OVERRIDE_HOST_FIELD: &str = r#\"\n            [backends]\n            \"shark\" = { url = \"http://a.com\", override_host = \"somehost.com\\n\" }\n        \"#;\n        match read_local_server_config(BAD_OVERRIDE_HOST_FIELD) {\n            Err(InvalidBackendDefinition {\n                err: InvalidOverrideHost(_),\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that backend health field accepts valid values.\n    #[test]\n    fn backend_configs_can_provide_health_status() {\n        let config_with_health = r#\"\n            [backends]\n            [backends.healthy_backend]\n            url = \"http://a.com\"\n            health = \"healthy\"\n\n            [backends.unhealthy_backend]\n            url = \"http://b.com\"\n            health = \"unhealthy\"\n\n            [backends.unknown_backend]\n            url = \"http://c.com\"\n            health = \"unknown\"\n\n            [backends.default_backend]\n            url = \"http://d.com\"\n        \"#;\n        let config = read_local_server_config(config_with_health)\n            .expect(\"can read backend config with health status\");\n\n        let healthy = config.backends.0.get(\"healthy_backend\").unwrap();\n        assert_eq!(healthy.health, crate::config::BackendHealth::Healthy);\n\n        let unhealthy = config.backends.0.get(\"unhealthy_backend\").unwrap();\n        assert_eq!(unhealthy.health, crate::config::BackendHealth::Unhealthy);\n\n        let unknown = config.backends.0.get(\"unknown_backend\").unwrap();\n        assert_eq!(unknown.health, crate::config::BackendHealth::Unknown);\n\n        let default = config.backends.0.get(\"default_backend\").unwrap();\n        assert_eq!(default.health, crate::config::BackendHealth::Unknown);\n    }\n\n    /// Check that health field must have valid value.\n    #[test]\n    fn backend_configs_health_must_be_valid() {\n        use BackendConfigError::InvalidHealthEntry;\n        static BAD_HEALTH_FIELD: &str = r#\"\n            [backends]\n            \"shark\" = { url = \"http://a.com\", health = \"sick\" }\n        \"#;\n        match read_local_server_config(BAD_HEALTH_FIELD) {\n            Err(InvalidBackendDefinition {\n                err: InvalidHealthEntry(value),\n                ..\n            }) if value == \"sick\" => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that health field must be a string.\n    #[test]\n    fn backend_configs_health_must_be_string() {\n        use BackendConfigError::InvalidHealthEntry;\n        static BAD_HEALTH_FIELD: &str = r#\"\n            [backends]\n            \"shark\" = { url = \"http://a.com\", health = true }\n        \"#;\n        match read_local_server_config(BAD_HEALTH_FIELD) {\n            Err(InvalidBackendDefinition {\n                err: InvalidHealthEntry(_),\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n}\n\n/// Unit tests for dictionaries/config_stores in the `local_server` section of a `fastly.toml` package manifest.\n///\n/// These tests check that we deserialize and validate the dictionary configurations section of\n/// the TOML data properly regardless of the format.\nmod dictionary_config_tests {\n    use {\n        super::read_local_server_config,\n        crate::error::{DictionaryConfigError, FastlyConfigError::InvalidDictionaryDefinition},\n    };\n\n    /// Check that dictionary definitions have a valid `format`.\n    #[test]\n    fn dictionary_configs_have_a_valid_format() {\n        use DictionaryConfigError::InvalidDictionaryFormat;\n        let invalid_format_field = r#\"\n            [dictionaries.a]\n            format = \"foo\"\n            contents = { apple = \"fruit\", potato = \"vegetable\" }\n        \"#;\n        match read_local_server_config(invalid_format_field) {\n            Err(InvalidDictionaryDefinition {\n                err: InvalidDictionaryFormat(format),\n                ..\n            }) if format == \"foo\" => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that config_store definitions have a valid `format`.\n    #[test]\n    fn config_store_configs_have_a_valid_format() {\n        use DictionaryConfigError::InvalidDictionaryFormat;\n        let invalid_format_field = r#\"\n            [config_stores.a]\n            format = \"foo\"\n            contents = { apple = \"fruit\", potato = \"vegetable\" }\n        \"#;\n        match read_local_server_config(invalid_format_field) {\n            Err(InvalidDictionaryDefinition {\n                err: InvalidDictionaryFormat(format),\n                ..\n            }) if format == \"foo\" => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n}\n\n/// Unit tests for dictionaries/config-stores in the `local_server` section of a `fastly.toml` package manifest.\n///\n/// These tests check that we deserialize and validate the dictionary/config-store configurations section of\n/// the TOML data properly for dictionaries/config-stores using JSON files to store their data.\nmod json_dictionary_config_tests {\n    use {\n        super::read_local_server_config,\n        crate::error::{DictionaryConfigError, FastlyConfigError::InvalidDictionaryDefinition},\n        std::{fs::File, io::Write},\n        tempfile::tempdir,\n    };\n\n    /// Check that dictionary definitions must be given as TOML tables.\n    #[test]\n    fn dictionary_configs_must_use_toml_tables() {\n        use DictionaryConfigError::InvalidEntryType;\n        static BAD_DEF: &str = r#\"\n            [dictionaries]\n            \"thing\" = \"stuff\"\n        \"#;\n        match read_local_server_config(BAD_DEF) {\n            Err(InvalidDictionaryDefinition {\n                err: InvalidEntryType,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that config_store definitions must be given as TOML tables.\n    #[test]\n    fn config_store_configs_must_use_toml_tables() {\n        use DictionaryConfigError::InvalidEntryType;\n        static BAD_DEF: &str = r#\"\n            [config_stores]\n            \"thing\" = \"stuff\"\n        \"#;\n        match read_local_server_config(BAD_DEF) {\n            Err(InvalidDictionaryDefinition {\n                err: InvalidEntryType,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that dictionary definitions cannot contain unrecognized keys.\n    #[test]\n    fn dictionary_configs_cannot_contain_unrecognized_keys() {\n        let dir = tempdir().unwrap();\n        let file_path = dir.path().join(\"secrets.json\");\n        let mut file = File::create(&file_path).unwrap();\n        writeln!(file, \"{{}}\").unwrap();\n\n        use DictionaryConfigError::UnrecognizedKey;\n        let bad_default = format!(\n            r#\"\n            [dictionaries]\n            thing = {{ file = '{}', format = \"json\", shrimp = true }}\n        \"#,\n            file_path.to_str().unwrap()\n        );\n        match read_local_server_config(&bad_default) {\n            Err(InvalidDictionaryDefinition {\n                err: UnrecognizedKey(key),\n                ..\n            }) if key == \"shrimp\" => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that config_store definitions cannot contain unrecognized keys.\n    #[test]\n    fn config_store_configs_cannot_contain_unrecognized_keys() {\n        let dir = tempdir().unwrap();\n        let file_path = dir.path().join(\"secrets.json\");\n        let mut file = File::create(&file_path).unwrap();\n        writeln!(file, \"{{}}\").unwrap();\n\n        use DictionaryConfigError::UnrecognizedKey;\n        let bad_default = format!(\n            r#\"\n            [config_stores]\n            thing = {{ file = '{}', format = \"json\", shrimp = true }}\n        \"#,\n            file_path.to_str().unwrap()\n        );\n        match read_local_server_config(&bad_default) {\n            Err(InvalidDictionaryDefinition {\n                err: UnrecognizedKey(key),\n                ..\n            }) if key == \"shrimp\" => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that dictionary definitions *must* include a `file` field.\n    #[test]\n    fn dictionary_configs_must_provide_a_file() {\n        use DictionaryConfigError::MissingFile;\n        static NO_FILE: &str = r#\"\n            [dictionaries]\n            thing = {format = \"json\"}\n        \"#;\n        match read_local_server_config(NO_FILE) {\n            Err(InvalidDictionaryDefinition {\n                err: MissingFile, ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that config_store definitions *must* include a `file` field.\n    #[test]\n    fn config_store_configs_must_provide_a_file() {\n        use DictionaryConfigError::MissingFile;\n        static NO_FILE: &str = r#\"\n            [config_stores]\n            thing = {format = \"json\"}\n        \"#;\n        match read_local_server_config(NO_FILE) {\n            Err(InvalidDictionaryDefinition {\n                err: MissingFile, ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that dictionary definitions *must* include a `format` field.\n    #[test]\n    fn dictionary_configs_must_provide_a_format() {\n        use DictionaryConfigError::MissingFormat;\n        let dir = tempdir().unwrap();\n        let file_path = dir.path().join(\"secrets.json\");\n        let mut file = File::create(&file_path).unwrap();\n        writeln!(file, \"{{}}\").unwrap();\n\n        let no_format_field = format!(\n            r#\"\n            [dictionaries]\n            \"thing\" = {{ file = '{}' }}\n        \"#,\n            file_path.to_str().unwrap()\n        );\n        match read_local_server_config(&no_format_field) {\n            Err(InvalidDictionaryDefinition {\n                err: MissingFormat, ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that config_store definitions *must* include a `format` field.\n    #[test]\n    fn config_store_configs_must_provide_a_format() {\n        use DictionaryConfigError::MissingFormat;\n        let dir = tempdir().unwrap();\n        let file_path = dir.path().join(\"secrets.json\");\n        let mut file = File::create(&file_path).unwrap();\n        writeln!(file, \"{{}}\").unwrap();\n\n        let no_format_field = format!(\n            r#\"\n            [config_stores]\n            \"thing\" = {{ file = '{}' }}\n        \"#,\n            file_path.to_str().unwrap()\n        );\n        match read_local_server_config(&no_format_field) {\n            Err(InvalidDictionaryDefinition {\n                err: MissingFormat, ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that file field is a string.\n    #[test]\n    fn dictionary_configs_must_provide_file_as_a_string() {\n        use DictionaryConfigError::InvalidFileEntry;\n        static BAD_FILE_FIELD: &str = r#\"\n            [dictionaries]\n            \"thing\" = { file = 3, format = \"json\" }\n        \"#;\n        match read_local_server_config(BAD_FILE_FIELD) {\n            Err(InvalidDictionaryDefinition {\n                err: InvalidFileEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that file field is a string.\n    #[test]\n    fn config_store_configs_must_provide_file_as_a_string() {\n        use DictionaryConfigError::InvalidFileEntry;\n        static BAD_FILE_FIELD: &str = r#\"\n            [config_stores]\n            \"thing\" = { file = 3, format = \"json\" }\n        \"#;\n        match read_local_server_config(BAD_FILE_FIELD) {\n            Err(InvalidDictionaryDefinition {\n                err: InvalidFileEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that file field is non empty.\n    #[test]\n    fn dictionary_configs_must_provide_a_non_empty_file() {\n        use DictionaryConfigError::EmptyFileEntry;\n        static EMPTY_FILE_FIELD: &str = r#\"\n            [dictionaries]\n            \"thing\" = { file = \"\", format = \"json\" }\n        \"#;\n        match read_local_server_config(EMPTY_FILE_FIELD) {\n            Err(InvalidDictionaryDefinition {\n                err: EmptyFileEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that file field is non empty.\n    #[test]\n    fn config_store_configs_must_provide_a_non_empty_file() {\n        use DictionaryConfigError::EmptyFileEntry;\n        static EMPTY_FILE_FIELD: &str = r#\"\n            [config_stores]\n            \"thing\" = { file = \"\", format = \"json\" }\n        \"#;\n        match read_local_server_config(EMPTY_FILE_FIELD) {\n            Err(InvalidDictionaryDefinition {\n                err: EmptyFileEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that format field is a string.\n    #[test]\n    fn dictionary_configs_must_provide_format_as_a_string() {\n        use DictionaryConfigError::InvalidFormatEntry;\n        static BAD_FORMAT_FIELD: &str = r#\"\n            [dictionaries]\n            \"thing\" = { format = 3}\n        \"#;\n        match read_local_server_config(BAD_FORMAT_FIELD) {\n            Err(InvalidDictionaryDefinition {\n                err: InvalidFormatEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that format field is a string.\n    #[test]\n    fn config_store_configs_must_provide_format_as_a_string() {\n        use DictionaryConfigError::InvalidFormatEntry;\n        static BAD_FORMAT_FIELD: &str = r#\"\n            [config_stores]\n            \"thing\" = { format = 3}\n        \"#;\n        match read_local_server_config(BAD_FORMAT_FIELD) {\n            Err(InvalidDictionaryDefinition {\n                err: InvalidFormatEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that format field is non empty.\n    #[test]\n    fn dictionary_configs_must_provide_a_non_empty_format() {\n        use DictionaryConfigError::EmptyFormatEntry;\n        static EMPTY_FORMAT_FIELD: &str = r#\"\n            [dictionaries]\n            \"thing\" = { format = \"\" }\n        \"#;\n        match read_local_server_config(EMPTY_FORMAT_FIELD) {\n            Err(InvalidDictionaryDefinition {\n                err: EmptyFormatEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that format field is non empty.\n    #[test]\n    fn config_store_configs_must_provide_a_non_empty_format() {\n        use DictionaryConfigError::EmptyFormatEntry;\n        static EMPTY_FORMAT_FIELD: &str = r#\"\n            [config_stores]\n            \"thing\" = { format = \"\" }\n        \"#;\n        match read_local_server_config(EMPTY_FORMAT_FIELD) {\n            Err(InvalidDictionaryDefinition {\n                err: EmptyFormatEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that format field set to json is valid.\n    #[test]\n    fn valid_dictionary_config_with_format_set_to_json() {\n        let dir = tempdir().unwrap();\n        let file_path = dir.path().join(\"secrets.json\");\n        let mut file = File::create(&file_path).unwrap();\n        writeln!(file, \"{{}}\").unwrap();\n\n        let dictionary = format!(\n            r#\"\n            [dictionaries]\n            \"thing\" = {{ file = '{}', format = \"json\" }}\n        \"#,\n            file_path.to_str().unwrap()\n        );\n        read_local_server_config(&dictionary).expect(\n            \"can read toml data containing local dictionary configurations using json format\",\n        );\n    }\n\n    /// Check that format field set to json is valid.\n    #[test]\n    fn valid_config_store_config_with_format_set_to_json() {\n        let dir = tempdir().unwrap();\n        let file_path = dir.path().join(\"secrets.json\");\n        let mut file = File::create(&file_path).unwrap();\n        writeln!(file, \"{{}}\").unwrap();\n\n        let dictionary = format!(\n            r#\"\n            [config_stores]\n            \"thing\" = {{ file = '{}', format = \"json\" }}\n        \"#,\n            file_path.to_str().unwrap()\n        );\n        read_local_server_config(&dictionary).expect(\n            \"can read toml data containing local dictionary configurations using json format\",\n        );\n    }\n}\n\n/// Unit tests for dictionaries/config_stores in the `local_server` section of a `fastly.toml` package manifest.\n///\n/// These tests check that we deserialize and validate the dictionary configurations section of\n/// the TOML data properly for dictionaries/config_stores using inline TOML to store their data.\nmod inline_toml_dictionary_config_tests {\n    use {\n        super::read_local_server_config,\n        crate::error::{DictionaryConfigError, FastlyConfigError::InvalidDictionaryDefinition},\n    };\n\n    #[test]\n    fn valid_inline_toml_dictionaries_can_be_parsed() {\n        let dictionary = r#\"\n            [dictionaries.inline_toml_example]\n            format = \"inline-toml\"\n            contents = { apple = \"fruit\", potato = \"vegetable\" }\n        \"#;\n        read_local_server_config(dictionary).expect(\n            \"can read toml data containing local dictionary configurations using json format\",\n        );\n    }\n\n    #[test]\n    fn valid_inline_toml_config_stores_can_be_parsed() {\n        let dictionary = r#\"\n            [config_stores.inline_toml_example]\n            format = \"inline-toml\"\n            contents = { apple = \"fruit\", potato = \"vegetable\" }\n        \"#;\n        read_local_server_config(dictionary).expect(\n            \"can read toml data containing local dictionary configurations using json format\",\n        );\n    }\n\n    /// Check that dictionary definitions *must* include a `format` field.\n    #[test]\n    fn dictionary_configs_must_provide_a_format() {\n        use DictionaryConfigError::MissingFormat;\n        let no_format_field = r#\"\n            [dictionaries.missing_format]\n            contents = { apple = \"fruit\", potato = \"vegetable\" }\n        \"#;\n        match read_local_server_config(no_format_field) {\n            Err(InvalidDictionaryDefinition {\n                err: MissingFormat, ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that config_store definitions *must* include a `format` field.\n    #[test]\n    fn config_store_configs_must_provide_a_format() {\n        use DictionaryConfigError::MissingFormat;\n        let no_format_field = r#\"\n            [config_stores.missing_format]\n            contents = { apple = \"fruit\", potato = \"vegetable\" }\n        \"#;\n        match read_local_server_config(no_format_field) {\n            Err(InvalidDictionaryDefinition {\n                err: MissingFormat, ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that dictionary definitions *must* include a `contents` field.\n    #[test]\n    fn dictionary_configs_must_provide_contents() {\n        use DictionaryConfigError::MissingContents;\n        let missing_contents = r#\"\n            [dictionaries.missing_contents]\n            format = \"inline-toml\"\n        \"#;\n        match read_local_server_config(missing_contents) {\n            Err(InvalidDictionaryDefinition {\n                err: MissingContents,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that config_store definitions *must* include a `contents` field.\n    #[test]\n    fn config_store_configs_must_provide_contents() {\n        use DictionaryConfigError::MissingContents;\n        let missing_contents = r#\"\n            [config_stores.missing_contents]\n            format = \"inline-toml\"\n        \"#;\n        match read_local_server_config(missing_contents) {\n            Err(InvalidDictionaryDefinition {\n                err: MissingContents,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n}\n\n/// Unit tests for Device Detection mapping in the `local_server` section of a `fastly.toml` package manifest.\n///\n/// These tests check that we deserialize and validate the Device Detection mappings section of\n/// the TOML data properly regardless of the format.\nmod device_detection_config_tests {\n    use {\n        super::read_local_server_config,\n        crate::error::{\n            DeviceDetectionConfigError, FastlyConfigError::InvalidDeviceDetectionDefinition,\n        },\n    };\n\n    /// Check that Device Detection definitions have a valid `format`.\n    #[test]\n    fn device_detection_has_a_valid_format() {\n        use DeviceDetectionConfigError::InvalidDeviceDetectionMappingFormat;\n        let invalid_format_field = r#\"\n            [device_detection]\n            format = \"foo\"\n            [device_detection.user_agent.\"Test User-Agent\"]\n            hwtype = \"Test\"\n        \"#;\n        match read_local_server_config(invalid_format_field) {\n            Err(InvalidDeviceDetectionDefinition {\n                err: InvalidDeviceDetectionMappingFormat(format),\n                ..\n            }) if format == \"foo\" => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n}\n\n/// Unit tests for Geolocation mapping in the `local_server` section of a `fastly.toml` package manifest.\n///\n/// These tests check that we deserialize and validate the Geolocation mappings section of\n/// the TOML data properly regardless of the format.\nmod geolocation_config_tests {\n    use {\n        super::read_local_server_config,\n        crate::error::{FastlyConfigError::InvalidGeolocationDefinition, GeolocationConfigError},\n    };\n\n    /// Check that Geolocation definitions have a valid `format`.\n    #[test]\n    fn geolocation_has_a_valid_format() {\n        use GeolocationConfigError::InvalidGeolocationMappingFormat;\n        let invalid_format_field = r#\"\n            [geolocation]\n            format = \"foo\"\n            [geolocation.addresses.\"123.45.67.89\"]\n            as_name = \"Test, Inc.\"\n        \"#;\n        match read_local_server_config(invalid_format_field) {\n            Err(InvalidGeolocationDefinition {\n                err: InvalidGeolocationMappingFormat(format),\n                ..\n            }) if format == \"foo\" => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n}\n\n/// Unit tests for Geolocation mapping in the `local_server` section of a `fastly.toml` package manifest.\n///\n/// These tests check that we deserialize and validate the dictionary configurations section of\n/// the TOML data properly for Geolocation mapping using JSON files to store their data.\nmod json_geolocation_config_tests {\n    use {\n        super::read_local_server_config,\n        crate::error::{FastlyConfigError::InvalidGeolocationDefinition, GeolocationConfigError},\n        std::{fs::File, io::Write},\n        tempfile::tempdir,\n    };\n\n    /// Check that Geolocation mapping *must* include a `file` field.\n    #[test]\n    fn geolocation_must_provide_a_file() {\n        use GeolocationConfigError::MissingFile;\n        static NO_FILE: &str = r#\"\n            [geolocation]\n            format = \"json\"\n        \"#;\n        match read_local_server_config(NO_FILE) {\n            Err(InvalidGeolocationDefinition {\n                err: MissingFile, ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that file field is a string.\n    #[test]\n    fn geolocation_must_provide_file_as_a_string() {\n        use GeolocationConfigError::InvalidFileEntry;\n        static BAD_FILE_FIELD: &str = r#\"\n            [geolocation]\n            file = 3\n            format = \"json\"\n        \"#;\n        match read_local_server_config(BAD_FILE_FIELD) {\n            Err(InvalidGeolocationDefinition {\n                err: InvalidFileEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that file field is non empty.\n    #[test]\n    fn geolocation_must_provide_a_non_empty_file() {\n        use GeolocationConfigError::EmptyFileEntry;\n        static EMPTY_FILE_FIELD: &str = r#\"\n            [geolocation]\n            file = \"\"\n            format = \"json\"\n        \"#;\n        match read_local_server_config(EMPTY_FILE_FIELD) {\n            Err(InvalidGeolocationDefinition {\n                err: EmptyFileEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that format field is a string.\n    #[test]\n    fn geolocation_must_provide_format_as_a_string() {\n        use GeolocationConfigError::InvalidFormatEntry;\n        static BAD_FORMAT_FIELD: &str = r#\"\n            [geolocation]\n            format = 3\n        \"#;\n        match read_local_server_config(BAD_FORMAT_FIELD) {\n            Err(InvalidGeolocationDefinition {\n                err: InvalidFormatEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that format field is non empty.\n    #[test]\n    fn geolocation_must_provide_a_non_empty_format() {\n        use GeolocationConfigError::EmptyFormatEntry;\n        static EMPTY_FORMAT_FIELD: &str = r#\"\n            [geolocation]\n            format = \"\"\n        \"#;\n        match read_local_server_config(EMPTY_FORMAT_FIELD) {\n            Err(InvalidGeolocationDefinition {\n                err: EmptyFormatEntry,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n\n    /// Check that format field set to json is valid.\n    #[test]\n    fn valid_geolocation_with_format_set_to_json() {\n        let dir = tempdir().unwrap();\n        let file_path = dir.path().join(\"mapping.json\");\n        let mut file = File::create(&file_path).unwrap();\n        writeln!(file, \"{{}}\").unwrap();\n\n        let dictionary = format!(\n            r#\"\n            [geolocation]\n            file = '{}'\n            format = \"json\"\n        \"#,\n            file_path.to_str().unwrap()\n        );\n        read_local_server_config(&dictionary).expect(\n            \"can read toml data containing local dictionary configurations using json format\",\n        );\n    }\n}\n\n/// Unit tests for Geolocation mapping in the `local_server` section of a `fastly.toml` package manifest.\n///\n/// These tests check that we deserialize and validate the dictionary configurations section of\n/// the TOML data properly for Geolocation mapping using inline TOML to store their data.\nmod inline_toml_geolocation_config_tests {\n    use {\n        super::read_local_server_config,\n        crate::error::{FastlyConfigError::InvalidGeolocationDefinition, GeolocationConfigError},\n    };\n\n    #[test]\n    fn valid_inline_toml_geolocation_can_be_parsed() {\n        let geolocation = r#\"\n            [geolocation]\n            format = \"inline-toml\"\n            [geolocation.addresses]\n            [geolocation.addresses.\"127.0.0.1\"]\n            as_name = \"Test, Inc.\"\n        \"#;\n        read_local_server_config(geolocation)\n            .expect(\"can read toml data containing local Geolocation mappings using toml format\");\n    }\n\n    /// Check that Geolocation mapping *must* include a `contents` field.\n    #[test]\n    fn geolocation_must_provide_contents() {\n        use GeolocationConfigError::MissingAddresses;\n        let missing_contents = r#\"\n            [geolocation]\n            format = \"inline-toml\"\n        \"#;\n        match read_local_server_config(missing_contents) {\n            Err(InvalidGeolocationDefinition {\n                err: MissingAddresses,\n                ..\n            }) => {}\n            res => panic!(\"unexpected result: {:?}\", res),\n        }\n    }\n}\n\nmod ca_cert_config_tests {\n    use super::read_local_server_config;\n\n    #[test]\n    fn ca_certs_default_to_empty() {\n        let standard_backend = r#\"\n            [backends]\n            [backends.dog]\n            url = \"http://localhost:7676/dog-mocks\"\n        \"#;\n\n        let basic = read_local_server_config(standard_backend).expect(\"can parse basic config\");\n        let dog_backend = basic.backends.0.get(\"dog\").expect(\"fetch failed :(\");\n        assert!(dog_backend.ca_certs.is_empty());\n    }\n\n    #[test]\n    fn reads_ca_certs() {\n        let ca_backend = r#\"\n[backends]\n[backends.dog]\nurl = \"http://localhost:7676/dog-mocks\"\n\n[backends.\"shark.server\"]\nurl = \"http://localhost:7676/shark-mocks\"\noverride_host = \"somehost.com\"\nca_certificate = '''\n-----BEGIN CERTIFICATE-----\nMIIDqTCCApGgAwIBAgIUDXDr/2fouphqlB8iJASenWOr/XwwDQYJKoZIhvcNAQEL\nBQAwZDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk9yZWdvbjERMA8GA1UEBwwIUG9y\ndGxhbmQxEDAOBgNVBAoMB1ZpY2Vyb3kxHzAdBgkqhkiG9w0BCQEWEGF3aWNrQGZh\nc3RseS5jb20wHhcNMjMwNzI3MDAwODU5WhcNMzMwNzI0MDAwODU5WjBkMQswCQYD\nVQQGEwJVUzEPMA0GA1UECAwGT3JlZ29uMREwDwYDVQQHDAhQb3J0bGFuZDEQMA4G\nA1UECgwHVmljZXJveTEfMB0GCSqGSIb3DQEJARYQYXdpY2tAZmFzdGx5LmNvbTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKxXdG4C6yEeLTtFPOXWTv1N\neEeJMLcAoupB9u3x0PYT+w+0ruAympviqGbEiyZL/qMKLYenLiQO+72VCISW5qfB\nZoCpwDxBon5TDUZ98JU93nVRml7uOg25G+KTs3aeJt6+rFDPNaNyxVcKgCuURB4y\nmwgosLUvxoEffFnHlURETLN4aSGQ6TLp8YEJp4EudTVo/l+kdhm6sLZMBkmUxnnl\nmuEc8ePAr1igYchz2tbcWRjzxoUOuEdoKaW2OCElNObt2WYPWzHs+6p1K8+KyTRY\n/pVOFtA43nuWmk++UHFthBAw9IqBuO0FMJr4SULnKfiTh5E9F+nZ0Q/1nfzzsAMC\nAwEAAaNTMFEwHQYDVR0OBBYEFGYM6HhP8yZ17eXw5nOfQ971u1l9MB8GA1UdIwQY\nMBaAFGYM6HhP8yZ17eXw5nOfQ971u1l9MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAFmFkUodKTXeT683GEj4SoiMbDL8d3x+Vc+kvLPC2Jloru4R\nQo0USu3eJZjNxKjmPbLii8gzf5ZmZHdytWQ+5irYjXBHrE9tPgmpavhM+0otpnUd\nvYosnfwv/aQEIiqeMkpqzbSKvb2I+TVpAC1xb6qbYE95tnsX/KEdAoJ/SAcZLGYQ\nLKGTjz3eKlgUWy69uwzHXkie8hxDVRlyA7cFY4AAqsLhL2KQPWtMT7fRKrVKfLYd\nQq7tJAMLnPnAdAUousI0RDcLpB8adGkhZH66lL4oV9U+aQ0dA0oiqSKZtMoHeWbr\n/L0ti7ZOfxOxRRCzt8KdLo/kGNTfAz+74P0MY80=\n-----END CERTIFICATE-----\n'''\n\"#;\n\n        let with_ca = read_local_server_config(ca_backend).expect(\"can parse backends with ca\");\n        let dog_backend = with_ca.backends.0.get(\"dog\").expect(\"fetch failed :(\");\n        assert!(dog_backend.ca_certs.is_empty());\n        let shark_backend = with_ca\n            .backends\n            .0\n            .get(\"shark.server\")\n            .expect(\"no blåhaj :(\");\n        assert!(!shark_backend.ca_certs.is_empty());\n    }\n\n    #[test]\n    fn reads_file_path_ca_certs() {\n        let ca_backend = format!(\n            r#\"\n[backends]\n[backends.dog]\nurl = \"http://localhost:7676/dog-mocks\"\n\n[backends.\"shark.server\"]\nurl = \"http://localhost:7676/shark-mocks\"\noverride_host = \"somehost.com\"\nca_certificate.file = {:?}\n\"#,\n            concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/test-fixtures/data/ca.pem\")\n        );\n\n        let with_ca = read_local_server_config(&ca_backend).expect(\"can parse backends with ca\");\n        let dog_backend = with_ca.backends.0.get(\"dog\").expect(\"fetch failed :(\");\n        assert!(dog_backend.ca_certs.is_empty());\n        let shark_backend = with_ca\n            .backends\n            .0\n            .get(\"shark.server\")\n            .expect(\"no blåhaj :(\");\n        assert!(!shark_backend.ca_certs.is_empty());\n    }\n\n    #[test]\n    fn reads_multiple_ca_certs() {\n        let ca_backend = format!(\n            r#\"\n[backends]\n[backends.dog]\nurl = \"http://localhost:7676/dog-mocks\"\n\n[backends.\"shark.server\"]\nurl = \"http://localhost:7676/shark-mocks\"\noverride_host = \"somehost.com\"\n[[backends.\"shark.server\".ca_certificate]]\nfile = {:?}\n[[backends.\"shark.server\".ca_certificate]]\nfile = {:?}\n[[backends.\"shark.server\".ca_certificate]]\nvalue = '''\n-----BEGIN CERTIFICATE-----\nMIIDqTCCApGgAwIBAgIUDXDr/2fouphqlB8iJASenWOr/XwwDQYJKoZIhvcNAQEL\nBQAwZDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk9yZWdvbjERMA8GA1UEBwwIUG9y\ndGxhbmQxEDAOBgNVBAoMB1ZpY2Vyb3kxHzAdBgkqhkiG9w0BCQEWEGF3aWNrQGZh\nc3RseS5jb20wHhcNMjMwNzI3MDAwODU5WhcNMzMwNzI0MDAwODU5WjBkMQswCQYD\nVQQGEwJVUzEPMA0GA1UECAwGT3JlZ29uMREwDwYDVQQHDAhQb3J0bGFuZDEQMA4G\nA1UECgwHVmljZXJveTEfMB0GCSqGSIb3DQEJARYQYXdpY2tAZmFzdGx5LmNvbTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKxXdG4C6yEeLTtFPOXWTv1N\neEeJMLcAoupB9u3x0PYT+w+0ruAympviqGbEiyZL/qMKLYenLiQO+72VCISW5qfB\nZoCpwDxBon5TDUZ98JU93nVRml7uOg25G+KTs3aeJt6+rFDPNaNyxVcKgCuURB4y\nmwgosLUvxoEffFnHlURETLN4aSGQ6TLp8YEJp4EudTVo/l+kdhm6sLZMBkmUxnnl\nmuEc8ePAr1igYchz2tbcWRjzxoUOuEdoKaW2OCElNObt2WYPWzHs+6p1K8+KyTRY\n/pVOFtA43nuWmk++UHFthBAw9IqBuO0FMJr4SULnKfiTh5E9F+nZ0Q/1nfzzsAMC\nAwEAAaNTMFEwHQYDVR0OBBYEFGYM6HhP8yZ17eXw5nOfQ971u1l9MB8GA1UdIwQY\nMBaAFGYM6HhP8yZ17eXw5nOfQ971u1l9MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAFmFkUodKTXeT683GEj4SoiMbDL8d3x+Vc+kvLPC2Jloru4R\nQo0USu3eJZjNxKjmPbLii8gzf5ZmZHdytWQ+5irYjXBHrE9tPgmpavhM+0otpnUd\nvYosnfwv/aQEIiqeMkpqzbSKvb2I+TVpAC1xb6qbYE95tnsX/KEdAoJ/SAcZLGYQ\nLKGTjz3eKlgUWy69uwzHXkie8hxDVRlyA7cFY4AAqsLhL2KQPWtMT7fRKrVKfLYd\nQq7tJAMLnPnAdAUousI0RDcLpB8adGkhZH66lL4oV9U+aQ0dA0oiqSKZtMoHeWbr\n/L0ti7ZOfxOxRRCzt8KdLo/kGNTfAz+74P0MY80=\n-----END CERTIFICATE-----\n'''\n\"#,\n            concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/test-fixtures/data/ca.pem\"),\n            concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/test-fixtures/data/ca.pem\")\n        );\n\n        let with_ca = read_local_server_config(&ca_backend).expect(\"can parse backends with ca\");\n        let dog_backend = with_ca.backends.0.get(\"dog\").expect(\"fetch failed :(\");\n        assert!(dog_backend.ca_certs.is_empty());\n        let shark_backend = with_ca\n            .backends\n            .0\n            .get(\"shark.server\")\n            .expect(\"no blåhaj :(\");\n        assert_eq!(3, shark_backend.ca_certs.len());\n    }\n}\n\n#[test]\nfn fastly_toml_files_with_unsupported_manifest_version() {\n    let err = FastlyConfig::from_str(\n        r#\"\n            manifest_version = 4\n            name = \"backend-config-example\"\n            language = \"rust\"\n    \"#,\n    )\n    .expect_err(\"an unsupported manifest_version should have evoked an error\");\n\n    assert!(matches!(\n        err,\n        FastlyConfigError::UnsupportedManifestVersion(4, 3)\n    ));\n}\n\n#[test]\nfn fastly_toml_with_fake_valid_fastly_keys() {\n    let config = FastlyConfig::from_str(\n        r#\"\n            manifest_version = 3\n            name = \"api-keys-example\"\n            language = \"rust\"\n\n            [local_server]\n            fake_valid_fastly_keys = [\"test-key-1\", \"test-key-2\"]\n    \"#,\n    )\n    .expect(\"can read toml data\");\n\n    let keys = config.fake_valid_fastly_keys();\n    assert_eq!(keys.len(), 2);\n    assert!(keys.contains(\"test-key-1\"));\n    assert!(keys.contains(\"test-key-2\"));\n}\n\n#[test]\nfn fastly_toml_without_fake_valid_fastly_keys_defaults_to_empty() {\n    let config = FastlyConfig::from_str(\n        r#\"\n            manifest_version = 3\n            name = \"no-api-keys-example\"\n            language = \"rust\"\n    \"#,\n    )\n    .expect(\"can read toml data\");\n\n    assert!(config.fake_valid_fastly_keys().is_empty());\n}\n\n#[test]\nfn fastly_toml_with_empty_fake_valid_fastly_keys() {\n    let config = FastlyConfig::from_str(\n        r#\"\n            manifest_version = 3\n            name = \"empty-api-keys-example\"\n            language = \"rust\"\n\n            [local_server]\n            fake_valid_fastly_keys = []\n    \"#,\n    )\n    .expect(\"can read toml data\");\n\n    assert!(config.fake_valid_fastly_keys().is_empty());\n}\n"
  },
  {
    "path": "src/config.rs",
    "content": "//! Fastly-specific configuration utilities.\n\nuse {\n    self::{\n        acl::AclConfig, backends::BackendsConfig, dictionaries::DictionariesConfig,\n        object_store::ObjectStoreConfig, secret_store::SecretStoreConfig,\n    },\n    crate::error::FastlyConfigError,\n    serde_derive::Deserialize,\n    std::{\n        collections::{HashMap, HashSet},\n        convert::TryInto,\n        fs,\n        path::Path,\n        str::FromStr,\n        sync::Arc,\n    },\n    toml::value::Table,\n};\n\n/// Unit tests for the [`FastlyConfig`] and [`TestingConfig`] types.\n#[cfg(test)]\nmod unit_tests;\n\n/// Fastly limits\nmod limits;\n\n/// Types and deserializers for dictionaries configuration settings.\nmod dictionaries;\n\npub use self::dictionaries::{Dictionary, LoadedDictionary};\n\npub type Dictionaries = HashMap<String, Dictionary>;\n\n/// Types and deserializers for acl configuration settings.\nmod acl;\npub use crate::acl::Acls;\n\n/// Types and deserializers for backend configuration settings.\nmod backends;\n\npub use self::backends::{Backend, BackendHealth, ClientCertError, ClientCertInfo};\n\npub type Backends = HashMap<String, Arc<Backend>>;\n\n/// Types and deserializers for device detection configuration settings.\nmod device_detection;\n\npub use self::device_detection::DeviceDetection;\n\n/// Types and deserializers for geolocation configuration settings.\nmod geolocation;\n\npub use self::geolocation::Geolocation;\n\n/// Types and deserializers for object store configuration settings.\nmod object_store;\n\npub use crate::object_store::ObjectStores;\n\n/// Types and deserializers for secret store configuration settings.\nmod secret_store;\npub use crate::secret_store::{SecretStore, SecretStores};\n\npub use crate::shielding_site::ShieldingSites;\n\n/// A set of fake valid Fastly keys for testing `fastly_key_is_valid`.\n///\n/// Real Fastly API keys should never be used in local testing. These are fake\n/// values used solely to exercise the `fastly_key_is_valid` hostcall in Viceroy.\npub type FakeValidFastlyKeys = HashSet<String>;\n\n/// Fastly-specific configuration information.\n///\n/// This `struct` represents the fields and values in a Compute package's `fastly.toml`.\n#[derive(Debug, Clone)]\npub struct FastlyConfig {\n    name: String,\n    description: String,\n    authors: Vec<String>,\n    language: String,\n    local_server: LocalServerConfig,\n}\n\nimpl FastlyConfig {\n    /// Get a reference to the package name.\n    pub fn name(&self) -> &str {\n        self.name.as_str()\n    }\n\n    /// Get a reference to the package description.\n    pub fn description(&self) -> &str {\n        self.description.as_str()\n    }\n\n    /// Get a reference to the package authors.\n    pub fn authors(&self) -> &[String] {\n        self.authors.as_ref()\n    }\n\n    /// Get a reference to the package language.\n    pub fn language(&self) -> &str {\n        self.language.as_str()\n    }\n\n    /// Get the acl configuration.\n    pub fn acls(&self) -> &Acls {\n        &self.local_server.acls.0\n    }\n\n    /// Get the backend configuration.\n    pub fn backends(&self) -> &Backends {\n        &self.local_server.backends.0\n    }\n\n    /// Get the device detection configuration.\n    pub fn device_detection(&self) -> &DeviceDetection {\n        &self.local_server.device_detection\n    }\n\n    /// Get the geolocation configuration.\n    pub fn geolocation(&self) -> &Geolocation {\n        &self.local_server.geolocation\n    }\n\n    /// Get the dictionaries configuration.\n    pub fn dictionaries(&self) -> &Dictionaries {\n        &self.local_server.dictionaries.0\n    }\n\n    /// Get the object store configuration.\n    pub fn object_stores(&self) -> &ObjectStores {\n        &self.local_server.object_stores.0\n    }\n\n    /// Get the secret store configuration.\n    pub fn secret_stores(&self) -> &SecretStores {\n        &self.local_server.secret_stores.0\n    }\n    /// Get the shielding site configuration.\n    pub fn shielding_sites(&self) -> &ShieldingSites {\n        &self.local_server.shielding_sites\n    }\n\n    /// Get the valid mock Fastly API keys configuration.\n    pub fn fake_valid_fastly_keys(&self) -> &FakeValidFastlyKeys {\n        &self.local_server.fake_valid_fastly_keys\n    }\n\n    /// Parse a `fastly.toml` file into a `FastlyConfig`.\n    pub fn from_file(path: impl AsRef<Path>) -> Result<Self, FastlyConfigError> {\n        fs::read_to_string(path.as_ref())\n            .map_err(|err| FastlyConfigError::IoError {\n                path: path.as_ref().display().to_string(),\n                err,\n            })\n            .and_then(Self::from_str)\n    }\n\n    /// Parse a string containing TOML data into a `FastlyConfig`.\n    fn from_str(toml: impl AsRef<str>) -> Result<Self, FastlyConfigError> {\n        toml::from_str::<'_, TomlFastlyConfig>(toml.as_ref())\n            .map_err(Into::into)\n            .and_then(TryInto::try_into)\n    }\n}\n\nimpl FromStr for FastlyConfig {\n    type Err = FastlyConfigError;\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Self::from_str(s)\n    }\n}\n\n/// Internal deserializer used to read data from a `fastly.toml` file.\n///\n/// Once a TOML file has been read using [`toml::from_str`][from-str], this can be converted into\n/// a [`FastlyConfig`][conf].\n///\n/// [conf]: struct.FastlyConfig.html\n/// [from-str]: https://docs.rs/toml/latest/toml/de/fn.from_str.html\n#[derive(Deserialize)]\nstruct TomlFastlyConfig {\n    manifest_version: Option<u32>,\n    local_server: Option<RawLocalServerConfig>,\n    // AJT 2020.03.10: the following fields are marked as optional because, for the time being,\n    // we are not expecting to actually use the fastly.toml manifest, but instead use a separate\n    // TOML file for backend configuration.\n    //\n    // See https://github.com/fastly/Viceroy/issues/109 for additional context.\n    name: Option<String>,\n    description: Option<String>,\n    authors: Option<Vec<String>>,\n    language: Option<String>,\n}\n\n/// Manifest versions greater than this are not yet supported.\nconst MAX_MANIFEST_VERSION: u32 = 3;\n\nimpl TryInto<FastlyConfig> for TomlFastlyConfig {\n    type Error = FastlyConfigError;\n    fn try_into(self) -> Result<FastlyConfig, Self::Error> {\n        let Self {\n            manifest_version,\n            name,\n            description,\n            authors,\n            language,\n            local_server,\n        } = self;\n\n        if let Some(manifest_version) = manifest_version\n            && manifest_version > MAX_MANIFEST_VERSION\n        {\n            return Err(FastlyConfigError::UnsupportedManifestVersion(\n                manifest_version,\n                MAX_MANIFEST_VERSION,\n            ));\n        }\n\n        let local_server = local_server\n            .map(TryInto::try_into)\n            .transpose()?\n            .unwrap_or_default();\n        Ok(FastlyConfig {\n            name: name.unwrap_or_default(),\n            description: description.unwrap_or_default(),\n            authors: authors.unwrap_or_default(),\n            language: language.unwrap_or_default(),\n            local_server,\n        })\n    }\n}\n\n/// Configuration settings used for tests.\n///\n/// This represents all of the `fastly.toml` fields whose keys begin with `testing`. Currently this\n/// section of the manifest is only used for providing backend definitions, but additional fields\n/// may be added in the future.\n#[derive(Clone, Debug, Default)]\npub struct LocalServerConfig {\n    acls: AclConfig,\n    backends: BackendsConfig,\n    device_detection: DeviceDetection,\n    geolocation: Geolocation,\n    dictionaries: DictionariesConfig,\n    object_stores: ObjectStoreConfig,\n    secret_stores: SecretStoreConfig,\n    shielding_sites: ShieldingSites,\n    fake_valid_fastly_keys: FakeValidFastlyKeys,\n}\n\n/// Enum of available (experimental) wasi modules\n#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]\npub enum ExperimentalModule {\n    WasiNn,\n}\n\n/// Internal deserializer used to read the `[testing]` section of a `fastly.toml` file.\n///\n/// Once a TOML file has been read using [`toml::from_str`], this can be converted into\n/// a [`LocalServerConfig`] with [`TryInto::try_into`].\n#[derive(Deserialize)]\nstruct RawLocalServerConfig {\n    acls: Option<Table>,\n    backends: Option<Table>,\n    device_detection: Option<Table>,\n    geolocation: Option<Table>,\n    #[serde(alias = \"config_stores\")]\n    dictionaries: Option<Table>,\n    #[serde(alias = \"object_store\", alias = \"kv_stores\")]\n    object_stores: Option<Table>,\n    secret_stores: Option<Table>,\n    shielding_sites: Option<Table>,\n    fake_valid_fastly_keys: Option<Vec<String>>,\n}\n\nimpl TryInto<LocalServerConfig> for RawLocalServerConfig {\n    type Error = FastlyConfigError;\n    fn try_into(self) -> Result<LocalServerConfig, Self::Error> {\n        let Self {\n            acls,\n            backends,\n            device_detection,\n            geolocation,\n            dictionaries,\n            object_stores,\n            secret_stores,\n            shielding_sites,\n            fake_valid_fastly_keys,\n        } = self;\n        let acls = if let Some(acls) = acls {\n            acls.try_into()?\n        } else {\n            AclConfig::default()\n        };\n        let backends = if let Some(backends) = backends {\n            backends.try_into()?\n        } else {\n            BackendsConfig::default()\n        };\n        let device_detection = if let Some(device_detection) = device_detection {\n            device_detection.try_into()?\n        } else {\n            DeviceDetection::default()\n        };\n        let geolocation = if let Some(geolocation) = geolocation {\n            geolocation.try_into()?\n        } else {\n            Geolocation::default()\n        };\n        let dictionaries = if let Some(dictionaries) = dictionaries {\n            dictionaries.try_into()?\n        } else {\n            DictionariesConfig::default()\n        };\n        let object_stores = if let Some(object_store) = object_stores {\n            object_store.try_into()?\n        } else {\n            ObjectStoreConfig::default()\n        };\n        let secret_stores = if let Some(secret_store) = secret_stores {\n            secret_store.try_into()?\n        } else {\n            SecretStoreConfig::default()\n        };\n        let shielding_sites = if let Some(shielding_sites) = shielding_sites {\n            shielding_sites.try_into()?\n        } else {\n            ShieldingSites::default()\n        };\n\n        let fake_valid_fastly_keys = fake_valid_fastly_keys\n            .unwrap_or_default()\n            .into_iter()\n            .collect::<FakeValidFastlyKeys>();\n\n        Ok(LocalServerConfig {\n            acls,\n            backends,\n            device_detection,\n            geolocation,\n            dictionaries,\n            object_stores,\n            secret_stores,\n            shielding_sites,\n            fake_valid_fastly_keys,\n        })\n    }\n}\n\n#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, Hash)]\npub enum UnknownImportBehavior {\n    /// Unknown imports are rejected at link time (default behavior)\n    #[default]\n    LinkError,\n    /// Unknown imports trap when called\n    Trap,\n}\n"
  },
  {
    "path": "src/downstream.rs",
    "content": "//! Operations related to handling the \"downstream\" (end-client) request\nuse std::net::SocketAddr;\n\nuse crate::body::Body;\nuse crate::error::DownstreamRequestError;\nuse crate::handoff::HandoffInfo;\nuse http::{HeaderMap, Request, Response};\nuse hyper::Uri;\nuse tokio::sync::oneshot::Sender;\n\n#[derive(Debug)]\npub struct DownstreamMetadata {\n    // A unique request ID.\n    pub req_id: u64,\n    /// The IP address and port that received this request.\n    pub server_addr: SocketAddr,\n    /// The downstream IP address and port for this request.\n    pub client_addr: SocketAddr,\n    /// The compliance region that this request was received in.\n    ///\n    /// For now this is just always `\"none\"`, but we place the field in the sandbox\n    /// to make it easier to implement custom configuration values later on.\n    pub compliance_region: String,\n    /// The originally received headers in this request, before the\n    /// guest potentially modified them.\n    pub original_headers: HeaderMap,\n}\n\n#[derive(Debug)]\npub struct DownstreamRequest {\n    pub req: hyper::Request<Body>,\n    pub metadata: DownstreamMetadata,\n    pub sender: Sender<DownstreamResponse>,\n}\n\n#[derive(Debug)]\npub enum DownstreamResponse {\n    Http(Response<Body>),\n    HandoffToPushpin(HandoffInfo),\n    HandoffToBackend(HandoffInfo),\n}\n\n/// Canonicalize the incoming request into the form expected by host calls.\n///\n/// The primary canonicalization is to provide an absolute URL (with authority), using the HOST\n/// header of the request.\npub fn prepare_request(req: Request<hyper::Body>) -> Result<Request<Body>, DownstreamRequestError> {\n    let (mut metadata, body) = req.into_parts();\n    let uri_parts = metadata.uri.into_parts();\n\n    // Prefer to find the host from the HOST header, rather than the URL.\n    let http_host = if let Some(host_header) = metadata.headers.get(http::header::HOST) {\n        std::str::from_utf8(host_header.as_bytes())\n            .map_err(|_| DownstreamRequestError::InvalidHost)?\n    } else {\n        uri_parts\n            .authority\n            .as_ref()\n            .ok_or(DownstreamRequestError::InvalidHost)?\n            .host()\n    };\n\n    // rebuild the request URI, replacing the authority with only the host and ensuring there is\n    // a path and scheme\n    let path_and_query = uri_parts\n        .path_and_query\n        .ok_or(DownstreamRequestError::InvalidUrl)?;\n    let scheme = uri_parts.scheme.unwrap_or(http::uri::Scheme::HTTP);\n    metadata.uri = Uri::builder()\n        .scheme(scheme)\n        .authority(http_host)\n        .path_and_query(path_and_query)\n        .build()\n        .map_err(|_| DownstreamRequestError::InvalidUrl)?;\n\n    Ok(Request::from_parts(metadata, Body::from(body)))\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "//! Error types.\n\nuse crate::{handoff::HandoffInfo, wiggle_abi::types::FastlyStatus};\nuse std::error::Error as StdError;\nuse std::io;\nuse url::Url;\nuse wiggle::GuestError;\n\n#[derive(Debug, thiserror::Error)]\n#[non_exhaustive]\npub enum Error {\n    /// Thrown by hostcalls when a buffer is larger than its `*_len` limit.\n    #[error(\"Buffer length error: {buf} too long to fit in {len}\")]\n    BufferLengthError {\n        buf: &'static str,\n        len: &'static str,\n    },\n\n    /// Error when viceroy has encountered a fatal error and the underlying wasmtime\n    /// instance must be terminated with a Trap.\n    #[error(\"Fatal error: [{0}]\")]\n    FatalError(String),\n\n    /// Error when viceroy has been given an invalid file.\n    #[error(\"Expected a valid Wasm file\")]\n    FileFormat,\n\n    #[error(\"Expected a valid wastime's profiling strategy\")]\n    ProfilingStrategy,\n\n    #[error(transparent)]\n    FastlyConfig(#[from] FastlyConfigError),\n\n    #[error(\"Could not determine address from backend URL: {0}\")]\n    BackendUrl(Url),\n\n    /// An error from guest-provided arguments to a hostcall. These errors may be created\n    /// automatically by the Wiggle-generated glue code that converts parameters from their ABI\n    /// representation into richer Rust types, or by fallible methods of `GuestPtr` in the\n    /// wiggle_abi trait implementations.\n    #[error(\"Guest error: [{0}]\")]\n    GuestError(#[from] wiggle::GuestError),\n\n    #[error(transparent)]\n    HandleError(#[from] HandleError),\n\n    #[error(transparent)]\n    HyperError(#[from] hyper::Error),\n\n    #[error(\"Backend connection error for '{backend_name}' ({uri}): {source}\")]\n    BackendConnectionError {\n        backend_name: String,\n        uri: String,\n        #[source]\n        source: hyper::Error,\n    },\n\n    #[error(transparent)]\n    Infallible(#[from] std::convert::Infallible),\n\n    /// Error when an invalid argument is supplied to a hostcall.\n    #[error(\"Invalid argument given\")]\n    InvalidArgument,\n\n    #[error(transparent)]\n    InvalidHeaderName(#[from] http::header::InvalidHeaderName),\n\n    #[error(transparent)]\n    InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),\n\n    #[error(transparent)]\n    InvalidMethod(#[from] http::method::InvalidMethod),\n\n    #[error(transparent)]\n    InvalidStatusCode(#[from] http::status::InvalidStatusCode),\n\n    #[error(transparent)]\n    InvalidUri(#[from] http::uri::InvalidUri),\n\n    #[error(transparent)]\n    IoError(#[from] std::io::Error),\n\n    #[error(\"Limit exceeded: {msg}\")]\n    LimitExceeded { msg: &'static str },\n\n    #[error(\"Missing downstream metadata for request\")]\n    MissingDownstreamMetadata,\n\n    #[error(transparent)]\n    Other(#[from] anyhow::Error),\n\n    #[error(\"Unsupported operation: {msg}\")]\n    Unsupported { msg: &'static str },\n\n    /// Downstream response is already sending.\n    #[error(\"Downstream response already sending\")]\n    DownstreamRespSending,\n\n    #[error(\"Unexpected error sending a chunk to a streaming body\")]\n    StreamingChunkSend,\n\n    #[error(\"Unknown backend: {0}\")]\n    UnknownBackend(String),\n\n    #[error(transparent)]\n    DictionaryError(#[from] crate::wiggle_abi::DictionaryError),\n\n    #[error(transparent)]\n    DeviceDetectionError(#[from] crate::wiggle_abi::DeviceDetectionError),\n\n    #[error(transparent)]\n    ObjectStoreError(#[from] crate::object_store::ObjectStoreError),\n\n    #[error(transparent)]\n    KvStoreError(#[from] crate::object_store::KvStoreError),\n\n    #[error(transparent)]\n    SecretStoreError(#[from] crate::wiggle_abi::SecretStoreError),\n\n    #[error{\"Expected UTF-8\"}]\n    Utf8Expected(#[from] std::str::Utf8Error),\n\n    #[error{\"Unsupported ABI version\"}]\n    AbiVersionMismatch,\n\n    #[error(transparent)]\n    DownstreamRequestError(#[from] DownstreamRequestError),\n\n    #[error(\"{0} is not currently supported for local testing\")]\n    NotAvailable(&'static str),\n\n    #[error(\"Could not load native certificates: {0}\")]\n    BadCerts(std::io::Error),\n\n    #[error(\"No CA certificates available\")]\n    TlsNoCAAvailable,\n\n    #[error(\"No valid CA certificates found in provided certificate bundle\")]\n    TlsNoValidCACerts,\n\n    #[error(\"Invalid or missing host for TLS connection\")]\n    TlsInvalidHost,\n\n    #[error(\"TLS certificate validation failed\")]\n    TlsCertificateValidationFailed,\n\n    #[error(\"Could not generate new backend name from '{0}'\")]\n    BackendNameRegistryError(String),\n\n    #[error(transparent)]\n    HttpError(#[from] http::Error),\n\n    #[error(\"Object Store '{0}' does not exist\")]\n    UnknownObjectStore(String),\n\n    #[error(\"Invalid Object Store `key` value used: {0}.\")]\n    ObjectStoreKeyValidationError(#[from] crate::object_store::KeyValidationError),\n\n    #[error(\"Unfinished streaming body\")]\n    UnfinishedStreamingBody,\n\n    #[error(\"Shared memory not supported yet\")]\n    SharedMemory,\n\n    #[error(\"Value absent from structure\")]\n    ValueAbsent,\n\n    #[error(\"String conversion error\")]\n    ToStr(#[from] http::header::ToStrError),\n\n    #[error(\"invalid client certificate\")]\n    InvalidClientCert(#[from] crate::config::ClientCertError),\n\n    #[error(\"Invalid response to ALPN request; wanted '{0}', got '{1}'\")]\n    InvalidAlpnResponse(&'static str, String),\n\n    #[error(\"Resource temporarily unavailable\")]\n    Again,\n\n    #[error(\"cache error: {0}\")]\n    CacheError(crate::cache::Error),\n}\n\nimpl Error {\n    /// Convert to an error code representation suitable for passing across the ABI boundary.\n    ///\n    /// For more information about specific error codes see [`fastly_shared::FastlyStatus`][status],\n    /// as well as the `witx` interface definition located in `wasm_abi/typenames.witx`.\n    ///\n    /// [status]: fastly_shared/struct.FastlyStatus.html\n    pub fn to_fastly_status(&self) -> FastlyStatus {\n        match self {\n            Error::BufferLengthError { .. } => FastlyStatus::Buflen,\n            Error::InvalidArgument => FastlyStatus::Inval,\n            Error::ValueAbsent => FastlyStatus::None,\n            Error::Unsupported { .. } | Error::NotAvailable(_) => FastlyStatus::Unsupported,\n            Error::HandleError { .. } => FastlyStatus::Badf,\n            Error::InvalidStatusCode { .. } => FastlyStatus::Inval,\n            Error::UnknownBackend(_) | Error::InvalidClientCert(_) => FastlyStatus::Inval,\n            // Map specific kinds of `hyper::Error` into their respective error codes.\n            Error::HyperError(e) if e.is_parse() => FastlyStatus::Httpinvalid,\n            Error::HyperError(e) if e.is_user() => FastlyStatus::Httpuser,\n            Error::HyperError(e) if e.is_incomplete_message() => FastlyStatus::Httpincomplete,\n            Error::HyperError(e)\n                if e.source()\n                    .and_then(|e| e.downcast_ref::<io::Error>())\n                    .map(|ioe| ioe.kind())\n                    == Some(io::ErrorKind::UnexpectedEof) =>\n            {\n                FastlyStatus::Httpincomplete\n            }\n            Error::HyperError(_) => FastlyStatus::Error,\n            // BackendConnectionError contains detailed context but maps to same status as HyperError\n            Error::BackendConnectionError { source, .. } if source.is_parse() => {\n                FastlyStatus::Httpinvalid\n            }\n            Error::BackendConnectionError { source, .. } if source.is_user() => {\n                FastlyStatus::Httpuser\n            }\n            Error::BackendConnectionError { source, .. } if source.is_incomplete_message() => {\n                FastlyStatus::Httpincomplete\n            }\n            Error::BackendConnectionError { source, .. }\n                if source\n                    .source()\n                    .and_then(|e| e.downcast_ref::<io::Error>())\n                    .map(|ioe| ioe.kind())\n                    == Some(io::ErrorKind::UnexpectedEof) =>\n            {\n                FastlyStatus::Httpincomplete\n            }\n            Error::BackendConnectionError { .. } => FastlyStatus::Error,\n            // Destructuring a GuestError is recursive, so we use a helper function:\n            Error::GuestError(e) => Self::guest_error_fastly_status(e),\n            // We delegate to some error types' own implementation of `to_fastly_status`.\n            Error::DictionaryError(e) => e.to_fastly_status(),\n            Error::DeviceDetectionError(e) => e.to_fastly_status(),\n            Error::ObjectStoreError(e) => e.into(),\n            Error::KvStoreError(e) => e.into(),\n            Error::SecretStoreError(e) => e.into(),\n            Error::Again => FastlyStatus::Again,\n            Error::LimitExceeded { .. } => FastlyStatus::Limitexceeded,\n            Error::CacheError(e) => e.into(),\n            // All other hostcall errors map to a generic `ERROR` value.\n            Error::AbiVersionMismatch\n            | Error::BackendUrl(_)\n            | Error::BadCerts(_)\n            | Error::TlsNoCAAvailable\n            | Error::TlsNoValidCACerts\n            | Error::TlsInvalidHost\n            | Error::TlsCertificateValidationFailed\n            | Error::DownstreamRequestError(_)\n            | Error::DownstreamRespSending\n            | Error::FastlyConfig(_)\n            | Error::FatalError(_)\n            | Error::FileFormat\n            | Error::Infallible(_)\n            | Error::InvalidHeaderName(_)\n            | Error::InvalidHeaderValue(_)\n            | Error::InvalidMethod(_)\n            | Error::InvalidUri(_)\n            | Error::IoError(_)\n            | Error::MissingDownstreamMetadata\n            | Error::Other(_)\n            | Error::ProfilingStrategy\n            | Error::StreamingChunkSend\n            | Error::Utf8Expected(_)\n            | Error::BackendNameRegistryError(_)\n            | Error::HttpError(_)\n            | Error::UnknownObjectStore(_)\n            | Error::ObjectStoreKeyValidationError(_)\n            | Error::UnfinishedStreamingBody\n            | Error::SharedMemory\n            | Error::ToStr(_)\n            | Error::InvalidAlpnResponse(_, _) => FastlyStatus::Error,\n        }\n    }\n\n    fn guest_error_fastly_status(e: &GuestError) -> FastlyStatus {\n        use GuestError::*;\n        match e {\n            PtrNotAligned { .. } => FastlyStatus::Badalign,\n            // We may want to expand the FastlyStatus enum to distinguish between more of these\n            // values.\n            InvalidFlagValue { .. }\n            | InvalidEnumValue { .. }\n            | PtrOutOfBounds { .. }\n            | PtrOverflow\n            | InvalidUtf8 { .. }\n            | TryFromIntError { .. } => FastlyStatus::Inval,\n            // These errors indicate either a pathological user input or an internal programming\n            // error\n            SliceLengthsDiffer => FastlyStatus::Error,\n            // Recursive case: InFunc wraps a GuestError with some context which\n            // doesn't determine what sort of FastlyStatus we return.\n            InFunc { err, .. } => Self::guest_error_fastly_status(err),\n        }\n    }\n}\n\n/// Errors thrown due to an invalid resource handle of some kind.\n#[derive(Debug, thiserror::Error)]\npub enum HandleError {\n    /// A request handle was not valid.\n    #[error(\"Invalid request handle: {0}\")]\n    InvalidRequestHandle(crate::wiggle_abi::types::RequestHandle),\n\n    /// A response handle was not valid.\n    #[error(\"Invalid response handle: {0}\")]\n    InvalidResponseHandle(crate::wiggle_abi::types::ResponseHandle),\n\n    /// A body handle was not valid.\n    #[error(\"Invalid body handle: {0}\")]\n    InvalidBodyHandle(crate::wiggle_abi::types::BodyHandle),\n\n    /// A logging endpoint handle was not valid.\n    #[error(\"Invalid endpoint handle: {0}\")]\n    InvalidEndpointHandle(crate::wiggle_abi::types::EndpointHandle),\n\n    /// A request handle was not valid.\n    #[error(\"Invalid pending request promise handle: {0}\")]\n    InvalidPendingRequestHandle(crate::wiggle_abi::types::PendingRequestHandle),\n\n    /// A request handle was not valid.\n    #[error(\"Invalid pending downstream request handle: {0}\")]\n    InvalidPendingDownstreamHandle(crate::wiggle_abi::types::AsyncItemHandle),\n\n    /// A lookup handle was not valid.\n    #[error(\"Invalid pending KV lookup handle: {0}\")]\n    InvalidPendingKvLookupHandle(crate::wiggle_abi::types::PendingKvLookupHandle),\n\n    /// A insert handle was not valid.\n    #[error(\"Invalid pending KV insert handle: {0}\")]\n    InvalidPendingKvInsertHandle(crate::wiggle_abi::types::PendingKvInsertHandle),\n\n    /// A delete handle was not valid.\n    #[error(\"Invalid pending KV delete handle: {0}\")]\n    InvalidPendingKvDeleteHandle(crate::wiggle_abi::types::PendingKvDeleteHandle),\n\n    /// A list handle was not valid.\n    #[error(\"Invalid pending KV list handle: {0}\")]\n    InvalidPendingKvListHandle(crate::wiggle_abi::types::PendingKvListHandle),\n\n    /// A dictionary handle was not valid.\n    #[error(\"Invalid dictionary handle: {0}\")]\n    InvalidDictionaryHandle(crate::wiggle_abi::types::DictionaryHandle),\n\n    /// An object-store handle was not valid.\n    #[error(\"Invalid object-store handle: {0}\")]\n    InvalidObjectStoreHandle(crate::wiggle_abi::types::ObjectStoreHandle),\n\n    /// A secret store handle was not valid.\n    #[error(\"Invalid secret store handle: {0}\")]\n    InvalidSecretStoreHandle(crate::wiggle_abi::types::SecretStoreHandle),\n\n    /// A secret handle was not valid.\n    #[error(\"Invalid secret handle: {0}\")]\n    InvalidSecretHandle(crate::wiggle_abi::types::SecretHandle),\n\n    /// An async item handle was not valid.\n    #[error(\"Invalid async item handle: {0}\")]\n    InvalidAsyncItemHandle(crate::wiggle_abi::types::AsyncItemHandle),\n\n    /// An acl handle was not valid.\n    #[error(\"Invalid acl handle: {0}\")]\n    InvalidAclHandle(crate::wiggle_abi::types::AclHandle),\n\n    /// A cache handle was not valid.\n    #[error(\"Invalid cache handle: {0}\")]\n    InvalidCacheHandle(crate::wiggle_abi::types::CacheHandle),\n}\n\n/// Errors that can occur in a worker thread running a guest module.\n///\n/// See [`ExecuteCtx::handle_request`][handle_request] and [`ExecuteCtx::run_guest`][run_guest] for\n/// more information about guest execution.\n///\n/// [handle_request]: ../execute/struct.ExecuteCtx.html#method.handle_request\n/// [run_guest]: ../execute/struct.ExecuteCtx.html#method.run_guest\n#[derive(Debug, thiserror::Error)]\npub(crate) enum ExecutionError {\n    /// Errors thrown by the guest's entrypoint.\n    ///\n    /// See [`wasmtime::Func::call`][call] for more information.\n    ///\n    /// [call]: https://docs.rs/wasmtime/latest/wasmtime/struct.Func.html#method.call\n    #[error(\"WebAssembly execution trapped: {0}\")]\n    WasmTrap(anyhow::Error),\n\n    /// Errors thrown when trying to instantiate a guest context.\n    #[error(\"Error creating context: {0}\")]\n    Context(anyhow::Error),\n\n    /// Errors thrown when type-checking WebAssembly before instantiation\n    #[error(\"Error type-checking WebAssembly instantiation: {0}\")]\n    Typechecking(anyhow::Error),\n\n    /// Errors thrown when trying to instantiate a guest module.\n    #[error(\"Error instantiating WebAssembly: {0}\")]\n    Instantiation(anyhow::Error),\n}\n\n/// Errors that can occur while parsing a `fastly.toml` file.\n#[derive(Debug, thiserror::Error)]\npub enum FastlyConfigError {\n    /// An I/O error that occurred while reading the file.\n    #[error(\"error reading '{path}': {err}\")]\n    IoError {\n        path: String,\n        #[source]\n        err: std::io::Error,\n    },\n\n    #[error(\"invalid configuration for '{name}': {err}\")]\n    InvalidDeviceDetectionDefinition {\n        name: String,\n        #[source]\n        err: DeviceDetectionConfigError,\n    },\n\n    #[error(\"invalid configuration for '{name}': {err}\")]\n    InvalidGeolocationDefinition {\n        name: String,\n        #[source]\n        err: GeolocationConfigError,\n    },\n\n    #[error(\"invalid configuration for '{name}': {err}\")]\n    InvalidAclDefinition {\n        name: String,\n        #[source]\n        err: AclConfigError,\n    },\n\n    #[error(\"invalid configuration for '{name}': {err}\")]\n    InvalidBackendDefinition {\n        name: String,\n        #[source]\n        err: BackendConfigError,\n    },\n\n    #[error(\"invalid configuration for '{name}': {err}\")]\n    InvalidDictionaryDefinition {\n        name: String,\n        #[source]\n        err: DictionaryConfigError,\n    },\n\n    #[error(\"invalid configuration for '{name}': {err}\")]\n    InvalidObjectStoreDefinition {\n        name: String,\n        #[source]\n        err: ObjectStoreConfigError,\n    },\n\n    #[error(\"invalid configuration for '{name}': {err}\")]\n    InvalidSecretStoreDefinition {\n        name: String,\n        #[source]\n        err: SecretStoreConfigError,\n    },\n\n    #[error(\"invalid configuration for '{name}': {err}\")]\n    InvalidShieldingSiteDefinition {\n        name: String,\n        #[source]\n        err: ShieldingSiteConfigError,\n    },\n\n    /// An error that occurred while deserializing the file.\n    ///\n    /// This represents errors caused by syntactically invalid TOML data, missing fields, etc.\n    #[error(\"error parsing `fastly.toml`: {0}\")]\n    InvalidFastlyToml(#[from] toml::de::Error),\n\n    /// The `manifest_version` field contained a value greater than is\n    /// currently supported.\n    #[error(\"unsupported manifest version {0}; max supported version is {1}\")]\n    UnsupportedManifestVersion(u32, u32),\n}\n\n/// Errors that may occur while validating acl configurations.\n#[derive(Debug, thiserror::Error)]\npub enum AclConfigError {\n    /// An I/O error that occurred while processing a file.\n    #[error(transparent)]\n    IoError(std::io::Error),\n\n    /// An error occurred parsing JSON.\n    #[error(transparent)]\n    JsonError(serde_json::error::Error),\n\n    #[error(\"acl must be a TOML table or string\")]\n    InvalidType,\n\n    #[error(\"missing 'file' field\")]\n    MissingFile,\n}\n\n/// Errors that may occur while validating backend configurations.\n#[derive(Debug, thiserror::Error)]\npub enum BackendConfigError {\n    #[error(\"definition was not provided as a TOML table\")]\n    InvalidEntryType,\n\n    #[error(\"invalid override_host: {0}\")]\n    InvalidOverrideHost(#[from] http::header::InvalidHeaderValue),\n\n    #[error(\"'override_host' field is empty\")]\n    EmptyOverrideHost,\n\n    #[error(\"'override_host' field was not a string\")]\n    InvalidOverrideHostEntry,\n\n    #[error(\"'cert_host' field is empty\")]\n    EmptyCertHost,\n\n    #[error(\"'cert_host' field was not a string\")]\n    InvalidCertHostEntry,\n\n    #[error(\"'ca_certificate' field is empty\")]\n    EmptyCACert,\n\n    #[error(\"'ca_certificate' field was invalid: {0}\")]\n    InvalidCACertEntry(String),\n\n    #[error(\"'use_sni' field was not a boolean\")]\n    InvalidUseSniEntry,\n\n    #[error(\"'grpc' field was not a boolean\")]\n    InvalidGrpcEntry,\n\n    #[error(\n        \"'health' field has invalid value '{0}' (expected 'unknown', 'healthy', or 'unhealthy')\"\n    )]\n    InvalidHealthEntry(String),\n\n    #[error(\"invalid url: {0}\")]\n    InvalidUrl(#[from] http::uri::InvalidUri),\n\n    #[error(\"'url' field was not a string\")]\n    InvalidUrlEntry,\n\n    #[error(\"no default definition provided\")]\n    MissingDefault,\n\n    #[error(\"missing 'url' field\")]\n    MissingUrl,\n\n    #[error(\"unrecognized key '{0}'\")]\n    UnrecognizedKey(String),\n\n    #[error(transparent)]\n    ClientCertError(#[from] crate::config::ClientCertError),\n}\n\n/// Errors that may occur while validating dictionary configurations.\n#[derive(Debug, thiserror::Error)]\npub enum DictionaryConfigError {\n    /// An I/O error that occurred while reading the file.\n    #[error(transparent)]\n    IoError(std::io::Error),\n\n    #[error(\"'contents' was not provided as a TOML table\")]\n    InvalidContentsType,\n\n    #[error(\"inline dictionary value was not a string\")]\n    InvalidInlineEntryType,\n\n    #[error(\"definition was not provided as a TOML table\")]\n    InvalidEntryType,\n\n    #[error(\"'name' field was not a string\")]\n    InvalidNameEntry,\n\n    #[error(\n        \"'{0}' is not a valid format for the dictionary. Supported format(s) are: 'inline-toml', 'json'.\"\n    )]\n    InvalidDictionaryFormat(String),\n\n    #[error(\"'file' field is empty\")]\n    EmptyFileEntry,\n\n    #[error(\"'format' field is empty\")]\n    EmptyFormatEntry,\n\n    #[error(\"'file' field was not a string\")]\n    InvalidFileEntry,\n\n    #[error(\"'format' field was not a string\")]\n    InvalidFormatEntry,\n\n    #[error(\"missing 'contents' field\")]\n    MissingContents,\n\n    #[error(\"no default definition provided\")]\n    MissingDefault,\n\n    #[error(\"missing 'name' field\")]\n    MissingName,\n\n    #[error(\"missing 'file' field\")]\n    MissingFile,\n\n    #[error(\"missing 'format' field\")]\n    MissingFormat,\n\n    #[error(\"unrecognized key '{0}'\")]\n    UnrecognizedKey(String),\n\n    #[error(\"Item key named '{key}' is too long, max size is {size}\")]\n    DictionaryItemKeyTooLong { key: String, size: i32 },\n\n    #[error(\"too many items, max amount is {size}\")]\n    DictionaryCountTooLong { size: i32 },\n\n    #[error(\n        \"Item value under key named '{key}' is of the wrong format. The value is expected to be a JSON String\"\n    )]\n    DictionaryItemValueWrongFormat { key: String },\n\n    #[error(\"Item value named '{key}' is too long, max size is {size}\")]\n    DictionaryItemValueTooLong { key: String, size: i32 },\n\n    #[error(\n        \"The file is of the wrong format. The file is expected to contain a single JSON Object\"\n    )]\n    DictionaryFileWrongFormat,\n}\n\n/// Errors that may occur while validating device detection configurations.\n#[derive(Debug, thiserror::Error)]\npub enum DeviceDetectionConfigError {\n    /// An I/O error that occurred while reading the file.\n    #[error(transparent)]\n    IoError(std::io::Error),\n\n    #[error(\"definition was not provided as a TOML table\")]\n    InvalidEntryType,\n\n    #[error(\"missing 'file' field\")]\n    MissingFile,\n\n    #[error(\"'file' field is empty\")]\n    EmptyFileEntry,\n\n    #[error(\"missing 'user_agents' field\")]\n    MissingUserAgents,\n\n    #[error(\"inline device detection value was not a string\")]\n    InvalidInlineEntryType,\n\n    #[error(\"'file' field was not a string\")]\n    InvalidFileEntry,\n\n    #[error(\"'user_agents' was not provided as a TOML table\")]\n    InvalidUserAgentsType,\n\n    #[error(\"unrecognized key '{0}'\")]\n    UnrecognizedKey(String),\n\n    #[error(\"missing 'format' field\")]\n    MissingFormat,\n\n    #[error(\"'format' field was not a string\")]\n    InvalidFormatEntry,\n\n    #[error(\n        \"'{0}' is not a valid format for the device detection mapping. Supported format(s) are: 'inline-toml', 'json'.\"\n    )]\n    InvalidDeviceDetectionMappingFormat(String),\n\n    #[error(\n        \"The file is of the wrong format. The file is expected to contain a single JSON Object\"\n    )]\n    DeviceDetectionFileWrongFormat,\n\n    #[error(\"'format' field is empty\")]\n    EmptyFormatEntry,\n\n    #[error(\n        \"Item value under key named '{key}' is of the wrong format. The value is expected to be a JSON String\"\n    )]\n    DeviceDetectionItemValueWrongFormat { key: String },\n}\n\n/// Errors that may occur while validating geolocation configurations.\n#[derive(Debug, thiserror::Error)]\npub enum GeolocationConfigError {\n    /// An I/O error that occurred while reading the file.\n    #[error(transparent)]\n    IoError(std::io::Error),\n\n    #[error(\"definition was not provided as a TOML table\")]\n    InvalidEntryType,\n\n    #[error(\"missing 'file' field\")]\n    MissingFile,\n\n    #[error(\"'file' field is empty\")]\n    EmptyFileEntry,\n\n    #[error(\"missing 'addresses' field\")]\n    MissingAddresses,\n\n    #[error(\"inline geolocation value was not a string\")]\n    InvalidInlineEntryType,\n\n    #[error(\"'file' field was not a string\")]\n    InvalidFileEntry,\n\n    #[error(\"'addresses' was not provided as a TOML table\")]\n    InvalidAddressesType,\n\n    #[error(\"unrecognized key '{0}'\")]\n    UnrecognizedKey(String),\n\n    #[error(\"missing 'format' field\")]\n    MissingFormat,\n\n    #[error(\"'format' field was not a string\")]\n    InvalidFormatEntry,\n\n    #[error(\"IP address not valid: '{0}'\")]\n    InvalidAddressEntry(String),\n\n    #[error(\n        \"'{0}' is not a valid format for the geolocation mapping. Supported format(s) are: 'inline-toml', 'json'.\"\n    )]\n    InvalidGeolocationMappingFormat(String),\n\n    #[error(\n        \"The file is of the wrong format. The file is expected to contain a single JSON Object\"\n    )]\n    GeolocationFileWrongFormat,\n\n    #[error(\"'format' field is empty\")]\n    EmptyFormatEntry,\n\n    #[error(\n        \"Item value under key named '{key}' is of the wrong format. The value is expected to be a JSON String\"\n    )]\n    GeolocationItemValueWrongFormat { key: String },\n}\n\n/// Errors that may occur while validating object store configurations.\n#[derive(Debug, thiserror::Error)]\npub enum ObjectStoreConfigError {\n    /// An I/O error that occurred while reading the file.\n    #[error(transparent)]\n    IoError(std::io::Error),\n    #[error(\"The `file` and `data` keys for the object `{0}` are set. Only one can be used.\")]\n    FileAndData(String),\n    #[error(\"The `file` or `data` key for the object `{0}` is not set. One must be used.\")]\n    NoFileOrData(String),\n    #[error(\"The `data` value for the object `{0}` is not a string.\")]\n    DataNotAString(String),\n    #[error(\"The `metadata` value for the object `{0}` is not a string.\")]\n    MetadataNotAString(String),\n    #[error(\"The `file` value for the object `{0}` is not a string.\")]\n    FileNotAString(String),\n    #[error(\"The `key` key for an object is not set. It must be used.\")]\n    NoKey,\n    #[error(\"The `key` value for an object is not a string.\")]\n    KeyNotAString,\n    #[error(\"There is no array of objects for the given store.\")]\n    NotAnArray,\n    #[error(\"There is an object in the given store that is not a table of keys.\")]\n    NotATable,\n    #[error(\"There was an error when manipulating the ObjectStore: {0}.\")]\n    ObjectStoreError(#[from] crate::object_store::ObjectStoreError),\n    #[error(\"There was an error when manipulating the KvStore: {0}.\")]\n    KvStoreError(#[from] crate::object_store::KvStoreError),\n    #[error(\"Invalid `key` value used: {0}.\")]\n    KeyValidationError(#[from] crate::object_store::KeyValidationError),\n    #[error(\"'{0}' is not a valid format for the config store. Supported format(s) are: 'json'.\")]\n    InvalidFileFormat(String),\n    #[error(\"When using a top-level 'file' to load data, both 'file' and 'format' must be set.\")]\n    OnlyOneFormatOrFileSet,\n    #[error(\n        \"The file is of the wrong format. The file is expected to contain a single JSON Object.\"\n    )]\n    FileWrongFormat,\n    #[error(\n        \"Item value under key named '{key}' is of the wrong format. The value is expected to be a JSON String.\"\n    )]\n    FileValueWrongFormat { key: String },\n    #[error(\n        \"Item value under key named '{key}' is of the wrong format. 'data' and 'file' are mutually exclusive.\"\n    )]\n    BothDataAndFilePresent { key: String },\n    #[error(\n        \"Item value under key named '{key}' is of the wrong format. One of 'data' or 'file' must be present.\"\n    )]\n    MissingDataOrFile { key: String },\n}\n\n/// Errors that may occur while validating secret store configurations.\n#[derive(Debug, thiserror::Error)]\npub enum SecretStoreConfigError {\n    /// An I/O error that occurred while reading the file.\n    #[error(transparent)]\n    IoError(std::io::Error),\n\n    #[error(\"'{0}' is not a valid format for the secret store. Supported format(s) are: 'json'.\")]\n    InvalidFileFormat(String),\n    #[error(\"When using a top-level 'file' to load data, both 'file' and 'format' must be set.\")]\n    OnlyOneFormatOrFileSet,\n    #[error(\n        \"The file is of the wrong format. The file is expected to contain a single JSON object.\"\n    )]\n    FileWrongFormat,\n    #[error(\n        \"More than one of `file`, `data`, or `env` keys for the object `{0}` are set. Only one can be used.\"\n    )]\n    FileDataEnvExclusive(String),\n    #[error(\"None of `file`, `data`, or `env` keys for the object `{0}` is set. One must be used.\")]\n    FileDataEnvNotSet(String),\n    #[error(\"The `data` value for the object `{0}` is not a string.\")]\n    DataNotAString(String),\n    #[error(\"The `file` value for the object `{0}` is not a string.\")]\n    FileNotAString(String),\n    #[error(\"The `env` value for the object `{0}` is not a string.\")]\n    EnvNotAString(String),\n\n    #[error(\"The `key` key for an object is not set. It must be used.\")]\n    NoKey,\n    #[error(\"The `key` value for an object is not a string.\")]\n    KeyNotAString,\n\n    #[error(\"There is no array of objects for the given store.\")]\n    NotAnArray,\n    #[error(\"There is an object in the given store that is not a table of keys.\")]\n    NotATable,\n\n    #[error(\"Invalid secret store name: {0}\")]\n    InvalidSecretStoreName(String),\n\n    #[error(\"Invalid secret name: {0}\")]\n    InvalidSecretName(String),\n}\n\n/// Errors that may occur while validating shielding site configurations.\n#[derive(Debug, thiserror::Error)]\npub enum ShieldingSiteConfigError {\n    #[error(\n        \"Illegal TOML value for shielding site; must be either the string 'local' or a table containing an encrypted and unencrypted URL.\"\n    )]\n    IllegalSiteValue,\n\n    #[error(\"Illegal TOML string for shielding site; must be 'local'\")]\n    IllegalSiteString,\n\n    #[error(\n        \"Illegal table for shielding site; must have exactly one key named 'encrypted', and one named 'unencrypted'\"\n    )]\n    IllegalSiteDefinition,\n\n    #[error(\"Illegal URL ({url}): {error}\")]\n    IllegalUrl { url: String, error: url::ParseError },\n}\n\n/// Errors related to the downstream request.\n#[derive(Debug, thiserror::Error)]\npub enum DownstreamRequestError {\n    #[error(\"Request HOST header is missing or invalid\")]\n    InvalidHost,\n\n    #[error(\"Request URL is invalid\")]\n    InvalidUrl,\n}\n\n/// An enum representing outcomes where the guest does not produce a standard\n/// HTTP response, but instead signals for a different action.\n#[derive(Debug, thiserror::Error)]\npub enum NonHttpResponse {\n    #[error(\"graceful Pushpin handoff\")]\n    HandoffToPushpin(HandoffInfo),\n    #[error(\"graceful Backend handoff\")]\n    HandoffToBackend(HandoffInfo),\n}\n"
  },
  {
    "path": "src/execute.rs",
    "content": "//! Guest code execution.\n\nuse {\n    crate::{\n        Error,\n        acl::Acls,\n        adapt,\n        body::Body,\n        body_tee::tee,\n        cache::Cache,\n        component as compute,\n        config::{\n            Backends, DeviceDetection, Dictionaries, ExperimentalModule, FakeValidFastlyKeys,\n            Geolocation, UnknownImportBehavior,\n        },\n        downstream::{DownstreamMetadata, DownstreamRequest, DownstreamResponse, prepare_request},\n        error::{ExecutionError, NonHttpResponse},\n        handoff::{HandoffConfig, HandoffRequestInfo, HandoffTlsConfig, perform_handoff},\n        linking::{ComponentCtx, WasmCtx, create_store, link_host_functions},\n        object_store::ObjectStores,\n        sandbox::Sandbox,\n        secret_store::SecretStores,\n        shielding_site::ShieldingSites,\n        upstream::TlsConfig,\n    },\n    futures::{\n        Future,\n        task::{Context, Poll},\n    },\n    http::StatusCode,\n    hyper::{Request, Response},\n    pin_project::pin_project,\n    std::{\n        collections::HashSet,\n        fmt, fs,\n        io::Write,\n        net::{Ipv4Addr, SocketAddr},\n        path::{Path, PathBuf},\n        pin::Pin,\n        sync::{\n            Arc, Mutex,\n            atomic::{AtomicBool, AtomicU64, Ordering},\n        },\n        thread::{self, JoinHandle},\n        time::{Duration, Instant, SystemTime},\n    },\n    tokio::sync::Mutex as AsyncMutex,\n    tokio::sync::oneshot::{self, Sender},\n    tracing::{Instrument, Level, error, event, info, info_span, warn},\n    wasmtime::{\n        Engine, GuestProfiler, InstancePre, Linker, Module, ProfilingStrategy,\n        component::{self, Component},\n    },\n    wasmtime_wasi::I32Exit,\n};\n\npub use wasmtime::WasmFeatures;\n\npub const DEFAULT_EPOCH_INTERRUPTION_PERIOD: Duration = Duration::from_micros(50);\n\nconst NEXT_REQ_PENDING_MAX: usize = 5;\nconst REGION_NONE: &str = \"none\";\n\nenum Instance {\n    Module(Module, InstancePre<WasmCtx>),\n    Component(\n        Component,\n        compute::bindings::AdapterServicePre<ComponentCtx>,\n    ),\n}\n\nimpl Instance {\n    fn unwrap_module(&self) -> (&Module, &InstancePre<WasmCtx>) {\n        match self {\n            Instance::Module(m, i) => (m, i),\n            Instance::Component(_, _) => panic!(\"unwrap_module called on a component\"),\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct GuestProfileConfig {\n    /// Path to write profiling results from the guest. In serve mode,\n    /// this must refer to a directory, while in run mode it names\n    /// a file.\n    pub path: PathBuf,\n    /// Period at which the guest should be profiled.\n    pub sample_period: Duration,\n}\n\npub struct NextRequest(Option<(Box<DownstreamRequest>, Arc<ExecuteCtx>)>);\n\nimpl NextRequest {\n    pub fn into_request(mut self) -> Option<DownstreamRequest> {\n        self.0.take().map(|(r, _)| *r)\n    }\n}\n\nimpl fmt::Debug for NextRequest {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let debug = self.0.as_ref().map(|(r, _)| r);\n        f.debug_tuple(\"NextRequest\")\n            .field(&debug)\n            .finish_non_exhaustive()\n    }\n}\n\nimpl Drop for NextRequest {\n    fn drop(&mut self) {\n        let Some((req, ctx)) = self.0.take() else {\n            return;\n        };\n\n        ctx.retry_request(*req);\n    }\n}\n\n/// Execution context used by a [`ViceroyService`](struct.ViceroyService.html).\n///\n/// This is all of the state needed to instantiate a module, in order to respond to an HTTP\n/// request. Note that it is very important that `ExecuteCtx` be cheaply clonable, as it is cloned\n/// every time that a viceroy service handles an incoming connection.\npub struct ExecuteCtx {\n    /// A reference to the global context for Wasm compilation.\n    engine: Engine,\n    /// An almost-linked Instance: each import function is linked, just needs a Store\n    instance_pre: Arc<Instance>,\n    /// The acls for this execution.\n    acls: Acls,\n    /// The backends for this execution.\n    backends: Backends,\n    /// The device detection mappings for this execution.\n    device_detection: DeviceDetection,\n    /// The geolocation mappings for this execution.\n    geolocation: Geolocation,\n    /// Preloaded TLS certificates and configuration\n    tls_config: TlsConfig,\n    /// The dictionaries for this execution.\n    dictionaries: Dictionaries,\n    /// Path to the config, defaults to None\n    config_path: Option<PathBuf>,\n    /// Where to direct logging endpoint messages, defaults to stdout\n    capture_logs: Arc<Mutex<dyn Write + Send>>,\n    /// Whether to treat stdout as a logging endpoint\n    log_stdout: bool,\n    /// Whether to treat stderr as a logging endpoint\n    log_stderr: bool,\n    /// The local Pushpin proxy port\n    local_pushpin_proxy_port: Option<u16>,\n    /// The ID to assign the next incoming request\n    next_req_id: Arc<AtomicU64>,\n    /// The ObjectStore associated with this instance of Viceroy\n    object_store: ObjectStores,\n    /// The secret stores for this execution.\n    secret_stores: SecretStores,\n    /// The shielding sites for this execution.\n    shielding_sites: ShieldingSites,\n    /// The valid mock Fastly API keys that should be considered valid.\n    fake_valid_fastly_keys: FakeValidFastlyKeys,\n    /// The cache for this service.\n    cache: Arc<Cache>,\n    /// Senders waiting for new requests for reusable sandboxes.\n    pending_reuse: Arc<AsyncMutex<Vec<Sender<NextRequest>>>>,\n    epoch_increment_thread: Option<JoinHandle<()>>,\n    // `Arc` so that it can be tracked both by this context and `epoch_increment_thread`.\n    epoch_increment_stop: Arc<AtomicBool>,\n    /// Configuration for guest profiling if enabled\n    guest_profile_config: Option<Arc<GuestProfileConfig>>,\n}\n\nimpl ExecuteCtx {\n    /// Build a new execution context, given the path to a module and a set of experimental wasi modules.\n    pub fn build(\n        module_path: impl AsRef<Path>,\n        profiling_strategy: ProfilingStrategy,\n        wasi_modules: HashSet<ExperimentalModule>,\n        guest_profile_config: Option<GuestProfileConfig>,\n        unknown_import_behavior: UnknownImportBehavior,\n        adapt_components: bool,\n        wasm_features: WasmFeatures,\n    ) -> Result<ExecuteCtxBuilder, Error> {\n        let input = fs::read(&module_path)?;\n\n        let is_wat = module_path\n            .as_ref()\n            .extension()\n            .map(|str| str == \"wat\")\n            .unwrap_or(false);\n\n        // When the input wasn't a component, but we're automatically adapting,\n        // apply the component adapter.\n        let is_component = adapt::is_component(&input);\n        let (is_wat, is_component, input) = if !is_component && adapt_components {\n            let input = if is_wat {\n                let text = String::from_utf8(input).map_err(|_| {\n                    anyhow::anyhow!(\"Failed to parse {}\", module_path.as_ref().display())\n                })?;\n                adapt::adapt_wat(&text)?\n            } else {\n                adapt::adapt_bytes(&input)?\n            };\n\n            (false, true, input)\n        } else {\n            (is_wat, is_component, input)\n        };\n\n        let config = &configure_wasmtime(wasm_features, profiling_strategy);\n        let engine = Engine::new(config)?;\n        let instance_pre = if is_component {\n            warn!(\n                \"\n\n   +------------------------------------------------------------------------+\n   |                                                                        |\n   | Wasm Component support in viceroy is in active development, and is not |\n   |                    supported for general consumption.                  |\n   |                                                                        |\n   +------------------------------------------------------------------------+\n\n            \"\n            );\n\n            // If logging isn't enabled, print the notice to stderr.\n            if !tracing::enabled!(Level::WARN) {\n                eprintln!(\n                    \"\n\n   +------------------------------------------------------------------------+\n   |                                                                        |\n   | Wasm Component support in viceroy is in active development, and is not |\n   |                    supported for general consumption.                  |\n   |                                                                        |\n   +------------------------------------------------------------------------+\n\n            \"\n                );\n            }\n\n            let mut linker: component::Linker<ComponentCtx> = component::Linker::new(&engine);\n            compute::link_host_functions(&mut linker)?;\n            let component = if is_wat {\n                Component::from_file(&engine, &module_path)?\n            } else {\n                Component::from_binary(&engine, &input)?\n            };\n\n            match unknown_import_behavior {\n                UnknownImportBehavior::LinkError => (),\n                UnknownImportBehavior::Trap => {\n                    linker.define_unknown_imports_as_traps(&component)?\n                }\n            }\n\n            let instance_pre = linker.instantiate_pre(&component)?;\n            Instance::Component(\n                component,\n                compute::bindings::AdapterServicePre::new(instance_pre)?,\n            )\n        } else {\n            let mut linker = Linker::new(&engine);\n            link_host_functions(&mut linker, &wasi_modules)?;\n            let module = if is_wat {\n                Module::from_file(&engine, &module_path)?\n            } else {\n                Module::from_binary(&engine, &input)?\n            };\n\n            match unknown_import_behavior {\n                UnknownImportBehavior::LinkError => (),\n                UnknownImportBehavior::Trap => linker.define_unknown_imports_as_traps(&module)?,\n            }\n\n            let instance_pre = linker.instantiate_pre(&module)?;\n            Instance::Module(module, instance_pre)\n        };\n\n        // Create the epoch-increment thread. Note that the period for epoch\n        // interruptions is driven by the guest profiling sample period if\n        // provided as guest stack sampling is done from the epoch\n        // interruption callback.\n\n        let epoch_increment_stop = Arc::new(AtomicBool::new(false));\n        let engine_clone = engine.clone();\n        let epoch_increment_stop_clone = epoch_increment_stop.clone();\n        let sample_period = guest_profile_config\n            .as_ref()\n            .map(|c| c.sample_period)\n            .unwrap_or(DEFAULT_EPOCH_INTERRUPTION_PERIOD);\n        let epoch_increment_thread = Some(thread::spawn(move || {\n            while !epoch_increment_stop_clone.load(Ordering::Relaxed) {\n                thread::sleep(sample_period);\n                engine_clone.increment_epoch();\n            }\n        }));\n\n        let inner = Self {\n            engine,\n            instance_pre: Arc::new(instance_pre),\n            acls: Acls::new(),\n            backends: Backends::default(),\n            device_detection: DeviceDetection::default(),\n            geolocation: Geolocation::default(),\n            tls_config: TlsConfig::new()?,\n            dictionaries: Dictionaries::default(),\n            config_path: None,\n            capture_logs: Arc::new(Mutex::new(std::io::stdout())),\n            log_stdout: false,\n            log_stderr: false,\n            local_pushpin_proxy_port: None,\n            next_req_id: Arc::new(AtomicU64::new(0)),\n            object_store: ObjectStores::new(),\n            secret_stores: SecretStores::new(),\n            shielding_sites: ShieldingSites::new(),\n            fake_valid_fastly_keys: FakeValidFastlyKeys::new(),\n            epoch_increment_thread,\n            epoch_increment_stop,\n            guest_profile_config: guest_profile_config.map(Arc::new),\n            cache: Arc::new(Cache::default()),\n            pending_reuse: Arc::new(AsyncMutex::new(vec![])),\n        };\n\n        Ok(ExecuteCtxBuilder { inner })\n    }\n\n    /// Create a new execution context, given the path to a module and a set of experimental wasi modules.\n    pub fn new(\n        module_path: impl AsRef<Path>,\n        profiling_strategy: ProfilingStrategy,\n        wasi_modules: HashSet<ExperimentalModule>,\n        guest_profile_config: Option<GuestProfileConfig>,\n        unknown_import_behavior: UnknownImportBehavior,\n        adapt_components: bool,\n        wasm_features: WasmFeatures,\n    ) -> Result<Arc<Self>, Error> {\n        ExecuteCtx::build(\n            module_path,\n            profiling_strategy,\n            wasi_modules,\n            guest_profile_config,\n            unknown_import_behavior,\n            adapt_components,\n            wasm_features,\n        )?\n        .finish()\n    }\n\n    /// Get the engine for this execution context.\n    pub fn engine(&self) -> &Engine {\n        &self.engine\n    }\n\n    /// Get the acls for this execution context.\n    pub fn acls(&self) -> &Acls {\n        &self.acls\n    }\n\n    /// Get the backends for this execution context.\n    pub fn backends(&self) -> &Backends {\n        &self.backends\n    }\n\n    /// Get the device detection mappings for this execution context.\n    pub fn device_detection(&self) -> &DeviceDetection {\n        &self.device_detection\n    }\n\n    /// Get the geolocation mappings for this execution context.\n    pub fn geolocation(&self) -> &Geolocation {\n        &self.geolocation\n    }\n\n    /// Get the dictionaries for this execution context.\n    pub fn dictionaries(&self) -> &Dictionaries {\n        &self.dictionaries\n    }\n\n    /// Where to direct logging endpoint messages. Defaults to stdout.\n    pub fn capture_logs(&self) -> Arc<Mutex<dyn Write + Send>> {\n        self.capture_logs.clone()\n    }\n\n    /// Whether to treat stdout as a logging endpoint.\n    pub fn log_stdout(&self) -> bool {\n        self.log_stdout\n    }\n\n    /// Whether to treat stderr as a logging endpoint.\n    pub fn log_stderr(&self) -> bool {\n        self.log_stderr\n    }\n\n    /// Gets the TLS configuration\n    pub fn tls_config(&self) -> &TlsConfig {\n        &self.tls_config\n    }\n\n    async fn maybe_receive_response(\n        receiver: oneshot::Receiver<DownstreamResponse>,\n    ) -> Option<(Response<Body>, Option<anyhow::Error>)> {\n        match receiver.await.ok()? {\n            DownstreamResponse::Http(resp) => Some((resp, None)),\n            DownstreamResponse::HandoffToPushpin(info) => Some((\n                Response::new(Body::empty()),\n                Some(NonHttpResponse::HandoffToPushpin(info).into()),\n            )),\n            DownstreamResponse::HandoffToBackend(info) => Some((\n                Response::new(Body::empty()),\n                Some(NonHttpResponse::HandoffToBackend(info).into()),\n            )),\n        }\n    }\n\n    /// Asynchronously handle a request.\n    ///\n    /// This method fully instantiates the wasm module housed within the `ExecuteCtx`,\n    /// including running the wasm start function. It then proceeds to execute the\n    /// instantiated module's WASI entry point, running to completion. If execution\n    /// results in an error, a response is still produced, but with a 500 status code.\n    ///\n    /// Build time: Before you build or test your code, we recommend to set the release flag\n    /// e.g. `cargo test --release` otherwise the execution will be very slow. This has to do\n    /// with the Cranelift compiler, which is extremely slow when compiled in debug mode.\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use std::collections::HashSet;\n    /// # use wasmtime::WasmFeatures;\n    /// use hyper::{Body, http::Request};\n    /// # use viceroy_lib::{Error, ExecuteCtx, ProfilingStrategy, ViceroyService};\n    /// # async fn f() -> Result<(), Error> {\n    /// # let req = Request::new(Body::from(\"\"));\n    /// let adapt_core_wasm = false;\n    /// let wasm_features = WasmFeatures::default();\n    /// let ctx = ExecuteCtx::new(\n    ///     \"path/to/a/file.wasm\",\n    ///     ProfilingStrategy::None,\n    ///     HashSet::new(),\n    ///     None,\n    ///     Default::default(),\n    ///     adapt_core_wasm,\n    ///     wasm_features\n    /// )?;\n    /// let local = \"127.0.0.1:80\".parse().unwrap();\n    /// let remote = \"127.0.0.1:0\".parse().unwrap();\n    /// let resp = ctx.handle_request(req, local, remote).await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn handle_request(\n        self: Arc<Self>,\n        mut incoming_req: Request<hyper::Body>,\n        local: SocketAddr,\n        remote: SocketAddr,\n    ) -> Result<(Response<Body>, Option<anyhow::Error>), Error> {\n        let orig_req_on_upgrade = hyper::upgrade::on(&mut incoming_req);\n        let (incoming_req_parts, incoming_req_body) = incoming_req.into_parts();\n        let local_pushpin_proxy_port = self.local_pushpin_proxy_port;\n\n        let (body_for_wasm, orig_body_tee) = tee(incoming_req_body).await;\n        let orig_request_info_for_pushpin = HandoffRequestInfo::from_parts(&incoming_req_parts);\n\n        let original_headers = incoming_req_parts.headers.clone();\n        let req = prepare_request(Request::from_parts(incoming_req_parts, body_for_wasm))?;\n\n        let req_id = self\n            .next_req_id\n            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);\n\n        let metadata = DownstreamMetadata {\n            req_id,\n            server_addr: local,\n            client_addr: remote,\n            compliance_region: String::from(REGION_NONE),\n            original_headers,\n        };\n\n        let backends = self.backends.clone();\n        let tls_config = self.tls_config.clone();\n\n        let (resp, mut err) = self.reuse_or_spawn_guest(req, metadata).await;\n\n        let span = info_span!(\"request\", id = req_id);\n        let _span = span.enter();\n\n        info!(\"response status: {:?}\", resp.status());\n\n        if let Some(e) = err {\n            match e.downcast::<NonHttpResponse>() {\n                Ok(NonHttpResponse::HandoffToPushpin(handoff_info)) => {\n                    let backend_name = handoff_info.backend_name.clone();\n\n                    info!(\"Pushpin handoff signaled to backend '{backend_name}'\");\n\n                    let local_pushpin_proxy_port = match local_pushpin_proxy_port {\n                        None => {\n                            error!(\"Pushpin handoff signaled, but Pushpin mode not enabled.\");\n                            let mut resp = Response::new(Body::from(hyper::Body::from(\n                                \"Pushpin handoff signaled, but Pushpin mode not enabled.\",\n                            )));\n                            *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;\n                            return Ok((resp, None));\n                        }\n                        Some(port) => port,\n                    };\n\n                    let pushpin_addr =\n                        SocketAddr::new(Ipv4Addr::LOCALHOST.into(), local_pushpin_proxy_port)\n                            .to_string();\n\n                    // To distinguish backends, the HTTP request header `pushpin-route: <backend_name>` is added\n                    // to the request. Pushpin routes should be configured with `id=<backend_name>`.\n                    let additional_headers =\n                        vec![(\"pushpin-route\".to_string(), backend_name.clone())];\n\n                    let handoff_resp = perform_handoff(\n                        handoff_info.request_info,\n                        orig_request_info_for_pushpin,\n                        orig_body_tee,\n                        orig_req_on_upgrade,\n                        HandoffConfig {\n                            target_addr: pushpin_addr.clone(),\n                            host_header: pushpin_addr, // Host header is the local proxy address\n                            display_name: format!(\"Pushpin [{backend_name}]\"),\n                            path_prefix: None, // Path prefix is applied by Pushpin route\n                            extra_headers: additional_headers,\n                            tls_config: None, // Pushpin only runs locally, without https\n                        },\n                    )\n                    .await;\n\n                    let (p, hyper_body) = handoff_resp.into_parts();\n                    return Ok((Response::from_parts(p, Body::from(hyper_body)), None));\n                }\n                Ok(NonHttpResponse::HandoffToBackend(handoff_info)) => {\n                    let backend_name = handoff_info.backend_name.clone();\n\n                    info!(\"Backend handoff signaled to backend '{backend_name}'\");\n\n                    let backend = backends.get(backend_name.as_str());\n                    let backend = match backend {\n                        None => {\n                            error!(\"Backend handoff signaled to unknown backend '{backend_name}'.\");\n                            let mut resp = Response::new(Body::from(hyper::Body::from(format!(\n                                \"Backend handoff signaled to unknown backend '{backend_name}'.\"\n                            ))));\n                            *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;\n                            return Ok((resp, None));\n                        }\n                        Some(backend) => backend,\n                    };\n\n                    let backend_uri = backend.uri.clone();\n                    let tls_handoff_config = if backend_uri.scheme_str() == Some(\"https\") {\n                        Some(HandoffTlsConfig {\n                            ca_certs: backend.ca_certs.clone(),\n                            client_cert: backend.client_cert.clone(),\n                            use_sni: backend.use_sni,\n                            cert_host: backend.cert_host.clone(),\n                            dns_name_fallback: backend.uri.host().unwrap_or_default().to_string(),\n                            is_grpc: backend.grpc,\n                            base_tls_config: tls_config.clone(), // Pass the builder from self\n                        })\n                    } else {\n                        None\n                    };\n\n                    let backend_host = backend_uri\n                        .authority()\n                        .map(|a| match (a.port(), backend_uri.scheme_str()) {\n                            (None, Some(\"wss\")) => format!(\"{}:443\", a),\n                            (None, Some(\"https\")) => format!(\"{}:443\", a),\n                            (None, Some(\"ws\")) => format!(\"{}:80\", a),\n                            (None, Some(\"http\")) => format!(\"{}:80\", a),\n                            _ => a.to_string(), // already has a port, or scheme is unknown (unlikely)\n                        })\n                        .unwrap_or_default();\n                    // Use override_host if present, otherwise fallback to the URI's authority\n                    let host_header = backend\n                        .override_host\n                        .clone()\n                        .map(|host| host.to_str()\n                            .expect(\"`backend.override_host`, if provided, should be a valid header value\")\n                            .to_string())\n                        .unwrap_or_else(|| backend_host.clone());\n\n                    // Prepend a path if there's actually a path in the backend URI\n                    let path_prefix = (!backend_uri.path().is_empty() && backend_uri.path() != \"/\")\n                        .then(|| backend_uri.path().to_string());\n\n                    let handoff_resp = perform_handoff(\n                        handoff_info.request_info,\n                        orig_request_info_for_pushpin,\n                        orig_body_tee,\n                        orig_req_on_upgrade,\n                        HandoffConfig {\n                            target_addr: backend_host,\n                            host_header,\n                            display_name: format!(\"Backend [{backend_name}]\"),\n                            path_prefix,\n                            extra_headers: vec![], // Standard backends don't need extra headers\n                            tls_config: tls_handoff_config,\n                        },\n                    )\n                    .await;\n\n                    let (p, hyper_body) = handoff_resp.into_parts();\n                    return Ok((Response::from_parts(p, Body::from(hyper_body)), None));\n                }\n                Err(e) => {\n                    err = Some(e);\n                }\n            }\n        }\n\n        Ok((resp, err))\n    }\n\n    /// Spawn a new guest to process a request whose processing was never attempted by\n    /// a reused sandbox.\n    pub(crate) fn retry_request(self: Arc<Self>, mut downstream: DownstreamRequest) {\n        if downstream.sender.is_closed() {\n            return;\n        }\n\n        tokio::task::spawn(async move {\n            let (sender, receiver) = oneshot::channel();\n            let original = std::mem::replace(&mut downstream.sender, sender);\n            let (resp, err) = self.spawn_guest(downstream, receiver).await;\n            let resp = guest_result_to_response(resp, err);\n            let _ = original.send(DownstreamResponse::Http(resp));\n        });\n    }\n\n    pub async fn handle_request_with_runtime_error(\n        self: Arc<Self>,\n        incoming_req: Request<hyper::Body>,\n        local: SocketAddr,\n        remote: SocketAddr,\n    ) -> Result<Response<Body>, Error> {\n        let result = self.handle_request(incoming_req, local, remote).await?;\n        let resp = guest_result_to_response(result.0, result.1);\n\n        Ok(resp)\n    }\n\n    async fn reuse_or_spawn_guest(\n        self: Arc<Self>,\n        req: Request<Body>,\n        metadata: DownstreamMetadata,\n    ) -> (Response<Body>, Option<anyhow::Error>) {\n        let (sender, receiver) = oneshot::channel();\n        let downstream = DownstreamRequest {\n            req,\n            sender,\n            metadata,\n        };\n\n        let mut next_req = NextRequest(Some((Box::new(downstream), self.clone())));\n        let mut reusable = self.pending_reuse.lock().await;\n\n        while let Some(pending) = reusable.pop() {\n            match pending.send(next_req) {\n                Ok(()) => {\n                    // Drop lock and wait for the guest to process our request.\n                    drop(reusable);\n\n                    if let Some(response) = Self::maybe_receive_response(receiver).await {\n                        return response;\n                    }\n                    return (Response::default(), None);\n                }\n                Err(nr) => next_req = nr,\n            }\n        }\n\n        drop(reusable);\n\n        let downstream = next_req\n            .into_request()\n            .expect(\"request should still be unprocessed\");\n        self.spawn_guest(downstream, receiver).await\n    }\n\n    async fn spawn_guest(\n        self: Arc<Self>,\n        downstream: DownstreamRequest,\n        receiver: oneshot::Receiver<DownstreamResponse>,\n    ) -> (Response<Body>, Option<anyhow::Error>) {\n        let active_cpu_time_us = Arc::new(AtomicU64::new(0));\n\n        // Spawn a separate task to run the guest code. That allows _this_ method to return a response early\n        // if the guest sends one, while the guest continues to run afterward within its task.\n        let req_id = downstream.metadata.req_id;\n        let guest_handle = tokio::task::spawn(CpuTimeTracking::new(\n            active_cpu_time_us.clone(),\n            self.run_guest(downstream, active_cpu_time_us)\n                .instrument(info_span!(\"request\", id = req_id)),\n        ));\n\n        if let Some(response) = Self::maybe_receive_response(receiver).await {\n            return response;\n        }\n\n        match guest_handle\n            .await\n            .expect(\"guest worker finished without panicking\")\n        {\n            Ok(_) => (Response::new(Body::empty()), None),\n            Err(ExecutionError::WasmTrap(e)) => {\n                event!(\n                    Level::ERROR,\n                    \"There was an error handling the request {}\",\n                    e.to_string()\n                );\n                (anyhow_response(&e), Some(e))\n            }\n            Err(e) => panic!(\"failed to run guest: {}\", e),\n        }\n    }\n\n    async fn run_guest(\n        self: Arc<Self>,\n        downstream: DownstreamRequest,\n        active_cpu_time_us: Arc<AtomicU64>,\n    ) -> Result<(), ExecutionError> {\n        info!(\n            \"handling request {} {}\",\n            downstream.req.method(),\n            downstream.req.uri()\n        );\n        let start_timestamp = Instant::now();\n        let req_id = downstream.metadata.req_id;\n        let sandbox = Sandbox::new(downstream, active_cpu_time_us, self.clone());\n\n        let guest_profile_path = self.guest_profile_config.as_deref().map(|pcfg| {\n            let now = SystemTime::now()\n                .duration_since(SystemTime::UNIX_EPOCH)\n                .unwrap()\n                .as_secs();\n            pcfg.path.join(format!(\"{}-{}.json\", now, req_id))\n        });\n\n        match self.instance_pre.as_ref() {\n            Instance::Component(component, instance_pre) => {\n                let profiler = self.guest_profile_config.as_deref().map(|pcfg| {\n                    let program_name = \"main\";\n                    GuestProfiler::new_component(\n                        program_name,\n                        pcfg.sample_period,\n                        component.clone(),\n                        std::iter::empty(),\n                    )\n                });\n\n                let req = sandbox.downstream_request();\n                let body = sandbox.downstream_request_body();\n\n                let mut store = ComponentCtx::create_store(&self, sandbox, profiler, |ctx| {\n                    ctx.arg(\"compute-app\");\n                })\n                .map_err(ExecutionError::Context)?;\n\n                let compute = instance_pre\n                    .instantiate_async(&mut store)\n                    .await\n                    .map_err(ExecutionError::Instantiation)?;\n\n                let result = compute\n                    .fastly_compute_http_incoming()\n                    .call_handle(&mut store, req.into(), body.into())\n                    .await;\n\n                let outcome = match result {\n                    Ok(Ok(())) => Ok(()),\n\n                    Ok(Err(())) => {\n                        event!(Level::ERROR, \"WebAssembly exited with an error\");\n                        Err(ExecutionError::WasmTrap(anyhow::Error::msg(\"failed\")))\n                    }\n\n                    Err(e) => {\n                        if let Some(exit) = e.downcast_ref::<I32Exit>() {\n                            if exit.0 == 0 {\n                                Ok(())\n                            } else {\n                                event!(Level::ERROR, \"WebAssembly exited with error: {:?}\", e);\n                                Err(ExecutionError::WasmTrap(e))\n                            }\n                        } else {\n                            event!(Level::ERROR, \"WebAssembly trapped: {:?}\", e);\n                            Err(ExecutionError::WasmTrap(e))\n                        }\n                    }\n                };\n\n                // If we collected a profile, write it to the file\n                write_profile_component(&mut store, guest_profile_path.as_ref());\n\n                // Ensure the downstream response channel is closed, whether or not a response was\n                // sent during execution.\n                let resp = outcome\n                    .as_ref()\n                    .err()\n                    .map(exec_err_to_response)\n                    .unwrap_or_default();\n                store\n                    .data_mut()\n                    .sandbox\n                    .close_downstream_response_sender(resp);\n\n                let request_duration = Instant::now().duration_since(start_timestamp);\n\n                info!(\n                    \"guest completed using {} of WebAssembly heap\",\n                    bytesize::ByteSize::b(store.data().limiter().memory_allocated as u64),\n                );\n\n                info!(\"guest completed in {:.0?}\", request_duration);\n\n                outcome\n            }\n\n            Instance::Module(module, instance_pre) => {\n                let profiler = self.guest_profile_config.as_deref().map(|pcfg| {\n                    let program_name = \"main\";\n                    GuestProfiler::new(\n                        program_name,\n                        pcfg.sample_period,\n                        vec![(program_name.to_string(), module.clone())],\n                    )\n                });\n\n                // We currently have to postpone linking and instantiation to the guest task\n                // due to wasmtime limitations, in particular the fact that `Instance` is not `Send`.\n                // However, the fact that the module itself is created within `ExecuteCtx::new`\n                // means that the heavy lifting happens only once.\n                let mut store = create_store(&self, sandbox, profiler, |ctx| {\n                    ctx.arg(\"compute-app\");\n                })\n                .map_err(ExecutionError::Context)?;\n\n                let instance = instance_pre\n                    .instantiate_async(&mut store)\n                    .await\n                    .map_err(ExecutionError::Instantiation)?;\n\n                // Pull out the `_start` function, which by convention with WASI is the main entry point for\n                // an application.\n                let main_func = instance\n                    .get_typed_func::<(), ()>(&mut store, \"_start\")\n                    .map_err(ExecutionError::Typechecking)?;\n\n                // Invoke the entrypoint function, which may or may not send a downstream response.\n                let outcome = match main_func.call_async(&mut store, ()).await {\n                    Ok(_) => Ok(()),\n                    Err(e) => {\n                        if let Some(exit) = e.downcast_ref::<I32Exit>() {\n                            if exit.0 == 0 {\n                                Ok(())\n                            } else {\n                                event!(Level::ERROR, \"WebAssembly exited with error: {:?}\", e);\n                                Err(ExecutionError::WasmTrap(e))\n                            }\n                        } else {\n                            event!(Level::ERROR, \"WebAssembly trapped: {:?}\", e);\n                            Err(ExecutionError::WasmTrap(e))\n                        }\n                    }\n                };\n\n                // If we collected a profile, write it to the file\n                write_profile(&mut store, guest_profile_path.as_ref());\n\n                // Ensure the downstream response channel is closed, whether or not a response was\n                // sent during execution.\n                let resp = outcome\n                    .as_ref()\n                    .err()\n                    .map(exec_err_to_response)\n                    .unwrap_or_default();\n                store.data_mut().close_downstream_response_sender(resp);\n\n                let request_duration = Instant::now().duration_since(start_timestamp);\n\n                info!(\n                    \"request completed using {} of WebAssembly heap\",\n                    bytesize::ByteSize::b(store.data().limiter().memory_allocated as u64)\n                );\n\n                info!(\"request completed in {:.0?}\", request_duration);\n\n                outcome\n            }\n        }\n    }\n\n    pub async fn run_main(\n        self: Arc<Self>,\n        program_name: &str,\n        args: &[String],\n    ) -> Result<(), anyhow::Error> {\n        // placeholders for request, result sender channel, and remote IP\n        let req = Request::get(\"http://example.com/\").body(Body::empty())?;\n        let metadata = DownstreamMetadata {\n            req_id: 0,\n            server_addr: (Ipv4Addr::LOCALHOST, 80).into(),\n            client_addr: (Ipv4Addr::LOCALHOST, 0).into(),\n            compliance_region: String::from(REGION_NONE),\n            original_headers: Default::default(),\n        };\n        let (sender, receiver) = oneshot::channel();\n        let downstream = DownstreamRequest {\n            req,\n            sender,\n            metadata,\n        };\n        let active_cpu_time_us = Arc::new(AtomicU64::new(0));\n\n        let sandbox = Sandbox::new(downstream, active_cpu_time_us.clone(), self.clone());\n\n        if let Instance::Component(_, _) = self.instance_pre.as_ref() {\n            panic!(\"components not currently supported with `run`\");\n        }\n\n        let (module, instance_pre) = self.instance_pre.unwrap_module();\n\n        let profiler = self.guest_profile_config.as_deref().map(|pcfg| {\n            GuestProfiler::new(\n                program_name,\n                pcfg.sample_period,\n                vec![(program_name.to_string(), module.clone())],\n            )\n        });\n\n        let mut store = create_store(&self, sandbox, profiler, |builder| {\n            builder.arg(program_name);\n            for arg in args {\n                builder.arg(arg);\n            }\n        })\n        .map_err(ExecutionError::Context)?;\n\n        let instance = instance_pre\n            .instantiate_async(&mut store)\n            .await\n            .map_err(ExecutionError::Instantiation)?;\n\n        // Pull out the `_start` function, which by convention with WASI is the main entry point for\n        // an application.\n        let main_func = instance\n            .get_typed_func::<(), ()>(&mut store, \"_start\")\n            .map_err(ExecutionError::Typechecking)?;\n\n        // Invoke the entrypoint function and collect its exit code\n        let result =\n            CpuTimeTracking::new(active_cpu_time_us, main_func.call_async(&mut store, ())).await;\n\n        // If we collected a profile, write it to the file\n        write_profile(\n            &mut store,\n            self.guest_profile_config.as_deref().map(|cfg| &cfg.path),\n        );\n\n        // Ensure the downstream response channel is closed, whether or not a response was\n        // sent during execution.\n        store\n            .data_mut()\n            .close_downstream_response_sender(Response::default());\n\n        // We don't do anything with any response on the receiver, but\n        // it's important to keep it alive until after the program has\n        // finished.\n        drop(receiver);\n\n        result\n    }\n\n    pub fn cache(&self) -> &Arc<Cache> {\n        &self.cache\n    }\n\n    pub fn config_path(&self) -> Option<&Path> {\n        self.config_path.as_deref()\n    }\n\n    pub fn object_store(&self) -> &ObjectStores {\n        &self.object_store\n    }\n\n    pub fn secret_stores(&self) -> &SecretStores {\n        &self.secret_stores\n    }\n\n    pub fn shielding_sites(&self) -> &ShieldingSites {\n        &self.shielding_sites\n    }\n\n    /// Get the valid mock Fastly API keys for this execution context.\n    pub fn fake_valid_fastly_keys(&self) -> &FakeValidFastlyKeys {\n        &self.fake_valid_fastly_keys\n    }\n\n    pub async fn register_pending_downstream(&self) -> Option<oneshot::Receiver<NextRequest>> {\n        let mut pending = self.pending_reuse.lock().await;\n\n        if pending.len() >= NEXT_REQ_PENDING_MAX {\n            return None;\n        }\n\n        let (tx, rx) = oneshot::channel();\n        pending.push(tx);\n\n        Some(rx)\n    }\n\n    pub fn is_component(&self) -> bool {\n        matches!(self.instance_pre.as_ref(), Instance::Component(_, _))\n    }\n}\n\npub struct ExecuteCtxBuilder {\n    inner: ExecuteCtx,\n}\n\nimpl ExecuteCtxBuilder {\n    pub fn finish(self) -> Result<Arc<ExecuteCtx>, Error> {\n        Ok(Arc::new(self.inner))\n    }\n\n    /// Set the acls for this execution context.\n    pub fn with_acls(mut self, acls: Acls) -> Self {\n        self.inner.acls = acls;\n        self\n    }\n\n    /// Set the backends for this execution context.\n    pub fn with_backends(mut self, backends: Backends) -> Self {\n        self.inner.backends = backends;\n        self\n    }\n\n    /// Set the device detection mappings for this execution context.\n    pub fn with_device_detection(mut self, device_detection: DeviceDetection) -> Self {\n        self.inner.device_detection = device_detection;\n        self\n    }\n\n    /// Set the geolocation mappings for this execution context.\n    pub fn with_geolocation(mut self, geolocation: Geolocation) -> Self {\n        self.inner.geolocation = geolocation;\n        self\n    }\n\n    /// Set the dictionaries for this execution context.\n    pub fn with_dictionaries(mut self, dictionaries: Dictionaries) -> Self {\n        self.inner.dictionaries = dictionaries;\n        self\n    }\n\n    /// Set the object store for this execution context.\n    pub fn with_object_stores(mut self, object_store: ObjectStores) -> Self {\n        self.inner.object_store = object_store;\n        self\n    }\n\n    /// Set the secret stores for this execution context.\n    pub fn with_secret_stores(mut self, secret_stores: SecretStores) -> Self {\n        self.inner.secret_stores = secret_stores;\n        self\n    }\n    /// Set the shielding sites for this execution context.\n    pub fn with_shielding_sites(mut self, shielding_sites: ShieldingSites) -> Self {\n        self.inner.shielding_sites = shielding_sites;\n        self\n    }\n\n    /// Set the valid mock Fastly API keys for this execution context.\n    pub fn with_fake_valid_fastly_keys(\n        mut self,\n        fake_valid_fastly_keys: FakeValidFastlyKeys,\n    ) -> Self {\n        self.inner.fake_valid_fastly_keys = fake_valid_fastly_keys;\n        self\n    }\n\n    /// Set the path to the config for this execution context.\n    pub fn with_config_path(mut self, config_path: PathBuf) -> Self {\n        self.inner.config_path = Some(config_path);\n        self\n    }\n\n    /// Set where to direct logging endpoint messages for this execution\n    /// context. Defaults to stdout.\n    pub fn with_capture_logs(mut self, capture_logs: Arc<Mutex<dyn Write + Send>>) -> Self {\n        self.inner.capture_logs = capture_logs;\n        self\n    }\n\n    /// Set the stdout logging policy for this execution context.\n    pub fn with_log_stdout(mut self, log_stdout: bool) -> Self {\n        self.inner.log_stdout = log_stdout;\n        self\n    }\n\n    /// Set the stderr logging policy for this execution context.\n    pub fn with_log_stderr(mut self, log_stderr: bool) -> Self {\n        self.inner.log_stderr = log_stderr;\n        self\n    }\n\n    /// Set the local Pushpin proxy port\n    pub fn with_local_pushpin_proxy_port(mut self, local_pushpin_proxy_port: Option<u16>) -> Self {\n        self.inner.local_pushpin_proxy_port = local_pushpin_proxy_port;\n        self\n    }\n}\n\nfn write_profile_to_file(profile: Box<GuestProfiler>, path: &PathBuf) {\n    match std::fs::File::create(path)\n        .map_err(anyhow::Error::new)\n        .and_then(|output| profile.finish(std::io::BufWriter::new(output)))\n    {\n        Err(e) => {\n            event!(\n                Level::ERROR,\n                \"failed writing profile at {}: {e:#}\",\n                path.display()\n            );\n        }\n        _ => {\n            event!(\n                Level::INFO,\n                \"\\nProfile written to: {}\\nView this profile at https://profiler.firefox.com/.\",\n                path.display()\n            );\n        }\n    }\n}\n\nfn write_profile(store: &mut wasmtime::Store<WasmCtx>, guest_profile_path: Option<&PathBuf>) {\n    if let (Some(profile), Some(path)) =\n        (store.data_mut().take_guest_profiler(), guest_profile_path)\n    {\n        write_profile_to_file(profile, path);\n    }\n}\n\nfn write_profile_component(\n    store: &mut wasmtime::Store<ComponentCtx>,\n    guest_profile_path: Option<&PathBuf>,\n) {\n    if let (Some(profile), Some(path)) =\n        (store.data_mut().take_guest_profiler(), guest_profile_path)\n    {\n        write_profile_to_file(profile, path);\n    }\n}\n\nfn guest_result_to_response(resp: Response<Body>, err: Option<anyhow::Error>) -> Response<Body> {\n    err.as_ref().map(anyhow_response).unwrap_or(resp)\n}\n\nfn exec_err_to_response(err: &ExecutionError) -> Response<Body> {\n    if let ExecutionError::WasmTrap(e) = err {\n        anyhow_response(e)\n    } else {\n        panic!(\"failed to run guest: {err}\")\n    }\n}\n\nfn anyhow_response(err: &anyhow::Error) -> Response<Body> {\n    Response::builder()\n        .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)\n        .body(Body::from(format!(\"{err:?}\").into_bytes()))\n        .unwrap()\n}\n\nimpl Drop for ExecuteCtx {\n    fn drop(&mut self) {\n        if let Some(join_handle) = self.epoch_increment_thread.take() {\n            self.epoch_increment_stop.store(true, Ordering::Relaxed);\n            join_handle.join().unwrap();\n        }\n    }\n}\n\nfn configure_wasmtime(\n    wasm_features: WasmFeatures,\n    profiling_strategy: ProfilingStrategy,\n) -> wasmtime::Config {\n    use wasmtime::{Config, InstanceAllocationStrategy, WasmBacktraceDetails};\n\n    let mut config = Config::new();\n    config.debug_info(false); // Keep this disabled - wasmtime will hang if enabled\n    config.wasm_backtrace_details(WasmBacktraceDetails::Enable);\n    config.async_support(true);\n    config.epoch_interruption(true);\n    config.profiler(profiling_strategy);\n\n    config.allocation_strategy(InstanceAllocationStrategy::OnDemand);\n\n    config.wasm_features(wasm_features, true);\n\n    // Wasm permits the \"relaxed\" instructions to be nondeterministic\n    // between runs, but requires them to be deterministic within runs.\n    // Snapshotting a program's execution to avoid redundantly running\n    // initialization code on each request is an important optimization,\n    // so we enable deterministic lowerings for relaxed SIMD to ensure\n    // that it works consistently even if the initialization runs on a\n    // different host architecture.\n    config.relaxed_simd_deterministic(true);\n\n    config\n}\n\n#[pin_project]\nstruct CpuTimeTracking<F> {\n    #[pin]\n    future: F,\n    time_spent: Arc<AtomicU64>,\n}\n\nimpl<F> CpuTimeTracking<F> {\n    fn new(time_spent: Arc<AtomicU64>, future: F) -> Self {\n        CpuTimeTracking { future, time_spent }\n    }\n}\n\nimpl<E, F: Future<Output = Result<(), E>>> Future for CpuTimeTracking<F> {\n    type Output = F::Output;\n\n    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {\n        let me = self.project();\n\n        let start = Instant::now();\n        let result = me.future.poll(cx);\n        // 2^64 microseconds is over half a million years, so I'm not terribly\n        // worried about this cast.\n        let runtime = start.elapsed().as_micros() as u64;\n        let _ = me.time_spent.fetch_add(runtime, Ordering::SeqCst);\n        result\n    }\n}\n"
  },
  {
    "path": "src/framing.rs",
    "content": "//! Utilities for validating \"framing headers\" (Content-Length and Transfer-Encoding) for HTTP messages.\n\nuse http::{HeaderMap, header};\n\npub fn content_length_is_valid(headers: &HeaderMap) -> bool {\n    let mut values = headers.get_all(header::CONTENT_LENGTH).iter();\n\n    match values.next() {\n        None => true,\n        Some(val) => val.as_bytes().iter().all(|b| b.is_ascii_digit()) && values.next().is_none(),\n    }\n}\n\npub fn transfer_encoding_is_supported(headers: &HeaderMap) -> bool {\n    let mut values = headers.get_all(header::TRANSFER_ENCODING).iter();\n\n    match values.next() {\n        None => true,\n        Some(val) => {\n            val.to_str()\n                .map(|s| s.eq_ignore_ascii_case(\"chunked\"))\n                .unwrap_or(false)\n                && values.next().is_none()\n        }\n    }\n}\n"
  },
  {
    "path": "src/handoff.rs",
    "content": "use {\n    crate::{config::ClientCertInfo, upstream::TlsConfig},\n    http::{\n        HeaderMap, HeaderName, HeaderValue, Request, Response, StatusCode, Uri, header,\n        request::Parts,\n    },\n    hyper::{\n        Body,\n        client::conn::{Builder, Parts as ConnParts},\n        http::Result as HttpResult,\n        upgrade::OnUpgrade,\n    },\n    tokio::{io::copy_bidirectional, net::TcpStream, task::JoinHandle},\n    tracing::{debug, error, info, warn},\n};\n\n/// The list of request header names that cannot be modified during handoff.\nconst PROTECTED_REQ_HEADERS: &[&str] = &[\n    // WebSocket control\n    \"host\",\n    \"connection\",\n    \"sec-websocket-version\",\n    \"sec-websocket-key\",\n    \"upgrade\",\n    // Internal Fastly\n    \"pushpin-route\",\n    // VCL\n    \"content-length\",\n    \"content-range\",\n    \"expect\",\n    \"fastly-ff\",\n    \"proxy-authenticate\",\n    \"proxy-authorization\",\n    \"te\",\n    \"trailer\",\n    \"transfer-encoding\",\n    // Other\n    \"cdn-loop\",\n];\n\n/// A signal to hand off the request to Pushpin or a backend\n#[derive(Debug)]\npub struct HandoffInfo {\n    pub backend_name: String,\n    pub request_info: Option<HandoffRequestInfo>,\n}\n\n/// Information about the request being handed off\n#[derive(Debug)]\npub struct HandoffRequestInfo {\n    pub method: String,\n    pub scheme: Option<String>,\n    pub authority: Option<String>,\n    pub path_and_query: Option<String>,\n    pub headers: HeaderMap,\n}\n\nimpl HandoffRequestInfo {\n    pub fn from_parts(parts: &Parts) -> Self {\n        HandoffRequestInfo {\n            method: parts.method.to_string(),\n            scheme: parts.uri.scheme().map(|x| x.to_string()),\n            authority: parts.uri.authority().map(|x| x.to_string()),\n            path_and_query: parts.uri.path_and_query().map(|p| p.to_string()),\n            headers: parts.headers.clone(),\n        }\n    }\n}\n\npub struct HandoffConfig {\n    pub target_addr: String,\n    pub host_header: String,\n    pub display_name: String,\n    pub path_prefix: Option<String>,\n    pub extra_headers: Vec<(String, String)>,\n    pub tls_config: Option<HandoffTlsConfig>,\n}\n\npub struct HandoffTlsConfig {\n    pub ca_certs: Vec<rustls::Certificate>,\n    pub client_cert: Option<ClientCertInfo>, // Viceroy's existing cert wrapper\n    pub use_sni: bool,\n    pub cert_host: Option<String>,\n    pub dns_name_fallback: String,\n    pub is_grpc: bool,\n    pub base_tls_config: TlsConfig,\n}\n\npub enum Connection {\n    Http(TcpStream),\n    Https(Box<tokio_rustls::client::TlsStream<TcpStream>>),\n}\n\nimpl tokio::io::AsyncRead for Connection {\n    fn poll_read(\n        self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context,\n        buf: &mut tokio::io::ReadBuf<'_>,\n    ) -> std::task::Poll<std::io::Result<()>> {\n        match std::pin::Pin::get_mut(self) {\n            Connection::Http(s) => std::pin::Pin::new(s).poll_read(cx, buf),\n            Connection::Https(s) => std::pin::Pin::new(s).poll_read(cx, buf),\n        }\n    }\n}\n\nimpl tokio::io::AsyncWrite for Connection {\n    fn poll_write(\n        self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context<'_>,\n        buf: &[u8],\n    ) -> std::task::Poll<std::io::Result<usize>> {\n        match std::pin::Pin::get_mut(self) {\n            Connection::Http(s) => std::pin::Pin::new(s).poll_write(cx, buf),\n            Connection::Https(s) => std::pin::Pin::new(s).poll_write(cx, buf),\n        }\n    }\n    fn poll_flush(\n        self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<std::io::Result<()>> {\n        match std::pin::Pin::get_mut(self) {\n            Connection::Http(s) => std::pin::Pin::new(s).poll_flush(cx),\n            Connection::Https(s) => std::pin::Pin::new(s).poll_flush(cx),\n        }\n    }\n    fn poll_shutdown(\n        self: std::pin::Pin<&mut Self>,\n        cx: &mut std::task::Context<'_>,\n    ) -> std::task::Poll<std::io::Result<()>> {\n        match std::pin::Pin::get_mut(self) {\n            Connection::Http(s) => std::pin::Pin::new(s).poll_shutdown(cx),\n            Connection::Https(s) => std::pin::Pin::new(s).poll_shutdown(cx),\n        }\n    }\n}\n\n/// Hands off the current request to the target address.\n///\n/// - The method and request URI (path + query) are taken from request_info\n///   if provided, or orig_request_info otherwise.\n/// - The body is taken from orig_body.\n/// - Headers are taken from request_info if provided (except for certain protected\n///   headers, which will keep their values from orig_request_info), or\n///   orig_request_info otherwise.\n/// - The Host header is always replaced by the parameter `host_header`.\n/// - `extra_headers`, if provided, are applied. This always replaces existing\n///   headers of the same name (including protected headers).\n/// - The `path_prefix`, if provided, is prepended to the request path.\n///\n/// The request is forwarded to `target_addr` and the resulting connection\n/// is held open until disconnected by either end.\n/// If the handoff target responds with `101 Switching Protocols`, then the\n/// `on_upgrade` future is used to take over the incoming request connection and\n/// wire it up with the handoff connection.\npub async fn perform_handoff(\n    request_info: Option<HandoffRequestInfo>,\n    orig_request_info: HandoffRequestInfo,\n    orig_body: Body,\n    on_upgrade: OnUpgrade,\n    perform_handoff_config: HandoffConfig,\n) -> Response<Body> {\n    let mut proxy_req = match create_request_for_handoff(\n        &perform_handoff_config.host_header,\n        request_info,\n        orig_request_info,\n        orig_body,\n    ) {\n        Ok(req) => req,\n        Err(e) => {\n            error!(\n                \"Failed to build {} request: {}\",\n                perform_handoff_config.display_name, e\n            );\n            return build_error_response(\n                StatusCode::INTERNAL_SERVER_ERROR,\n                format!(\n                    \"Failed to build {} request: {}\",\n                    perform_handoff_config.display_name, e\n                ),\n            );\n        }\n    };\n\n    // Prepend path prefix if the backend has one (e.g., http://localhost:3000/api/v1)\n    if let Some(prefix) = perform_handoff_config.path_prefix {\n        let mut parts = proxy_req.uri().clone().into_parts();\n        let original_path = parts\n            .path_and_query\n            .as_ref()\n            .map(|pq| pq.as_str())\n            .unwrap_or(\"\");\n\n        // Ensure we don't end up with // if the prefix and path both have slashes\n        let prefix = prefix.trim_end_matches('/');\n        let original_path = if !original_path.starts_with('/') && !original_path.is_empty() {\n            format!(\"/{original_path}\")\n        } else {\n            original_path.to_string()\n        };\n\n        let new_path = format!(\"{prefix}{original_path}\");\n        if let Ok(pq) = new_path.parse() {\n            parts.path_and_query = Some(pq);\n            if let Ok(uri) = Uri::from_parts(parts) {\n                *proxy_req.uri_mut() = uri;\n            }\n        }\n    }\n\n    // Insert additional headers\n    for (name, value) in perform_handoff_config.extra_headers {\n        if let (Ok(header_name), Ok(header_val)) = (HeaderName::try_from(name), value.parse()) {\n            proxy_req.headers_mut().insert(header_name, header_val);\n        }\n    }\n\n    // Initiate the connection, and manage/stream/upgrade it\n    execute_handoff(\n        perform_handoff_config.target_addr,\n        perform_handoff_config.display_name,\n        proxy_req,\n        on_upgrade,\n        perform_handoff_config.tls_config,\n    )\n    .await\n}\n\n/// Creates a request suitable for use with execute_handoff().\nfn create_request_for_handoff(\n    backend_host: &str,\n    handoff_request_info: Option<HandoffRequestInfo>,\n    original_request_info: HandoffRequestInfo,\n    body: Body,\n) -> HttpResult<Request<Body>> {\n    let (path_and_query, method) = if let Some(ref handoff_request_info) = handoff_request_info {\n        (\n            handoff_request_info.path_and_query.as_deref().unwrap_or(\"\"),\n            handoff_request_info.method.as_str(),\n        )\n    } else {\n        (\n            original_request_info\n                .path_and_query\n                .as_deref()\n                .unwrap_or(\"\"),\n            original_request_info.method.as_str(),\n        )\n    };\n    let mut req = Request::builder().method(method).uri(path_and_query);\n\n    if let Some(handoff_request_info) = handoff_request_info {\n        // move the original headers defined in `PROTECTED_REQ_HEADERS` to the top of the req.headers\n        for (name, value) in &original_request_info.headers {\n            if PROTECTED_REQ_HEADERS\n                .iter()\n                .any(|h| h.eq_ignore_ascii_case(name.as_str()))\n            {\n                req = req.header(name, value);\n            }\n        }\n        // add the req headers received via the handoff call, except for the ones in `PROTECTED_REQ_HEADERS`\n        for (name, value) in &handoff_request_info.headers {\n            if !PROTECTED_REQ_HEADERS\n                .iter()\n                .any(|h| h.eq_ignore_ascii_case(name.as_str()))\n            {\n                req = req.header(name, value);\n            }\n        }\n    } else {\n        for (name, value) in &original_request_info.headers {\n            req = req.header(name, value);\n        }\n    }\n\n    let mut req = req.body(body)?;\n    req.headers_mut().insert(\n        header::HOST,\n        HeaderValue::from_str(backend_host).expect(\"`backend_host` should be a valid header value\"),\n    );\n    Ok(req)\n}\n\n/// Executes a handoff by forwarding the request and managing connection upgrades.\n/// If the handoff target responds with `101 Switching Protocols`, then use the\n/// provided `OnUpgrade` future to take over the incoming request connection and\n/// wire it up with the handoff connection.\nasync fn execute_handoff(\n    target_addr: String,\n    target_name: String,\n    req: Request<Body>,\n    downstream_on_upgrade: OnUpgrade,\n    tls_config: Option<HandoffTlsConfig>,\n) -> Response<Body> {\n    debug!(\"Proxying through handoff target '{target_name}'.\");\n\n    let handoff_stream = match TcpStream::connect(&target_addr).await {\n        Ok(str) => str,\n        Err(e) => {\n            error!(\"Could not connect to handoff target: {e}.\");\n            return build_error_response(\n                StatusCode::INTERNAL_SERVER_ERROR,\n                format!(\"Could not connect to handoff target: {e}.\"),\n            );\n        }\n    };\n\n    let handoff_connection = if let Some(config) = tls_config {\n        debug!(\"TLS Handoff triggered for {}\", target_name);\n\n        // Finalize Root Certificates\n        let mut custom_roots = rustls::RootCertStore::empty();\n        let (added, _) = custom_roots.add_parsable_certificates(&config.ca_certs);\n        debug!(\"Using {added} certificates from provided CA certificate.\");\n\n        let builder = if config.ca_certs.is_empty() {\n            config\n                .base_tls_config\n                .partial_config\n                .with_root_certificates(config.base_tls_config.default_roots)\n        } else {\n            config\n                .base_tls_config\n                .partial_config\n                .with_root_certificates(custom_roots)\n        };\n\n        // Finalize Client Authentication\n        let mut client_config = if let Some(client_cert_info) = &config.client_cert {\n            builder\n                .with_client_auth_cert(client_cert_info.certs(), client_cert_info.key())\n                .expect(\"`backend.client_cert` should have valid private key\")\n        } else {\n            builder.with_no_client_auth()\n        };\n\n        client_config.enable_sni = config.use_sni;\n        if config.is_grpc {\n            client_config.alpn_protocols = vec![b\"h2\".to_vec()];\n        }\n\n        // Resolve SNI Host\n        let cert_host = config\n            .cert_host\n            .as_deref()\n            .unwrap_or(&config.dns_name_fallback);\n        let dnsname = rustls::client::ServerName::try_from(cert_host)\n            .expect(\"`backend.cert_host` should be a valid DNS name\");\n\n        let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(client_config));\n        let tls = connector\n            .connect(dnsname, handoff_stream)\n            .await\n            .expect(\"Should be able to initiate TLS stream\");\n\n        Connection::Https(Box::new(tls))\n    } else {\n        Connection::Http(handoff_stream)\n    };\n\n    let (mut sender, conn) = match Builder::new().handshake(handoff_connection).await {\n        Ok(res) => res,\n        Err(e) => {\n            error!(\"Handoff handshake failed: {e}\");\n            return build_error_response(\n                StatusCode::INTERNAL_SERVER_ERROR,\n                format!(\"Could not connect to upstream service: {e}\"),\n            );\n        }\n    };\n\n    // Spawn the connection driver and keep a handle to it.\n    // We need this future to complete so we can get the raw IO stream back after the HTTP response is parsed.\n    // `without_shutdown` prevents hyper from trying to gracefully close the connection,\n    // which is what we want when taking it over for a WebSocket.\n    let conn_fut = tokio::spawn(conn.without_shutdown());\n\n    let upstream_resp = match sender.send_request(req).await {\n        Ok(proxy_resp) => {\n            info!(\n                \"Handoff target '{}' responded with status: {}. Proxying response.\",\n                target_name,\n                proxy_resp.status()\n            );\n            proxy_resp\n        }\n        Err(e) => {\n            error!(\"Handoff request failed: {e}\");\n            return build_error_response(\n                StatusCode::BAD_GATEWAY,\n                format!(\"Handoff request failed: {e}\"),\n            );\n        }\n    };\n\n    // If handoff target responds with `101 Switching Protocols`, then we spawn an async task\n    // to attempt an upgrade\n    if upstream_resp.status() == StatusCode::SWITCHING_PROTOCOLS {\n        debug!(\"Handoff target requested 101 Switching Protocols; upgrading...\");\n        tokio::spawn(proxy_upgraded_connection(downstream_on_upgrade, conn_fut));\n    }\n\n    upstream_resp\n}\n\n/// A background task to proxy an upgraded (e.g., WebSocket) connection\nasync fn proxy_upgraded_connection(\n    downstream_req_on_upgrade: OnUpgrade,\n    upstream_conn_fut: JoinHandle<Result<ConnParts<Connection>, hyper::Error>>,\n) {\n    // Await the client-side upgrade. This future will not resolve until\n    // the `101` response is sent to the client by the main service.\n    let mut downstream_upgraded = match downstream_req_on_upgrade.await {\n        Ok(upgraded) => upgraded,\n        Err(e) => {\n            error!(\"Downstream client upgrade failed: {e}\");\n            return;\n        }\n    };\n\n    debug!(\"Downstream client connection upgraded.\");\n\n    // Await the server-side connection driver to get the raw IO back.\n    let mut upstream_parts = match upstream_conn_fut.await {\n        Ok(Ok(parts)) => parts,\n        Ok(Err(e)) => {\n            error!(\"Upstream connection error: {e}\");\n            return;\n        }\n        Err(e) => {\n            warn!(\"Upstream connection task failed: {e}\");\n            return;\n        }\n    };\n\n    debug!(\"Upstream connection IO stream obtained.\");\n\n    match copy_bidirectional(&mut downstream_upgraded, &mut upstream_parts.io).await {\n        Ok((from_client, from_server)) => {\n            info!(\n                \"Upgraded proxy connection finished gracefully. Bytes transferred: client->server: {}, server->client: {}\",\n                from_client, from_server\n            );\n        }\n        Err(e) => {\n            error!(\"Upgraded proxy I/O error: {e}\");\n        }\n    }\n}\n\n/// A helper function to build a simple error response.\nfn build_error_response(status: StatusCode, message: impl ToString) -> Response<Body> {\n    let mut resp = Response::new(Body::from(format!(\"Error: {}\", message.to_string())));\n    *resp.status_mut() = status;\n    resp\n}\n"
  },
  {
    "path": "src/headers.rs",
    "content": "use hyper::{HeaderMap, header};\n\npub fn filter_outgoing_headers(headers: &mut HeaderMap) {\n    // Remove framing-related headers; we rely on Hyper to insert the appropriate\n    // framing headers automatically, and do not allow guests to include them.\n    headers.remove(header::CONTENT_LENGTH);\n    headers.remove(header::TRANSFER_ENCODING);\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! Viceroy implementation details.\n\n// When building the project in release mode:\n//   (1): Promote warnings into errors.\n//   (2): Deny broken documentation links.\n//   (3): Deny invalid codeblock attributes in documentation.\n//   (4): Promote warnings in examples into errors, except for unused variables.\n#![cfg_attr(not(debug_assertions), deny(warnings))]\n#![cfg_attr(not(debug_assertions), deny(clippy::all))]\n#![cfg_attr(not(debug_assertions), deny(broken_intra_doc_links))]\n#![cfg_attr(not(debug_assertions), deny(invalid_codeblock_attributes))]\n#![cfg_attr(not(debug_assertions), doc(test(attr(deny(warnings)))))]\n#![cfg_attr(not(debug_assertions), doc(test(attr(allow(dead_code)))))]\n#![cfg_attr(not(debug_assertions), doc(test(attr(allow(unused_variables)))))]\n\npub mod adapt;\npub mod body;\npub mod cache;\npub mod config;\npub mod error;\npub mod logging;\npub mod sandbox;\n\nmod acl;\nmod async_io;\nmod body_tee;\nmod collecting_body;\npub mod component;\nmod downstream;\nmod execute;\nmod framing;\nmod handoff;\nmod headers;\nmod linking;\nmod object_store;\nmod secret_store;\nmod service;\nmod shielding_site;\nmod shift_mem;\nmod streaming_body;\nmod upstream;\npub mod wiggle_abi;\n\npub use {\n    error::Error, execute::ExecuteCtx, execute::GuestProfileConfig, execute::WasmFeatures,\n    service::ViceroyService, upstream::BackendConnector, wasmtime::ProfilingStrategy,\n};\n"
  },
  {
    "path": "src/linking.rs",
    "content": "//! Linking and name resolution.\n\nuse {\n    crate::{\n        Error, body::Body, config::ExperimentalModule, execute::ExecuteCtx, logging::LogEndpoint,\n        sandbox::Sandbox, wiggle_abi,\n    },\n    hyper::Response,\n    std::collections::HashSet,\n    wasmtime::{GuestProfiler, Linker, Store, StoreLimits, StoreLimitsBuilder, UpdateDeadline},\n    wasmtime_wasi::p1::WasiP1Ctx,\n    wasmtime_wasi_nn::witx::WasiNnCtx,\n};\n\npub struct Limiter {\n    /// Total memory allocated so far.\n    pub memory_allocated: usize,\n    /// The internal limiter we use to actually answer calls\n    internal: StoreLimits,\n}\n\nimpl Limiter {\n    pub fn for_wasip2() -> Self {\n        Self::new(100, 100, 100)\n    }\n\n    pub fn for_wasip1() -> Self {\n        Self::new(1, 1, 1)\n    }\n\n    fn new(max_instances: usize, max_memories: usize, max_tables: usize) -> Self {\n        Limiter {\n            memory_allocated: 0,\n            internal: StoreLimitsBuilder::new()\n                .instances(max_instances)\n                .memories(max_memories)\n                .memory_size(128 * 1024 * 1024)\n                .table_elements(98765)\n                .tables(max_tables)\n                .build(),\n        }\n    }\n}\n\nimpl wasmtime::ResourceLimiter for Limiter {\n    fn memory_growing(\n        &mut self,\n        current: usize,\n        desired: usize,\n        maximum: Option<usize>,\n    ) -> anyhow::Result<bool> {\n        // limit the amount of memory that an instance can use to (roughly) 128MB, erring on\n        // the side of letting things run that might get killed on Compute, because we are not\n        // tracking some runtime factors in this count.\n        let result = self.internal.memory_growing(current, desired, maximum);\n\n        if matches!(result, Ok(true)) {\n            // Track the diff in memory allocated over time. As each instance will start with 0 and\n            // gradually resize, this will track the total allocations throughout the lifetime of the\n            // instance.\n            self.memory_allocated += desired - current;\n        }\n\n        result\n    }\n\n    fn table_growing(\n        &mut self,\n        current: usize,\n        desired: usize,\n        maximum: Option<usize>,\n    ) -> anyhow::Result<bool> {\n        self.internal.table_growing(current, desired, maximum)\n    }\n\n    fn memory_grow_failed(&mut self, error: anyhow::Error) -> anyhow::Result<()> {\n        self.internal.memory_grow_failed(error)\n    }\n\n    fn table_grow_failed(&mut self, error: anyhow::Error) -> anyhow::Result<()> {\n        self.internal.table_grow_failed(error)\n    }\n\n    fn instances(&self) -> usize {\n        self.internal.instances()\n    }\n\n    fn tables(&self) -> usize {\n        self.internal.tables()\n    }\n\n    fn memories(&self) -> usize {\n        self.internal.memories()\n    }\n}\n\n#[allow(unused)]\npub struct ComponentCtx {\n    pub wasi_ctx: wasmtime_wasi::WasiCtx,\n    pub wasi_table: wasmtime_wasi::ResourceTable,\n    pub wasi_random: wasmtime_wasi::random::WasiRandomCtx,\n    pub(crate) sandbox: Sandbox,\n    guest_profiler: Option<Box<GuestProfiler>>,\n}\n\n/// An extension trait for users of `ComponentCtx` to access the sandbox.\npub trait SandboxView {\n    fn sandbox(&self) -> &Sandbox;\n    fn sandbox_mut(&mut self) -> &mut Sandbox;\n}\n\nimpl SandboxView for ComponentCtx {\n    fn sandbox(&self) -> &Sandbox {\n        &self.sandbox\n    }\n\n    fn sandbox_mut(&mut self) -> &mut Sandbox {\n        &mut self.sandbox\n    }\n}\n\nimpl ComponentCtx {\n    pub fn wasi(&mut self) -> &mut wasmtime_wasi::WasiCtx {\n        &mut self.wasi_ctx\n    }\n\n    pub fn sandbox(&mut self) -> &mut Sandbox {\n        &mut self.sandbox\n    }\n\n    pub fn take_guest_profiler(&mut self) -> Option<Box<GuestProfiler>> {\n        self.guest_profiler.take()\n    }\n\n    pub fn limiter(&self) -> &Limiter {\n        self.sandbox.limiter()\n    }\n\n    pub fn close_downstream_response_sender(&mut self, resp: Response<Body>) {\n        self.sandbox.close_downstream_response_sender(resp)\n    }\n\n    /// Initialize a new [`Store`][store], given an [`ExecuteCtx`][ctx].\n    ///\n    /// [ctx]: ../wiggle_abi/struct.ExecuteCtx.html\n    /// [store]: https://docs.rs/wasmtime/latest/wasmtime/struct.Store.html\n    pub(crate) fn create_store(\n        ctx: &ExecuteCtx,\n        sandbox: Sandbox,\n        guest_profiler: Option<GuestProfiler>,\n        extra_init: impl FnOnce(&mut wasmtime_wasi::WasiCtxBuilder),\n    ) -> Result<Store<ComponentCtx>, anyhow::Error> {\n        let mut builder = make_wasi_ctx(ctx, &sandbox);\n\n        extra_init(&mut builder);\n\n        let wasm_ctx = Self {\n            wasi_table: wasmtime_wasi::ResourceTable::new(),\n            wasi_ctx: builder.build(),\n            wasi_random: wasmtime_wasi::random::WasiRandomCtx::default(),\n            sandbox,\n            guest_profiler: guest_profiler.map(Box::new),\n        };\n        let mut store = Store::new(ctx.engine(), wasm_ctx);\n        store.set_epoch_deadline(1);\n\n        // instrument hostcalls to have those show up in profiles\n        store.call_hook(|mut store, kind| {\n            if let Some(mut prof) = store.data_mut().guest_profiler.take() {\n                prof.call_hook(&store, kind);\n                store.data_mut().guest_profiler = Some(prof);\n            }\n            Ok(())\n        });\n\n        // sampling profiler\n        store.epoch_deadline_callback(|mut store| {\n            if let Some(mut prof) = store.data_mut().guest_profiler.take() {\n                prof.sample(&store, std::time::Duration::ZERO);\n                store.data_mut().guest_profiler = Some(prof);\n            }\n            Ok(UpdateDeadline::Yield(1))\n        });\n\n        store.limiter(|ctx| ctx.sandbox.limiter_mut());\n        Ok(store)\n    }\n}\n\nimpl wasmtime_wasi::WasiView for ComponentCtx {\n    fn ctx(&mut self) -> wasmtime_wasi::WasiCtxView<'_> {\n        wasmtime_wasi::WasiCtxView {\n            ctx: &mut self.wasi_ctx,\n            table: &mut self.wasi_table,\n        }\n    }\n}\n\nimpl wasmtime_wasi_io::IoView for ComponentCtx {\n    fn table(&mut self) -> &mut wasmtime_wasi::ResourceTable {\n        &mut self.wasi_table\n    }\n}\n\npub struct WasmCtx {\n    wasi: WasiP1Ctx,\n    wasi_nn: WasiNnCtx,\n    sandbox: Sandbox,\n    guest_profiler: Option<Box<GuestProfiler>>,\n}\n\nimpl WasmCtx {\n    pub fn wasi(&mut self) -> &mut WasiP1Ctx {\n        &mut self.wasi\n    }\n\n    fn wasi_nn(&mut self) -> &mut WasiNnCtx {\n        &mut self.wasi_nn\n    }\n\n    pub fn sandbox(&mut self) -> &mut Sandbox {\n        &mut self.sandbox\n    }\n\n    pub fn take_guest_profiler(&mut self) -> Option<Box<GuestProfiler>> {\n        self.guest_profiler.take()\n    }\n\n    pub fn limiter(&self) -> &Limiter {\n        self.sandbox.limiter()\n    }\n}\n\nimpl WasmCtx {\n    pub fn close_downstream_response_sender(&mut self, resp: Response<Body>) {\n        self.sandbox.close_downstream_response_sender(resp)\n    }\n}\n\n/// Initialize a new [`Store`][store], given an [`ExecuteCtx`][ctx].\n///\n/// [ctx]: ../wiggle_abi/struct.ExecuteCtx.html\n/// [store]: https://docs.rs/wasmtime/latest/wasmtime/struct.Store.html\npub(crate) fn create_store(\n    ctx: &ExecuteCtx,\n    sandbox: Sandbox,\n    guest_profiler: Option<GuestProfiler>,\n    extra_init: impl FnOnce(&mut wasmtime_wasi::WasiCtxBuilder),\n) -> Result<Store<WasmCtx>, anyhow::Error> {\n    let mut builder = make_wasi_ctx(ctx, &sandbox);\n\n    extra_init(&mut builder);\n\n    let wasi = builder.build_p1();\n    let (backends, registry) = wasmtime_wasi_nn::preload(&[])?;\n    let wasi_nn = WasiNnCtx::new(backends, registry);\n    let wasm_ctx = WasmCtx {\n        wasi,\n        wasi_nn,\n        sandbox,\n        guest_profiler: guest_profiler.map(Box::new),\n    };\n    let mut store = Store::new(ctx.engine(), wasm_ctx);\n    store.set_epoch_deadline(1);\n\n    // instrument hostcalls to have those show up in profiles\n    store.call_hook(|mut store, kind| {\n        if let Some(mut prof) = store.data_mut().guest_profiler.take() {\n            prof.call_hook(&store, kind);\n            store.data_mut().guest_profiler = Some(prof);\n        }\n        Ok(())\n    });\n\n    // sampling profiler\n    store.epoch_deadline_callback(|mut store| {\n        if let Some(mut prof) = store.data_mut().guest_profiler.take() {\n            prof.sample(&store, std::time::Duration::ZERO);\n            store.data_mut().guest_profiler = Some(prof);\n        }\n        Ok(UpdateDeadline::Yield(1))\n    });\n\n    store.limiter(|ctx| ctx.sandbox.limiter_mut());\n    Ok(store)\n}\n\n/// Constructs a `WasiCtxBuilder` for _each_ incoming request.\nfn make_wasi_ctx(ctx: &ExecuteCtx, sandbox: &Sandbox) -> wasmtime_wasi::WasiCtxBuilder {\n    let mut wasi_ctx = wasmtime_wasi::WasiCtxBuilder::new();\n\n    // Viceroy provides the same `FASTLY_*` environment variables that the production\n    // Compute platform provides:\n\n    wasi_ctx\n        // These variables are stubbed out for compatibility\n        .env(\"FASTLY_CACHE_GENERATION\", \"0\")\n        .env(\"FASTLY_CUSTOMER_ID\", \"0000000000000000000000\")\n        .env(\"FASTLY_POP\", \"XXX\")\n        .env(\"FASTLY_REGION\", \"Somewhere\")\n        .env(\"FASTLY_SERVICE_ID\", \"0000000000000000000000\")\n        .env(\"FASTLY_SERVICE_VERSION\", \"0\")\n        // signal that we're in a local testing environment\n        .env(\"FASTLY_HOSTNAME\", \"localhost\")\n        // ...which is not the staging environment\n        .env(\"FASTLY_IS_STAGING\", \"0\")\n        // request IDs start at 0 and increment, rather than being UUIDs, for ease of testing\n        .env(\"FASTLY_TRACE_ID\", format!(\"{:032x}\", sandbox.sandbox_id()));\n\n    if ctx.log_stdout() {\n        wasi_ctx.stdout(LogEndpoint::new(b\"stdout\", ctx.capture_logs()));\n    } else {\n        wasi_ctx.inherit_stdout();\n    }\n\n    if ctx.log_stderr() {\n        wasi_ctx.stderr(LogEndpoint::new(b\"stderr\", ctx.capture_logs()));\n    } else {\n        wasi_ctx.inherit_stderr();\n    }\n\n    wasi_ctx\n}\n\npub fn link_host_functions(\n    linker: &mut Linker<WasmCtx>,\n    experimental_modules: &HashSet<ExperimentalModule>,\n) -> Result<(), Error> {\n    experimental_modules\n        .iter()\n        .try_for_each(|experimental_module| match experimental_module {\n            ExperimentalModule::WasiNn => {\n                wasmtime_wasi_nn::witx::add_to_linker(linker, WasmCtx::wasi_nn)\n            }\n        })?;\n\n    wasmtime_wasi::p1::add_to_linker_async(linker, WasmCtx::wasi)?;\n    wiggle_abi::fastly_abi::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_acl::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_async_io::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_backend::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_cache::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_compute_runtime::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_config_store::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_device_detection::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_dictionary::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_erl::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_geo::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_http_body::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_http_cache::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_http_downstream::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_http_req::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_http_resp::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_image_optimizer::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_kv_store::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_log::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_object_store::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_purge::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_secret_store::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_shielding::add_to_linker(linker, WasmCtx::sandbox)?;\n    wiggle_abi::fastly_uap::add_to_linker(linker, WasmCtx::sandbox)?;\n    link_legacy_aliases(linker)?;\n    Ok(())\n}\n\nfn link_legacy_aliases(linker: &mut Linker<WasmCtx>) -> Result<(), Error> {\n    linker.alias(\"fastly_abi\", \"init\", \"env\", \"xqd_init\")?;\n\n    let body = \"fastly_http_body\";\n    linker.alias(body, \"append\", \"env\", \"xqd_body_append\")?;\n    linker.alias(body, \"new\", \"env\", \"xqd_body_new\")?;\n    linker.alias(body, \"read\", \"env\", \"xqd_body_read\")?;\n    linker.alias(body, \"write\", \"env\", \"xqd_body_write\")?;\n    linker.alias(body, \"close\", \"env\", \"xqd_body_close\")?;\n    // `xqd_body_close_downstream` is deprecated since `fastly-sys:0.3.4`, and renamed to\n    // `xqd_body_close`. We include it here under both names for compatibility's sake.\n    linker.alias(body, \"close\", \"env\", \"xqd_body_close_downstream\")?;\n\n    linker.alias(\"fastly_log\", \"endpoint_get\", \"env\", \"xqd_log_endpoint_get\")?;\n    linker.alias(\"fastly_log\", \"write\", \"env\", \"xqd_log_write\")?;\n\n    let req = \"fastly_http_req\";\n    linker.alias(\n        req,\n        \"body_downstream_get\",\n        \"env\",\n        \"xqd_req_body_downstream_get\",\n    )?;\n    linker.alias(\n        req,\n        \"cache_override_set\",\n        \"env\",\n        \"xqd_req_cache_override_set\",\n    )?;\n    linker.alias(\n        req,\n        \"downstream_client_ip_addr\",\n        \"env\",\n        \"xqd_req_downstream_client_ip_addr\",\n    )?;\n    linker.alias(\n        req,\n        \"downstream_client_request_id\",\n        \"env\",\n        \"xqd_req_downstream_client_request_id\",\n    )?;\n    linker.alias(\n        req,\n        \"downstream_tls_cipher_openssl_name\",\n        \"env\",\n        \"xqd_req_downstream_tls_cipher_openssl_name\",\n    )?;\n    linker.alias(\n        req,\n        \"downstream_tls_protocol\",\n        \"env\",\n        \"xqd_req_downstream_tls_protocol\",\n    )?;\n    linker.alias(\n        req,\n        \"downstream_tls_client_hello\",\n        \"env\",\n        \"xqd_req_downstream_tls_client_hello\",\n    )?;\n    linker.alias(req, \"new\", \"env\", \"xqd_req_new\")?;\n\n    linker.alias(req, \"header_names_get\", \"env\", \"xqd_req_header_names_get\")?;\n    linker.alias(\n        req,\n        \"original_header_names_get\",\n        \"env\",\n        \"xqd_req_original_header_names_get\",\n    )?;\n    linker.alias(\n        req,\n        \"original_header_count\",\n        \"env\",\n        \"xqd_req_original_header_count\",\n    )?;\n    linker.alias(req, \"header_value_get\", \"env\", \"xqd_req_header_value_get\")?;\n    linker.alias(req, \"header_values_get\", \"env\", \"xqd_req_header_values_get\")?;\n    linker.alias(req, \"header_values_set\", \"env\", \"xqd_req_header_values_set\")?;\n    linker.alias(req, \"header_insert\", \"env\", \"xqd_req_header_insert\")?;\n    linker.alias(req, \"header_append\", \"env\", \"xqd_req_header_append\")?;\n    linker.alias(req, \"header_remove\", \"env\", \"xqd_req_header_remove\")?;\n    linker.alias(req, \"method_get\", \"env\", \"xqd_req_method_get\")?;\n    linker.alias(req, \"method_set\", \"env\", \"xqd_req_method_set\")?;\n    linker.alias(req, \"uri_get\", \"env\", \"xqd_req_uri_get\")?;\n    linker.alias(req, \"uri_set\", \"env\", \"xqd_req_uri_set\")?;\n    linker.alias(req, \"version_get\", \"env\", \"xqd_req_version_get\")?;\n    linker.alias(req, \"version_set\", \"env\", \"xqd_req_version_set\")?;\n    linker.alias(req, \"send\", \"env\", \"xqd_req_send\")?;\n    linker.alias(req, \"send_async\", \"env\", \"xqd_req_send_async\")?;\n    linker.alias(\n        req,\n        \"send_async_streaming\",\n        \"env\",\n        \"xqd_req_send_async_streaming\",\n    )?;\n    linker.alias(req, \"pending_req_poll\", \"env\", \"xqd_pending_req_poll\")?;\n    linker.alias(req, \"pending_req_wait\", \"env\", \"xqd_pending_req_wait\")?;\n    linker.alias(req, \"pending_req_select\", \"env\", \"xqd_pending_req_select\")?;\n\n    let resp = \"fastly_http_resp\";\n    linker.alias(resp, \"new\", \"env\", \"xqd_resp_new\")?;\n\n    linker.alias(resp, \"header_names_get\", \"env\", \"xqd_resp_header_names_get\")?;\n    linker.alias(resp, \"header_value_get\", \"env\", \"xqd_resp_header_value_get\")?;\n    linker.alias(\n        resp,\n        \"header_values_get\",\n        \"env\",\n        \"xqd_resp_header_values_get\",\n    )?;\n    linker.alias(\n        resp,\n        \"header_values_set\",\n        \"env\",\n        \"xqd_resp_header_values_set\",\n    )?;\n    linker.alias(resp, \"header_insert\", \"env\", \"xqd_resp_header_insert\")?;\n    linker.alias(resp, \"header_append\", \"env\", \"xqd_resp_header_append\")?;\n    linker.alias(resp, \"header_remove\", \"env\", \"xqd_resp_header_remove\")?;\n    linker.alias(resp, \"version_get\", \"env\", \"xqd_resp_version_get\")?;\n    linker.alias(resp, \"version_set\", \"env\", \"xqd_resp_version_set\")?;\n    linker.alias(resp, \"status_get\", \"env\", \"xqd_resp_status_get\")?;\n    linker.alias(resp, \"status_set\", \"env\", \"xqd_resp_status_set\")?;\n    Ok(())\n}\n"
  },
  {
    "path": "src/logging.rs",
    "content": "use std::{\n    io::{self, Write},\n    pin::Pin,\n    sync::{Arc, Mutex},\n    task::{Context, Poll},\n};\nuse tokio::io::AsyncWrite;\n\n/// A named logging endpoint.\n#[derive(Clone)]\npub struct LogEndpoint {\n    name: Vec<u8>,\n    writer: Arc<Mutex<dyn Write + Send>>,\n}\n\nimpl LogEndpoint {\n    /// Allocate a new `LogEndpoint` with the given name, with log messages sent\n    /// to the given writer.\n    pub fn new(name: &[u8], writer: Arc<Mutex<dyn Write + Send>>) -> LogEndpoint {\n        LogEndpoint {\n            name: name.to_owned(),\n            writer,\n        }\n    }\n\n    /// Write a log entry to this endpoint.\n    ///\n    /// Log entries are prefixed with the endpoint name and terminated with a newline.\n    /// Any newlines in the message will be escaped to the string r\"\\n\".\n    ///\n    /// The entry is written atomically to the writer given to [`LogEndpoint::new`].\n    pub fn write_entry(&self, mut msg: &[u8]) -> io::Result<()> {\n        const LOG_ENDPOINT_DELIM: &[u8] = b\" :: \";\n\n        // Strip any trailing newlines; we will add a newline at the end,\n        // and escape any interior newlines.\n        if msg.last() == Some(&b'\\n') {\n            msg = &msg[..msg.len() - 1];\n        }\n\n        if msg.is_empty() {\n            return Ok(());\n        }\n\n        // Accumulate log entry into a buffer before writing, while escaping newlines\n        let mut to_write =\n            Vec::with_capacity(msg.len() + self.name.len() + LOG_ENDPOINT_DELIM.len() + 1);\n\n        to_write.extend_from_slice(&self.name);\n        to_write.extend_from_slice(LOG_ENDPOINT_DELIM);\n        for &byte in msg {\n            if byte == b'\\n' {\n                to_write.extend_from_slice(br\"\\n\");\n            } else {\n                to_write.push(byte);\n            }\n        }\n        to_write.push(b'\\n');\n\n        self.writer.lock().unwrap().write_all(&to_write)\n    }\n}\n\nimpl Write for LogEndpoint {\n    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {\n        self.write_entry(buf)?;\n        Ok(buf.len())\n    }\n\n    fn flush(&mut self) -> io::Result<()> {\n        self.writer.lock().unwrap().flush()\n    }\n}\n\nimpl wasmtime_wasi::cli::StdoutStream for LogEndpoint {\n    fn p2_stream(&self) -> Box<dyn wasmtime_wasi::p2::OutputStream> {\n        Box::new(self.clone())\n    }\n\n    fn async_stream(&self) -> Box<dyn AsyncWrite + Send + Sync> {\n        Box::new(self.clone())\n    }\n}\n\n#[wasmtime_wasi::async_trait]\nimpl wasmtime_wasi::p2::Pollable for LogEndpoint {\n    async fn ready(&mut self) {}\n}\n\nimpl wasmtime_wasi::cli::IsTerminal for LogEndpoint {\n    fn is_terminal(&self) -> bool {\n        false\n    }\n}\n\nimpl wasmtime_wasi::p2::OutputStream for LogEndpoint {\n    fn write(&mut self, bytes: bytes::Bytes) -> wasmtime_wasi::p2::StreamResult<()> {\n        self.write_entry(&bytes)\n            .map_err(|e| wasmtime_wasi::p2::StreamError::LastOperationFailed(anyhow::anyhow!(e)))\n    }\n\n    fn flush(&mut self) -> wasmtime_wasi::p2::StreamResult<()> {\n        <Self as Write>::flush(self)\n            .map_err(|e| wasmtime_wasi::p2::StreamError::LastOperationFailed(anyhow::anyhow!(e)))\n    }\n\n    fn check_write(&mut self) -> wasmtime_wasi::p2::StreamResult<usize> {\n        Ok(1024 * 1024)\n    }\n}\n\nimpl AsyncWrite for LogEndpoint {\n    fn poll_write(\n        self: Pin<&mut Self>,\n        _cx: &mut Context<'_>,\n        buf: &[u8],\n    ) -> Poll<Result<usize, std::io::Error>> {\n        self.write_entry(buf)?;\n        Poll::Ready(Ok(buf.len()))\n    }\n\n    fn poll_flush(\n        mut self: Pin<&mut Self>,\n        _cx: &mut Context<'_>,\n    ) -> Poll<Result<(), std::io::Error>> {\n        Poll::Ready(<Self as Write>::flush(&mut self))\n    }\n\n    fn poll_shutdown(\n        self: Pin<&mut Self>,\n        _cx: &mut Context<'_>,\n    ) -> Poll<Result<(), std::io::Error>> {\n        Poll::Ready(Ok(()))\n    }\n}\n"
  },
  {
    "path": "src/object_store.rs",
    "content": "use {\n    crate::wiggle_abi::types::{FastlyStatus, KvError, KvInsertMode},\n    base64::prelude::*,\n    serde::Serialize,\n    std::{\n        collections::BTreeMap,\n        sync::{Arc, RwLock},\n        time::SystemTime,\n    },\n};\n\n#[derive(Debug, Clone)]\npub struct ObjectValue {\n    pub body: Vec<u8>,\n    pub metadata: String,\n    pub metadata_len: usize,\n    pub generation: u64,\n    pub expiration: Option<SystemTime>,\n}\n\n#[derive(Clone, Debug, Default)]\npub struct ObjectStores {\n    #[allow(clippy::type_complexity)]\n    stores: Arc<RwLock<BTreeMap<ObjectStoreKey, BTreeMap<ObjectKey, ObjectValue>>>>,\n}\n\nimpl ObjectStores {\n    pub fn new() -> Self {\n        Self {\n            stores: Arc::new(RwLock::new(BTreeMap::new())),\n        }\n    }\n\n    pub(crate) fn store_exists(&self, obj_store_key: &str) -> Result<bool, ObjectStoreError> {\n        Ok(self\n            .stores\n            .read()\n            .map_err(|_| ObjectStoreError::PoisonedLock)?\n            .get(&ObjectStoreKey::new(obj_store_key))\n            .is_some())\n    }\n\n    pub fn lookup(\n        &self,\n        obj_store_key: ObjectStoreKey,\n        obj_key: ObjectKey,\n    ) -> Result<Option<ObjectValue>, KvStoreError> {\n        let mut res = Ok(None);\n\n        self.stores\n            .write()\n            .map_err(|_| KvStoreError::InternalError)?\n            .entry(obj_store_key)\n            .and_modify(|store| match store.get(&obj_key) {\n                Some(val) => {\n                    res = Ok(Some(val.clone()));\n                    // manages ttl\n                    if let Some(exp) = val.expiration\n                        && SystemTime::now() >= exp\n                    {\n                        store.remove(&obj_key);\n                        res = Ok(None);\n                    }\n                }\n                None => {\n                    res = Ok(None);\n                }\n            });\n\n        res\n    }\n\n    pub(crate) fn insert_empty_store(\n        &self,\n        obj_store_key: ObjectStoreKey,\n    ) -> Result<(), ObjectStoreError> {\n        self.stores\n            .write()\n            .map_err(|_| ObjectStoreError::PoisonedLock)?\n            .entry(obj_store_key)\n            .and_modify(|_| {})\n            .or_insert_with(BTreeMap::new);\n\n        Ok(())\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn insert(\n        &self,\n        obj_store_key: ObjectStoreKey,\n        obj_key: ObjectKey,\n        obj: Vec<u8>,\n        mode: KvInsertMode,\n        generation: Option<u64>,\n        metadata: Option<String>,\n        ttl: Option<std::time::Duration>,\n    ) -> Result<(), KvStoreError> {\n        // manages ttl\n        let existing = self\n            .lookup(obj_store_key.clone(), obj_key.clone())\n            .map_err(|_| KvStoreError::InternalError)?;\n\n        if let Some(g) = generation\n            && let Some(val) = &existing\n            && val.generation != g\n        {\n            return Err(KvStoreError::PreconditionFailed);\n        }\n\n        let out_obj = match mode {\n            KvInsertMode::Overwrite => obj,\n            KvInsertMode::Add => {\n                if existing.is_some() {\n                    // key exists, add fails\n                    return Err(KvStoreError::PreconditionFailed);\n                }\n                obj\n            }\n            KvInsertMode::Append => {\n                let mut out_obj;\n                match existing {\n                    None => {\n                        out_obj = obj;\n                    }\n                    Some(v) => {\n                        out_obj = v.body;\n                        out_obj.append(&mut obj.clone());\n                    }\n                }\n                out_obj\n            }\n            KvInsertMode::Prepend => {\n                let mut out_obj;\n                match existing {\n                    None => {\n                        out_obj = obj;\n                    }\n                    Some(mut v) => {\n                        out_obj = obj;\n                        out_obj.append(&mut v.body);\n                    }\n                }\n                out_obj\n            }\n        };\n\n        let exp = ttl.map(|t| SystemTime::now() + t);\n\n        let mut obj_val = ObjectValue {\n            body: out_obj,\n            metadata: String::new(),\n            metadata_len: 0,\n            generation: SystemTime::now()\n                .duration_since(SystemTime::UNIX_EPOCH)\n                .unwrap()\n                .as_nanos() as u64,\n            expiration: exp,\n        };\n\n        // magic number hack to ensure a case for integration tests\n        if obj_val.generation == 1337 {\n            obj_val.generation = 1338;\n        }\n\n        if let Some(m) = metadata {\n            obj_val.metadata_len = m.len();\n            obj_val.metadata = m;\n        }\n\n        self.stores\n            .write()\n            .map_err(|_| KvStoreError::InternalError)?\n            .entry(obj_store_key)\n            .and_modify(|store| {\n                store.insert(obj_key.clone(), obj_val.clone());\n            })\n            .or_insert_with(|| {\n                let mut store = BTreeMap::new();\n                store.insert(obj_key, obj_val);\n                store\n            });\n\n        Ok(())\n    }\n\n    pub fn delete(\n        &self,\n        obj_store_key: ObjectStoreKey,\n        obj_key: ObjectKey,\n    ) -> Result<bool, KvStoreError> {\n        let mut res = Ok(true);\n\n        self.stores\n            .write()\n            .map_err(|_| KvStoreError::InternalError)?\n            .entry(obj_store_key)\n            .and_modify(|store| match store.get(&obj_key) {\n                // 404 if the key doesn't exist; otherwise, delete\n                Some(val) => {\n                    // manages ttl\n                    if let Some(exp) = val.expiration\n                        && SystemTime::now() >= exp\n                    {\n                        res = Ok(false);\n                    }\n                    store.remove(&obj_key);\n                }\n                None => res = Ok(false),\n            });\n\n        res\n    }\n\n    pub fn list(\n        &self,\n        obj_store_key: ObjectStoreKey,\n        cursor: Option<String>,\n        prefix: Option<String>,\n        limit: u32,\n    ) -> Result<Vec<u8>, KvStoreError> {\n        let mut res = Err(KvStoreError::InternalError);\n\n        let cursor = match cursor {\n            Some(c) => {\n                let cursor_bytes = BASE64_STANDARD\n                    .decode(c)\n                    .map_err(|_| KvStoreError::BadRequest)?;\n                let decoded =\n                    String::from_utf8(cursor_bytes).map_err(|_| KvStoreError::BadRequest)?;\n                Some(decoded)\n            }\n            None => None,\n        };\n\n        self.stores\n            .write()\n            .map_err(|_| KvStoreError::InternalError)?\n            .entry(obj_store_key.clone())\n            .and_modify(|store| {\n                // manages ttl\n                // a bit wasteful to run this loop twice, but we need mutable access to store,\n                // and it's already claimed in the filters below\n                let ttl_list = store.iter_mut().map(|(k, _)| k.clone()).collect::<Vec<_>>();\n                ttl_list.into_iter().for_each(|k| {\n                    let val = store.get(&k);\n                    if let Some(v) = val\n                        && let Some(exp) = v.expiration\n                        && SystemTime::now() >= exp\n                    {\n                        store.remove(&k);\n                    }\n                });\n\n                let mut list = store\n                    .iter_mut()\n                    .filter(|(k, _)| {\n                        if let Some(c) = &cursor {\n                            &k.0 > c\n                        } else {\n                            true\n                        }\n                    })\n                    .filter(|(k, _)| {\n                        if let Some(p) = &prefix {\n                            k.0.starts_with(p)\n                        } else {\n                            true\n                        }\n                    })\n                    .map(|(k, _)| String::from_utf8(k.0.as_bytes().to_vec()).unwrap())\n                    .collect::<Vec<_>>();\n\n                // limit\n                let old_len = list.len();\n                list.truncate(limit as usize);\n                let new_len = list.len();\n\n                let next_cursor = match old_len != new_len {\n                    true => Some(BASE64_STANDARD.encode(&list[new_len - 1])),\n                    false => None,\n                };\n\n                #[derive(Serialize)]\n                struct Metadata {\n                    limit: u32,\n                    #[serde(skip_serializing_if = \"Option::is_none\")]\n                    prefix: Option<String>,\n                    #[serde(skip_serializing_if = \"Option::is_none\")]\n                    next_cursor: Option<String>,\n                }\n                #[derive(Serialize)]\n                struct JsonOutput {\n                    data: Vec<String>,\n                    meta: Metadata,\n                }\n\n                let body = JsonOutput {\n                    data: list,\n                    meta: Metadata {\n                        limit,\n                        prefix,\n                        next_cursor,\n                    },\n                };\n\n                match serde_json::to_string(&body).map_err(|_| KvStoreError::InternalError) {\n                    Ok(s) => res = Ok(s.as_bytes().to_vec()),\n                    Err(e) => res = Err(e),\n                };\n            });\n        res\n    }\n}\n\n#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Default)]\npub struct ObjectStoreKey(String);\n\nimpl ObjectStoreKey {\n    pub fn new(key: impl ToString) -> Self {\n        Self(key.to_string())\n    }\n}\n\n#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Default)]\npub struct ObjectKey(String);\n\nimpl ObjectKey {\n    pub fn new(key: impl ToString) -> Result<Self, KeyValidationError> {\n        let key = key.to_string();\n        is_valid_key(&key)?;\n        Ok(Self(key))\n    }\n}\n\n#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, thiserror::Error)]\npub enum ObjectStoreError {\n    #[error(\"The object was not in the store\")]\n    MissingObject,\n    #[error(\"Viceroy's ObjectStore lock was poisoned\")]\n    PoisonedLock,\n    /// An Object Store with the given name was not found.\n    #[error(\"Unknown object-store: {0}\")]\n    UnknownObjectStore(String),\n}\n\nimpl From<&ObjectStoreError> for FastlyStatus {\n    fn from(e: &ObjectStoreError) -> Self {\n        use ObjectStoreError::*;\n        match e {\n            MissingObject => FastlyStatus::None,\n            PoisonedLock => panic!(\"{}\", e),\n            UnknownObjectStore(_) => FastlyStatus::Inval,\n        }\n    }\n}\n\n#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, thiserror::Error)]\npub enum KvStoreError {\n    #[error(\"The error was not set\")]\n    Uninitialized,\n    #[error(\n        \"KV store cannot or will not process the request due to something that is perceived to be a client error\"\n    )]\n    BadRequest,\n    #[error(\n        \"KV store cannot fulfill the request, as defined by the client's prerequisites (ie. if-generation-match)\"\n    )]\n    PreconditionFailed,\n    #[error(\"The size limit for a KV store key was exceeded\")]\n    PayloadTooLarge,\n    #[error(\"The system encountered an unexpected internal error\")]\n    InternalError,\n    #[error(\"Too many requests have been made to the KV store\")]\n    TooManyRequests,\n}\n\nimpl From<&KvError> for Result<Option<()>, KvStoreError> {\n    fn from(e: &KvError) -> Self {\n        Err(match e {\n            KvError::Ok => return Ok(Some(())),\n            KvError::NotFound => return Ok(None),\n            KvError::Uninitialized => KvStoreError::Uninitialized,\n            KvError::BadRequest => KvStoreError::BadRequest,\n            KvError::PreconditionFailed => KvStoreError::PreconditionFailed,\n            KvError::PayloadTooLarge => KvStoreError::PayloadTooLarge,\n            KvError::InternalError => KvStoreError::InternalError,\n            KvError::TooManyRequests => KvStoreError::TooManyRequests,\n        })\n    }\n}\n\nimpl From<&KvStoreError> for KvError {\n    fn from(e: &KvStoreError) -> Self {\n        match e {\n            KvStoreError::Uninitialized => KvError::Uninitialized,\n            KvStoreError::BadRequest => KvError::BadRequest,\n            KvStoreError::PreconditionFailed => KvError::PreconditionFailed,\n            KvStoreError::PayloadTooLarge => KvError::PayloadTooLarge,\n            KvStoreError::InternalError => KvError::InternalError,\n            KvStoreError::TooManyRequests => KvError::TooManyRequests,\n        }\n    }\n}\n\nimpl From<&KvStoreError> for FastlyStatus {\n    fn from(e: &KvStoreError) -> Self {\n        match e {\n            KvStoreError::Uninitialized => panic!(\"{}\", e),\n            KvStoreError::BadRequest => FastlyStatus::Inval,\n            KvStoreError::PreconditionFailed => FastlyStatus::Inval,\n            KvStoreError::PayloadTooLarge => FastlyStatus::Inval,\n            KvStoreError::InternalError => FastlyStatus::Inval,\n            KvStoreError::TooManyRequests => FastlyStatus::Inval,\n        }\n    }\n}\n\n/// Keys in the Object Store must follow the following rules:\n///\n///   * Keys can contain any sequence of valid Unicode characters, of length 1-1024 bytes when\n///     UTF-8 encoded.\n///   * Keys cannot contain Carriage Return or Line Feed characters.\n///   * Keys cannot start with `.well-known/acme-challenge/`.\n///   * Keys cannot be named `.` or `..`.\n///   * Keys cannot use Unicode characters 0 through 32, 65534 and 65535 as\n///     single-character key names.  (0x0 through 0x20, 0xFFFE and 0xFFFF)\nfn is_valid_key(key: &str) -> Result<(), KeyValidationError> {\n    let len = key.len();\n    if len < 1 {\n        return Err(KeyValidationError::EmptyKey);\n    } else if len > 1024 {\n        return Err(KeyValidationError::Over1024Bytes);\n    }\n\n    if key.starts_with(\".well-known/acme-challenge\") {\n        return Err(KeyValidationError::StartsWithWellKnown);\n    }\n\n    if key.eq(\"..\") || key.contains(\"../\") || key.ends_with(\"/..\") {\n        return Err(KeyValidationError::ContainsDotDot);\n    } else if key.eq(\".\") || key.contains(\"./\") || key.ends_with(\"/.\") {\n        return Err(KeyValidationError::ContainsDot);\n    } else if key.contains('\\r') {\n        return Err(KeyValidationError::Contains(\"\\r\".to_owned()));\n    } else if key.contains('\\n') {\n        return Err(KeyValidationError::Contains(\"\\n\".to_owned()));\n    } else if key.contains('#') {\n        return Err(KeyValidationError::Contains(\"#\".to_owned()));\n    } else if key.contains(';') {\n        return Err(KeyValidationError::Contains(\";\".to_owned()));\n    } else if key.contains('?') {\n        return Err(KeyValidationError::Contains(\"?\".to_owned()));\n    } else if key.contains('^') {\n        return Err(KeyValidationError::Contains(\"^\".to_owned()));\n    } else if key.contains('|') {\n        return Err(KeyValidationError::Contains(\"|\".to_owned()));\n    }\n\n    if key.len() == 1 {\n        let k = key.chars().next().unwrap();\n        match k {\n            '\\u{0}'..='\\u{20}' => {\n                return Err(KeyValidationError::Contains(k.escape_unicode().to_string()));\n            }\n            '\\u{FFFE}'..='\\u{FFFF}' => {\n                return Err(KeyValidationError::Contains(k.escape_unicode().to_string()));\n            }\n            _ => {}\n        }\n    }\n\n    Ok(())\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum KeyValidationError {\n    #[error(\"Keys for objects cannot be empty\")]\n    EmptyKey,\n    #[error(\"Keys for objects cannot be over 1024 bytes in size\")]\n    Over1024Bytes,\n    #[error(\"Keys for objects cannot start with `.well-known/acme-challenge`\")]\n    StartsWithWellKnown,\n    #[error(\"Keys for objects cannot be named `.`\")]\n    ContainsDot,\n    #[error(\"Keys for objects cannot be named `..`\")]\n    ContainsDotDot,\n    #[error(\"Keys for objects cannot contain a `{0}`\")]\n    Contains(String),\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    const STORE_NAME: &'static str = \"test_store\";\n\n    #[test]\n    fn test_kv_store_exists() {\n        let stores = ObjectStores::default();\n        stores\n            .insert_empty_store(ObjectStoreKey(STORE_NAME.to_string()))\n            .unwrap();\n\n        let res = stores.store_exists(STORE_NAME);\n        match res {\n            Ok(true) => {}\n            Ok(false) => panic!(\"should have been Ok(true)\"),\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n    }\n\n    #[test]\n    fn test_kv_store_basics() {\n        let stores = ObjectStores::default();\n        stores\n            .insert_empty_store(ObjectStoreKey(STORE_NAME.to_string()))\n            .unwrap();\n\n        let key = \"insert_key\".to_string();\n        let val1 = \"val1\".to_string();\n\n        // insert\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val1.clone().into(),\n            KvInsertMode::Overwrite,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n\n        // lookup\n        let res = stores.lookup(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n        );\n        match res {\n            Ok(Some(ov)) => {\n                assert_eq!(ov.body, val1.as_bytes().to_vec())\n            }\n            Ok(None) => panic!(\"should have been Ok(Some(_))\"),\n            Err(_) => panic!(\"should have been Ok(_)\"),\n        }\n\n        // list\n        let limit = 1000;\n        let res = stores.list(ObjectStoreKey(STORE_NAME.to_string()), None, None, limit);\n        match res {\n            Ok(ov) => {\n                let val = format!(r#\"{{\"data\":[\"{key}\"],\"meta\":{{\"limit\":{limit}}}}}\"#);\n                assert_eq!(std::str::from_utf8(&ov).unwrap(), val)\n            }\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        // delete\n        let res = stores.delete(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n        );\n        match res {\n            Ok(_) => {}\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n    }\n\n    #[test]\n    fn test_kv_store_item_404s() {\n        let stores = ObjectStores::default();\n        stores\n            .insert_empty_store(ObjectStoreKey(STORE_NAME.to_string()))\n            .unwrap();\n\n        let res = stores.lookup(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(\"bad_key\".to_string()),\n        );\n        match res {\n            Ok(Some(_)) => panic!(\"should have been Ok(None)\"),\n            Ok(None) => {}\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        let res = stores.delete(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(\"bad_key\".to_string()),\n        );\n        match res {\n            Ok(true) => panic!(\"should have been Ok(false)\"),\n            Ok(false) => {}\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n    }\n\n    #[test]\n    fn test_kv_store_item_insert_modes() {\n        let stores = ObjectStores::default();\n        stores\n            .insert_empty_store(ObjectStoreKey(STORE_NAME.to_string()))\n            .unwrap();\n\n        let key = \"insert_key\".to_string();\n        let val1 = \"val1\".to_string();\n        let val2 = \"val2\".to_string();\n        let val3 = \"val3\".to_string();\n\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val1.clone().into(),\n            KvInsertMode::Add,\n            None,\n            None,\n            None,\n        );\n        assert!(res.is_ok());\n        // fail on Add, because key already exists\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val1.clone().into(),\n            KvInsertMode::Add,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Ok(_) => panic!(\"should not have been OK\"),\n            Err(e) => assert_eq!(e, KvStoreError::PreconditionFailed),\n        }\n        // prepend val2\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val2.clone().into(),\n            KvInsertMode::Prepend,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n        // append val3\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val3.clone().into(),\n            KvInsertMode::Append,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n        let res = stores.lookup(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n        );\n        match res {\n            Ok(Some(ov)) => {\n                let val = format!(\"{val2}{val1}{val3}\");\n                assert_eq!(ov.body, val.as_bytes().to_vec())\n            }\n            Ok(None) => panic!(\"should have been Ok(Some((_))\"),\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        // overwrite val3\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val3.clone().into(),\n            KvInsertMode::Overwrite,\n            None,\n            Some(val2.clone()),\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n\n        // test overwrite\n        let res = stores.lookup(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n        );\n        match res {\n            Ok(Some(ov)) => {\n                assert_eq!(ov.body, val3.as_bytes().to_vec());\n                assert_eq!(ov.metadata, val2);\n            }\n            Ok(None) => panic!(\"should have been Ok(Some(_))\"),\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n    }\n\n    #[test]\n    fn test_kv_store_item_insert_generation() {\n        let stores = ObjectStores::default();\n        stores\n            .insert_empty_store(ObjectStoreKey(STORE_NAME.to_string()))\n            .unwrap();\n\n        let key = \"insert_key\".to_string();\n        let val1 = \"val1\".to_string();\n\n        // insert val1\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val1.clone().into(),\n            KvInsertMode::Overwrite,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n\n        // test overwrite, get gen\n        let generation;\n        let res = stores.lookup(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n        );\n        match res {\n            Ok(Some(ov)) => {\n                assert_eq!(ov.body, val1.as_bytes().to_vec());\n                generation = ov.generation;\n            }\n            Ok(None) => panic!(\"should have been Ok(Some(_))\"),\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        // test generation match failure\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val1.clone().into(),\n            KvInsertMode::Overwrite,\n            Some(1337),\n            None,\n            None,\n        );\n        match res {\n            Err(KvStoreError::PreconditionFailed) => {}\n            _ => panic!(\"should have been Err(KvStoreError::PreconditionFailed)\"),\n        }\n\n        // test generation match positive\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val1.clone().into(),\n            KvInsertMode::Overwrite,\n            Some(generation),\n            None,\n            None,\n        );\n        match res {\n            Ok(_) => {}\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        // check result\n        let res = stores.lookup(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n        );\n        match res {\n            Ok(Some(ov)) => {\n                assert_eq!(ov.body, val1.as_bytes().to_vec());\n            }\n            Ok(None) => panic!(\"should have been Ok(Some(_))\"),\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n    }\n\n    #[test]\n    fn test_kv_store_item_list_advanced() {\n        let stores = ObjectStores::default();\n        stores\n            .insert_empty_store(ObjectStoreKey(STORE_NAME.to_string()))\n            .unwrap();\n\n        let key = \"insert_key\".to_string();\n        let prefix = \"key\".to_string();\n        let key1 = format!(\"{prefix}1\").to_string();\n        let key2 = format!(\"{prefix}2\").to_string();\n        let key3 = format!(\"{prefix}3\").to_string();\n        let val1 = \"val1\".to_string();\n        let val2 = \"val2\".to_string();\n        let val3 = \"val3\".to_string();\n\n        // insert insert_key\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key.clone()),\n            val1.clone().into(),\n            KvInsertMode::Overwrite,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n\n        // insert val1\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key1.clone()),\n            val1.clone().into(),\n            KvInsertMode::Overwrite,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n        // insert val2\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key2.clone()),\n            val2.clone().into(),\n            KvInsertMode::Overwrite,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n        // insert val3\n        let res = stores.insert(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            ObjectKey(key3.clone()),\n            val3.clone().into(),\n            KvInsertMode::Overwrite,\n            None,\n            None,\n            None,\n        );\n        match res {\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n            _ => {}\n        }\n\n        // list\n        let limit = 1000;\n        let res = stores.list(ObjectStoreKey(STORE_NAME.to_string()), None, None, limit);\n        match res {\n            Ok(ov) => {\n                let val = format!(\n                    r#\"{{\"data\":[\"{key}\",\"{key1}\",\"{key2}\",\"{key3}\"],\"meta\":{{\"limit\":{limit}}}}}\"#\n                );\n                assert_eq!(std::str::from_utf8(&ov).unwrap(), val)\n            }\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        // list w/prefix\n        let limit = 1000;\n        let res = stores.list(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            None,\n            Some(prefix.clone()),\n            limit,\n        );\n        match res {\n            Ok(ov) => {\n                let val = format!(\n                    r#\"{{\"data\":[\"{key1}\",\"{key2}\",\"{key3}\"],\"meta\":{{\"limit\":{limit},\"prefix\":\"{prefix}\"}}}}\"#\n                );\n                assert_eq!(std::str::from_utf8(&ov).unwrap(), val)\n            }\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        // list w/prefix&limit\n        let limit = 1;\n        let res = stores.list(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            None,\n            Some(prefix.clone()),\n            limit,\n        );\n        match res {\n            Ok(ov) => {\n                let next_cursor = BASE64_STANDARD.encode(key1.clone());\n                let val = format!(\n                    r#\"{{\"data\":[\"{key1}\"],\"meta\":{{\"limit\":{limit},\"prefix\":\"{prefix}\",\"next_cursor\":\"{next_cursor}\"}}}}\"#\n                );\n                assert_eq!(std::str::from_utf8(&ov).unwrap(), val)\n            }\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        // list w/prefix&limit&cursor\n        let limit = 1;\n        let last_cursor = BASE64_STANDARD.encode(key1.clone());\n        let res = stores.list(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            Some(last_cursor),\n            Some(prefix.clone()),\n            limit,\n        );\n        match res {\n            Ok(ov) => {\n                let next_cursor = BASE64_STANDARD.encode(key2.clone());\n                let val = format!(\n                    r#\"{{\"data\":[\"{key2}\"],\"meta\":{{\"limit\":{limit},\"prefix\":\"{prefix}\",\"next_cursor\":\"{next_cursor}\"}}}}\"#\n                );\n                assert_eq!(std::str::from_utf8(&ov).unwrap(), val)\n            }\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n\n        // list w/prefix&limit&cursor\n        let limit = 1;\n        let last_cursor = BASE64_STANDARD.encode(key2.clone());\n        let res = stores.list(\n            ObjectStoreKey(STORE_NAME.to_string()),\n            Some(last_cursor),\n            Some(prefix.clone()),\n            limit,\n        );\n        match res {\n            Ok(ov) => {\n                let val = format!(\n                    r#\"{{\"data\":[\"{key3}\"],\"meta\":{{\"limit\":{limit},\"prefix\":\"{prefix}\"}}}}\"#\n                );\n                assert_eq!(std::str::from_utf8(&ov).unwrap(), val)\n            }\n            Err(e) => panic!(\"should not have been Err({:?})\", e),\n        }\n    }\n}\n"
  },
  {
    "path": "src/sandbox/async_item.rs",
    "content": "use std::time::{Duration, Instant};\n\nuse crate::cache::CacheEntry;\nuse crate::downstream::DownstreamRequest;\nuse crate::execute::NextRequest;\nuse crate::object_store::{KvStoreError, ObjectValue};\nuse crate::{body::Body, error::Error, streaming_body::StreamingBody};\nuse anyhow::anyhow;\nuse futures::Future;\nuse futures::FutureExt;\nuse http::Response;\nuse tokio::sync::oneshot;\n\n#[derive(Debug)]\npub struct PendingKvLookupTask(PeekableTask<Result<Option<ObjectValue>, KvStoreError>>);\nimpl PendingKvLookupTask {\n    pub fn new(t: PeekableTask<Result<Option<ObjectValue>, KvStoreError>>) -> PendingKvLookupTask {\n        PendingKvLookupTask(t)\n    }\n    pub fn task(self) -> PeekableTask<Result<Option<ObjectValue>, KvStoreError>> {\n        self.0\n    }\n}\n\n#[derive(Debug)]\npub struct PendingKvInsertTask(PeekableTask<Result<(), KvStoreError>>);\nimpl PendingKvInsertTask {\n    pub fn new(t: PeekableTask<Result<(), KvStoreError>>) -> PendingKvInsertTask {\n        PendingKvInsertTask(t)\n    }\n    pub fn task(self) -> PeekableTask<Result<(), KvStoreError>> {\n        self.0\n    }\n}\n\n#[derive(Debug)]\npub struct PendingKvDeleteTask(PeekableTask<Result<bool, KvStoreError>>);\nimpl PendingKvDeleteTask {\n    pub fn new(t: PeekableTask<Result<bool, KvStoreError>>) -> PendingKvDeleteTask {\n        PendingKvDeleteTask(t)\n    }\n    pub fn task(self) -> PeekableTask<Result<bool, KvStoreError>> {\n        self.0\n    }\n}\n\n#[derive(Debug)]\npub struct PendingKvListTask(PeekableTask<Result<Vec<u8>, KvStoreError>>);\nimpl PendingKvListTask {\n    pub fn new(t: PeekableTask<Result<Vec<u8>, KvStoreError>>) -> PendingKvListTask {\n        PendingKvListTask(t)\n    }\n    pub fn task(self) -> PeekableTask<Result<Vec<u8>, KvStoreError>> {\n        self.0\n    }\n}\n\n/// This is very similar to a [PeekableTask] task, but is intentionally\n/// set up so that it has no spawned tokio tasks separating us from the\n/// [oneshot::Sender] and detecting when it's dropped.\n///\n/// The `try_recv()` method can be used to make sure that we safely recover\n/// any pending requests that are sent to us without dropping them.\n#[derive(Debug)]\npub enum PendingDownstreamReqTask {\n    Complete(Result<Option<NextRequest>, Error>),\n    Waiting(oneshot::Receiver<NextRequest>, Instant),\n}\n\nimpl PendingDownstreamReqTask {\n    pub fn new(\n        rx: Option<oneshot::Receiver<NextRequest>>,\n        timeout: Duration,\n    ) -> PendingDownstreamReqTask {\n        if let Some(rx) = rx {\n            PendingDownstreamReqTask::Waiting(rx, Instant::now() + timeout)\n        } else {\n            PendingDownstreamReqTask::Complete(Ok(None))\n        }\n    }\n\n    /// Receive a downstream request.\n    ///\n    /// This will block until the sender side of the channel is either dropped\n    /// or sends us a value.\n    pub async fn recv(mut self) -> Result<Option<DownstreamRequest>, Error> {\n        self.await_ready().await;\n\n        let Self::Complete(res) = self else {\n            return Ok(None);\n        };\n\n        let Some(res) = res? else {\n            return Ok(None);\n        };\n\n        Ok(res.into_request())\n    }\n\n    /// Drive this task to completion.\n    ///\n    /// If we have passed the deadline for the task, we try to recover any request\n    /// in the channel. If there is none, then the timed out task will resolve to\n    /// [Error::NoDownstreamReqsAvailable].\n    ///\n    /// ## Cancel Safety\n    ///\n    /// Note that this method is used with [FutureExt::now_or_never] in [AsyncItem::is_ready], and\n    /// should therefore be kept cancel safe.\n    pub async fn await_ready(&mut self) {\n        if let Self::Waiting(rx, deadline) = self {\n            let v = if Instant::now() > *deadline {\n                rx.close();\n                rx.try_recv().ok()\n            } else {\n                tokio::time::timeout_at((*deadline).into(), rx)\n                    .await\n                    .ok()\n                    .and_then(|r| r.ok())\n            };\n\n            *self = Self::Complete(Ok(v));\n        }\n    }\n}\n\n/// An async item, waiting for a cache lookup to complete.\n#[derive(Debug)]\npub struct PendingCacheTask(PeekableTask<CacheEntry>);\nimpl PendingCacheTask {\n    pub fn new(t: PeekableTask<CacheEntry>) -> PendingCacheTask {\n        PendingCacheTask(t)\n    }\n    pub fn task(self) -> PeekableTask<CacheEntry> {\n        self.0\n    }\n\n    /// Get a mutable reference to the CacheEntry, possibly blocking until it becomes available.\n    pub async fn as_mut(&mut self) -> &mut Result<CacheEntry, Error> {\n        self.0.await_ready().await;\n        self.0\n            .get_mut()\n            .expect(\"internal error: PeekableTask was not ready after AwaitReady\")\n    }\n}\n\n/// Represents either a full body, or the write end of a streaming body.\n///\n/// This enum is needed because we reuse the handle for a body when it is transformed into a streaming\n/// body (writeable only). It is used within the body handle map in `Sandbox`.\n#[derive(Debug)]\npub enum AsyncItem {\n    Body(Body),\n    StreamingBody(StreamingBody),\n    PendingReq(PeekableTask<Response<Body>>),\n    PendingDownstream(PendingDownstreamReqTask),\n    PendingKvLookup(PendingKvLookupTask),\n    PendingKvInsert(PendingKvInsertTask),\n    PendingKvDelete(PendingKvDeleteTask),\n    PendingKvList(PendingKvListTask),\n    PendingCache(PendingCacheTask),\n    Ready,\n}\n\nimpl AsyncItem {\n    pub fn is_streaming(&self) -> bool {\n        matches!(self, Self::StreamingBody(_))\n    }\n\n    pub fn as_body(&self) -> Option<&Body> {\n        match self {\n            Self::Body(body) => Some(body),\n            _ => None,\n        }\n    }\n\n    pub fn as_body_mut(&mut self) -> Option<&mut Body> {\n        match self {\n            Self::Body(body) => Some(body),\n            _ => None,\n        }\n    }\n\n    pub fn into_body(self) -> Option<Body> {\n        match self {\n            Self::Body(body) => Some(body),\n            _ => None,\n        }\n    }\n\n    pub fn as_streaming_mut(&mut self) -> Option<&mut StreamingBody> {\n        match self {\n            Self::StreamingBody(sender) => Some(sender),\n            _ => None,\n        }\n    }\n\n    pub fn into_streaming(self) -> Option<StreamingBody> {\n        match self {\n            Self::StreamingBody(streaming) => Some(streaming),\n            _ => None,\n        }\n    }\n\n    pub fn begin_streaming(&mut self) -> Option<Body> {\n        if self.is_streaming() {\n            return None;\n        }\n\n        let (streaming, receiver) = StreamingBody::new();\n        match std::mem::replace(self, Self::StreamingBody(streaming)) {\n            Self::Body(mut body) => {\n                body.push_back(receiver);\n                Some(body)\n            }\n            _ => {\n                unreachable!(\"!self.is_streaming, but was actually streaming\");\n            }\n        }\n    }\n\n    pub fn as_pending_kv_lookup(&self) -> Option<&PendingKvLookupTask> {\n        match self {\n            Self::PendingKvLookup(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn into_pending_kv_lookup(self) -> Option<PendingKvLookupTask> {\n        match self {\n            Self::PendingKvLookup(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn as_pending_kv_insert(&self) -> Option<&PendingKvInsertTask> {\n        match self {\n            Self::PendingKvInsert(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn into_pending_kv_insert(self) -> Option<PendingKvInsertTask> {\n        match self {\n            Self::PendingKvInsert(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn as_pending_kv_delete(&self) -> Option<&PendingKvDeleteTask> {\n        match self {\n            Self::PendingKvDelete(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn into_pending_kv_delete(self) -> Option<PendingKvDeleteTask> {\n        match self {\n            Self::PendingKvDelete(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn as_pending_kv_list(&self) -> Option<&PendingKvListTask> {\n        match self {\n            Self::PendingKvList(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn into_pending_kv_list(self) -> Option<PendingKvListTask> {\n        match self {\n            Self::PendingKvList(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn as_pending_req(&self) -> Option<&PeekableTask<Response<Body>>> {\n        match self {\n            Self::PendingReq(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn as_pending_req_mut(&mut self) -> Option<&mut PeekableTask<Response<Body>>> {\n        match self {\n            Self::PendingReq(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn as_pending_cache(&self) -> Option<&PendingCacheTask> {\n        match self {\n            Self::PendingCache(op) => Some(op),\n            _ => None,\n        }\n    }\n\n    pub fn as_pending_cache_mut(&mut self) -> Option<&mut PendingCacheTask> {\n        match self {\n            Self::PendingCache(op) => Some(op),\n            _ => None,\n        }\n    }\n\n    pub fn into_pending_cache(self) -> Option<PendingCacheTask> {\n        match self {\n            Self::PendingCache(op) => Some(op),\n            _ => None,\n        }\n    }\n\n    pub fn into_pending_req(self) -> Option<PeekableTask<Response<Body>>> {\n        match self {\n            Self::PendingReq(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn as_pending_downstream_req_mut(&mut self) -> Option<&mut PendingDownstreamReqTask> {\n        match self {\n            Self::PendingDownstream(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub fn into_pending_downstream_req(self) -> Option<PendingDownstreamReqTask> {\n        match self {\n            Self::PendingDownstream(req) => Some(req),\n            _ => None,\n        }\n    }\n\n    pub async fn await_ready(&mut self) {\n        match self {\n            Self::StreamingBody(body) => body.await_ready().await,\n            Self::Body(body) => body.await_ready().await,\n            Self::PendingReq(req) => req.await_ready().await,\n            Self::PendingDownstream(req) => req.await_ready().await,\n            Self::PendingKvLookup(req) => req.0.await_ready().await,\n            Self::PendingKvInsert(req) => req.0.await_ready().await,\n            Self::PendingKvDelete(req) => req.0.await_ready().await,\n            Self::PendingKvList(req) => req.0.await_ready().await,\n            Self::PendingCache(req) => req.0.await_ready().await,\n            Self::Ready => (),\n        }\n    }\n\n    pub fn is_ready(&mut self) -> bool {\n        self.await_ready().now_or_never().is_some()\n    }\n}\n\nimpl From<PeekableTask<Response<Body>>> for AsyncItem {\n    fn from(req: PeekableTask<Response<Body>>) -> Self {\n        Self::PendingReq(req)\n    }\n}\n\nimpl From<PendingKvLookupTask> for AsyncItem {\n    fn from(task: PendingKvLookupTask) -> Self {\n        Self::PendingKvLookup(task)\n    }\n}\n\nimpl From<PendingKvInsertTask> for AsyncItem {\n    fn from(task: PendingKvInsertTask) -> Self {\n        Self::PendingKvInsert(task)\n    }\n}\n\nimpl From<PendingKvDeleteTask> for AsyncItem {\n    fn from(task: PendingKvDeleteTask) -> Self {\n        Self::PendingKvDelete(task)\n    }\n}\n\nimpl From<PendingKvListTask> for AsyncItem {\n    fn from(task: PendingKvListTask) -> Self {\n        Self::PendingKvList(task)\n    }\n}\n\nimpl From<PendingCacheTask> for AsyncItem {\n    fn from(task: PendingCacheTask) -> Self {\n        Self::PendingCache(task)\n    }\n}\n\nimpl From<PendingDownstreamReqTask> for AsyncItem {\n    fn from(task: PendingDownstreamReqTask) -> Self {\n        Self::PendingDownstream(task)\n    }\n}\n\n#[derive(Debug)]\npub enum PeekableTask<T> {\n    Waiting(oneshot::Receiver<Result<T, Error>>),\n    Complete(Result<T, Error>),\n}\n\nimpl<T: Send + 'static> PeekableTask<T> {\n    pub async fn spawn(fut: impl Future<Output = Result<T, Error>> + 'static + Send) -> Self {\n        let (sender, receiver) = oneshot::channel();\n        tokio::task::spawn(async move { sender.send(fut.await) });\n        Self::Waiting(receiver)\n    }\n\n    pub fn complete(t: T) -> Self {\n        PeekableTask::Complete(Ok(t))\n    }\n\n    /// Block until a response is ready.\n    pub async fn await_ready(&mut self) {\n        if let PeekableTask::Waiting(rx) = self {\n            match rx.await {\n                Ok(v) => *self = PeekableTask::Complete(v),\n                _ => {\n                    // todo, not the correct error type\n                    *self = PeekableTask::Complete(Err(anyhow!(\n                        \"peekable task sender unexpectedly dropped\"\n                    )\n                    .into()));\n                }\n            }\n        }\n    }\n\n    pub async fn recv(self) -> Result<T, Error> {\n        match self {\n            PeekableTask::Waiting(rx) => rx\n                .await\n                .map_err(|_| anyhow!(\"peekable task sender unexpectedly dropped\"))?,\n            PeekableTask::Complete(res) => res,\n        }\n    }\n\n    pub fn get_mut(&mut self) -> Option<&mut Result<T, Error>> {\n        if let PeekableTask::Complete(res) = self {\n            Some(res)\n        } else {\n            None\n        }\n    }\n}\n"
  },
  {
    "path": "src/sandbox/downstream.rs",
    "content": "//! Downstream response.\n\nuse {\n    crate::{\n        body::Body,\n        downstream::DownstreamResponse,\n        error::Error,\n        framing::{content_length_is_valid, transfer_encoding_is_supported},\n        handoff::HandoffInfo,\n        headers::filter_outgoing_headers,\n        sandbox::ViceroyResponseMetadata,\n        wiggle_abi::types::FramingHeadersMode,\n    },\n    hyper::http::response::Response,\n    std::mem,\n    tokio::sync::oneshot::Sender,\n};\n\n/// Downstream response states.\n///\n/// See [`Sandbox::set_downstream_response_sender`][set] and\n/// [`Sandbox::send_downstream_response`][send] for more information.\n///\n/// [send]: struct.Sandbox.html#method.send_downstream_response\n/// [set]: struct.Sandbox.html#method.set_downstream_response_sender\npub enum DownstreamResponseState {\n    /// No channel to send the response has been opened yet.\n    Closed,\n    /// A channel has been opened, but no response has been sent yet.\n    Pending(Sender<DownstreamResponse>),\n    /// The guest has initiated a proxy to Pushpin; the response will come from there.\n    HandingOffToPushpin,\n    /// The guest has initiated a proxy to a Backend; the response will come from there.\n    HandingOffToBackend,\n    /// A response has already been sent downstream.\n    Sent,\n}\n\nimpl DownstreamResponseState {\n    /// Open a channel to send a [`Response`][resp] downstream, given a [`oneshot::Sender`][sender].\n    ///\n    /// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n    /// [sender]: https://docs.rs/tokio/latest/tokio/sync/oneshot/struct.Sender.html\n    pub fn new(sender: Sender<DownstreamResponse>) -> Self {\n        DownstreamResponseState::Pending(sender)\n    }\n\n    pub fn is_unsent(&self) -> bool {\n        matches!(self, Self::Pending(_))\n    }\n\n    /// Send a [`Response`][resp] downstream.\n    ///\n    /// Yield an error if a response has already been sent.\n    ///\n    /// # Panics\n    ///\n    /// This method will panic if the associated receiver was dropped prematurely.\n    ///\n    /// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n    pub fn send(&mut self, mut response: Response<Body>) -> Result<(), Error> {\n        use DownstreamResponseState::{\n            Closed, HandingOffToBackend, HandingOffToPushpin, Pending, Sent,\n        };\n\n        let mut framing_headers_mode = response\n            .extensions()\n            .get::<ViceroyResponseMetadata>()\n            .map(|metadata: &ViceroyResponseMetadata| metadata.framing_headers_mode)\n            .unwrap_or(FramingHeadersMode::Automatic);\n\n        if framing_headers_mode == FramingHeadersMode::ManuallyFromHeaders {\n            if !content_length_is_valid(response.headers()) {\n                tracing::warn!(\n                    \"Downstream response has malformed Content-Length header, falling back to automatic framing.\"\n                );\n                framing_headers_mode = FramingHeadersMode::Automatic;\n            } else if !transfer_encoding_is_supported(response.headers()) {\n                tracing::warn!(\n                    \"Downstream response has unsupported Transfer-Encoding header, falling back to automatic framing.\"\n                );\n                framing_headers_mode = FramingHeadersMode::Automatic;\n            } else if !response\n                .headers()\n                .contains_key(hyper::header::CONTENT_LENGTH)\n                && !response\n                    .headers()\n                    .contains_key(hyper::header::TRANSFER_ENCODING)\n            {\n                tracing::warn!(\n                    \"Downstream response has neither Content-Length nor Transfer-Encoding header, falling back to automatic framing.\"\n                );\n                framing_headers_mode = FramingHeadersMode::Automatic;\n            }\n        }\n        if framing_headers_mode != FramingHeadersMode::ManuallyFromHeaders {\n            filter_outgoing_headers(response.headers_mut());\n        }\n\n        // Supporting 103 Early Hints responses is currently infeasible, as Hyper does not\n        // support sending multiple responses on a single connection. But we don't want\n        // to generate errors for them either. Early Hints will be dropped, but logged\n        // so that people will know they *did work*, even though they won't reach\n        // the client.\n        //\n        // Other 1xx status codes, however, are not allowed and generate an InvalidArgument\n        // error.\n        if response.status().as_u16() == 103 {\n            // We'll do these at different log levels in case someone wants to squelch some.\n            tracing::warn!(\n                \"Guest returned 103 Early Hints response which will not be sent to the client\"\n            );\n            tracing::info!(\"{:#?}\", response);\n            return Ok(());\n        } else if response.status().is_informational() {\n            return Err(Error::InvalidArgument);\n        }\n\n        // Mark this `DownstreamResponse` as having been sent, and match on the previous value.\n        match mem::replace(self, Sent) {\n            Closed => panic!(\"downstream response channel was closed\"),\n            Pending(sender) => sender\n                .send(DownstreamResponse::Http(response))\n                .map_err(|_| ())\n                .expect(\"response receiver is open\"),\n            Sent | HandingOffToPushpin | HandingOffToBackend => {\n                return Err(Error::DownstreamRespSending);\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn redirect_to_pushpin(&mut self, redirect_info: HandoffInfo) -> Result<(), Error> {\n        use DownstreamResponseState::{\n            Closed, HandingOffToBackend, HandingOffToPushpin, Pending, Sent,\n        };\n\n        // Mark this `DownstreamResponse` as having been sent, and match on the previous value.\n        match mem::replace(self, HandingOffToPushpin) {\n            Closed => panic!(\"downstream response channel was closed\"),\n            Pending(sender) => sender\n                .send(DownstreamResponse::HandoffToPushpin(redirect_info))\n                .map_err(|_| ())\n                .expect(\"response receiver is open\"),\n            Sent | HandingOffToPushpin | HandingOffToBackend => {\n                return Err(Error::DownstreamRespSending);\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn redirect_to_backend(&mut self, redirect_info: HandoffInfo) -> Result<(), Error> {\n        use DownstreamResponseState::{\n            Closed, HandingOffToBackend, HandingOffToPushpin, Pending, Sent,\n        };\n\n        // Mark this `DownstreamResponse` as having been sent, and match on the previous value.\n        match mem::replace(self, HandingOffToBackend) {\n            Closed => panic!(\"downstream response channel was closed\"),\n            Pending(sender) => sender\n                .send(DownstreamResponse::HandoffToBackend(redirect_info))\n                .map_err(|_| ())\n                .expect(\"response receiver is open\"),\n            Sent | HandingOffToPushpin | HandingOffToBackend => {\n                return Err(Error::DownstreamRespSending);\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Close the `DownstreamResponse`, potentially without sending any response.\n    #[allow(unused)]\n    pub fn close(&mut self) {\n        *self = DownstreamResponseState::Closed;\n    }\n}\n"
  },
  {
    "path": "src/sandbox.rs",
    "content": "//! Sandbox type and related facilities.\n\nmod async_item;\nmod downstream;\n\npub use async_item::{\n    AsyncItem, PeekableTask, PendingCacheTask, PendingDownstreamReqTask, PendingKvDeleteTask,\n    PendingKvInsertTask, PendingKvListTask, PendingKvLookupTask,\n};\n\nuse std::collections::HashMap;\nuse std::future::Future;\nuse std::io::Write;\nuse std::net::IpAddr;\nuse std::path::Path;\nuse std::sync::atomic::AtomicU64;\nuse std::sync::{Arc, Mutex};\nuse std::time::Duration;\n\nuse crate::cache::{Cache, CacheEntry};\nuse crate::linking::Limiter;\nuse crate::object_store::KvStoreError;\nuse crate::wiggle_abi::types::{CacheBusyHandle, CacheHandle, FramingHeadersMode};\n\nuse {\n    self::downstream::DownstreamResponseState,\n    crate::{\n        ExecuteCtx,\n        acl::Acl,\n        body::Body,\n        config::{Backend, Backends, Dictionaries, LoadedDictionary},\n        downstream::{DownstreamMetadata, DownstreamRequest},\n        error::{Error, HandleError},\n        handoff::HandoffInfo,\n        logging::LogEndpoint,\n        object_store::{ObjectKey, ObjectStoreKey, ObjectStores, ObjectValue},\n        secret_store::{SecretLookup, SecretStores},\n        shielding_site::ShieldingSites,\n        streaming_body::StreamingBody,\n        upstream::{SelectTarget, TlsConfig},\n        wiggle_abi::types::{\n            self, AclHandle, BodyHandle, ContentEncodings, DictionaryHandle, EndpointHandle,\n            KvInsertMode, KvStoreDeleteHandle, KvStoreHandle, KvStoreInsertHandle,\n            KvStoreListHandle, KvStoreLookupHandle, PendingKvDeleteHandle, PendingKvInsertHandle,\n            PendingKvListHandle, PendingKvLookupHandle, PendingRequestHandle, RequestHandle,\n            RequestPromiseHandle, ResponseHandle, SecretHandle, SecretStoreHandle,\n        },\n    },\n    cranelift_entity::{PrimaryMap, entity_impl},\n    futures::future::{self, FutureExt},\n    http::{HeaderMap, Response, request, response},\n};\n\nconst NEXT_REQ_ACCEPT_MAX: usize = 5;\nconst NEXT_REQ_TIMEOUT: Duration = Duration::from_secs(10);\nconst NGWAF_ALLOW_VERDICT: &str = \"allow\";\n\npub struct RequestParts {\n    parts: Option<request::Parts>,\n    metadata: Option<DownstreamMetadata>,\n}\n\n/// Data specific to an individual request, including any host-side\n/// allocations on behalf of the guest processing the request.\npub struct Sandbox {\n    sandbox_id: u64,\n    /// The amount of time we've spent on this sandbox in microseconds.\n    pub active_cpu_time_us: Arc<AtomicU64>,\n    /// Handle for the downstream request \"parts\". NB the backing parts data can be mutated\n    /// or even removed from the relevant map.\n    downstream_req_handle: RequestHandle,\n    /// Handle for the downstream request body. NB the backing body data can be mutated\n    /// or even removed from the relevant map.\n    downstream_req_body_handle: BodyHandle,\n    /// A channel for sending a [`Response`][resp] downstream to the client.\n    ///\n    /// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n    downstream_resp: DownstreamResponseState,\n    /// Handle for receiving a new downstream request.\n    downstream_pending_handle: Option<AsyncItemHandle>,\n    /// A handle map for items that provide blocking operations. These items are grouped together\n    /// in order to support generic async operations that work across different object types.\n    async_items: PrimaryMap<AsyncItemHandle, Option<AsyncItem>>,\n    /// The context for executing the service that is shared between sandboxes.\n    ctx: Arc<ExecuteCtx>,\n    /// A handle map for the component [`Parts`][parts] of the sandbox's HTTP [`Request`][req]s.\n    ///\n    /// [parts]: https://docs.rs/http/latest/http/request/struct.Parts.html\n    /// [req]: https://docs.rs/http/latest/http/request/struct.Request.html\n    req_parts: PrimaryMap<RequestHandle, RequestParts>,\n    /// A handle map for the component [`Parts`][parts] of the sandbox's HTTP [`Response`][resp]s.\n    ///\n    /// [parts]: https://docs.rs/http/latest/http/response/struct.Parts.html\n    /// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n    resp_parts: PrimaryMap<ResponseHandle, Option<response::Parts>>,\n    /// Where to direct logging endpoint messages.\n    capture_logs: Arc<Mutex<dyn Write + Send>>,\n    /// A handle map for logging endpoints.\n    log_endpoints: PrimaryMap<EndpointHandle, LogEndpoint>,\n    /// A by-name map for logging endpoints.\n    log_endpoints_by_name: HashMap<Vec<u8>, EndpointHandle>,\n    /// Active ACL handles.\n    acl_handles: PrimaryMap<AclHandle, Arc<Acl>>,\n    /// The NGWAF verdict to return when using the `inspect` hostcall.\n    ngwaf_verdict: String,\n    /// The backends dynamically added by the program. This is separated from\n    /// `backends` because we do not want one sandbox to effect the backends\n    /// available to any other sandbox.\n    dynamic_backends: Backends,\n    /// The dictionaries that have been opened by the guest.\n    loaded_dictionaries: PrimaryMap<DictionaryHandle, LoadedDictionary>,\n    /// The object stores configured for this execution.\n    ///\n    /// Populated prior to guest execution.\n    kv_store_by_name: PrimaryMap<KvStoreHandle, ObjectStoreKey>,\n    /// The secret stores configured for this execution.\n    ///\n    /// Populated prior to guest execution, and never modified.\n    secret_stores_by_name: PrimaryMap<SecretStoreHandle, String>,\n    /// The secrets for this execution.\n    ///\n    /// Populated prior to guest execution, and never modified.\n    secrets_by_name: PrimaryMap<SecretHandle, SecretLookup>,\n    /// How many additional downstream requests have been receive by this Sandbox.\n    next_req_accepted: usize,\n    /// Memory usage limiter to ensure the guest doesn't use over 128mb of heap.\n    limiter: Limiter,\n}\n\nimpl Sandbox {\n    /// Create an empty sandbox.\n    #[allow(clippy::too_many_arguments)]\n    pub fn new(\n        downstream: DownstreamRequest,\n        active_cpu_time_us: Arc<AtomicU64>,\n        ctx: Arc<ExecuteCtx>,\n    ) -> Sandbox {\n        let (parts, body) = downstream.req.into_parts();\n\n        let mut async_items: PrimaryMap<AsyncItemHandle, Option<AsyncItem>> = PrimaryMap::new();\n        let mut req_parts = PrimaryMap::new();\n\n        let sandbox_id = downstream.metadata.req_id;\n        let downstream_req_handle = req_parts.push(RequestParts {\n            parts: Some(parts),\n            metadata: Some(downstream.metadata),\n        });\n        let downstream_req_body_handle = async_items.push(Some(AsyncItem::Body(body))).into();\n\n        let limiter = if ctx.is_component() {\n            Limiter::for_wasip2()\n        } else {\n            Limiter::for_wasip1()\n        };\n\n        Sandbox {\n            sandbox_id,\n            downstream_req_handle,\n            downstream_req_body_handle,\n            active_cpu_time_us,\n            async_items,\n            req_parts,\n            resp_parts: PrimaryMap::new(),\n            downstream_resp: DownstreamResponseState::new(downstream.sender),\n            capture_logs: ctx.capture_logs(),\n            log_endpoints: PrimaryMap::new(),\n            log_endpoints_by_name: HashMap::new(),\n            acl_handles: PrimaryMap::new(),\n            ngwaf_verdict: NGWAF_ALLOW_VERDICT.to_string(),\n            dynamic_backends: Backends::default(),\n            loaded_dictionaries: PrimaryMap::new(),\n            kv_store_by_name: PrimaryMap::new(),\n            secret_stores_by_name: PrimaryMap::new(),\n            secrets_by_name: PrimaryMap::new(),\n            downstream_pending_handle: None,\n            next_req_accepted: 0,\n            limiter,\n\n            ctx,\n        }\n    }\n\n    // ----- Downstream Request API -----\n\n    /// Retrieve the downstream metadata address associated with a request handle.\n    pub fn downstream_metadata(\n        &self,\n        handle: RequestHandle,\n    ) -> Result<Option<&DownstreamMetadata>, HandleError> {\n        self.req_parts\n            .get(handle)\n            .ok_or(HandleError::InvalidRequestHandle(handle))\n            .map(|r| r.metadata.as_ref())\n    }\n\n    /// Retrieve the downstream client IP address associated with a request handle.\n    pub fn downstream_client_ip(\n        &self,\n        handle: RequestHandle,\n    ) -> Result<Option<IpAddr>, HandleError> {\n        Ok(self\n            .downstream_metadata(handle)?\n            .map(|md| md.client_addr.ip()))\n    }\n\n    /// Retrieve the IP address the downstream client connected to a request handle.\n    pub fn downstream_server_ip(\n        &self,\n        handle: RequestHandle,\n    ) -> Result<Option<IpAddr>, HandleError> {\n        Ok(self\n            .downstream_metadata(handle)?\n            .map(|md| md.server_addr.ip()))\n    }\n\n    /// Retrieve the compliance region that received the request for the given handle.\n    pub fn downstream_compliance_region(\n        &self,\n        handle: RequestHandle,\n    ) -> Result<Option<&str>, HandleError> {\n        Ok(self\n            .downstream_metadata(handle)?\n            .map(|md| md.compliance_region.as_str()))\n    }\n\n    /// Retrieve the request ID for the given request handle.\n    pub fn downstream_request_id(&self, handle: RequestHandle) -> Result<Option<u64>, HandleError> {\n        Ok(self.downstream_metadata(handle)?.map(|md| md.req_id))\n    }\n\n    /// Retrieve the handle corresponding to the most recent downstream request.\n    pub fn downstream_request(&self) -> RequestHandle {\n        self.downstream_req_handle\n    }\n\n    /// Retrieve the handle corresponding to the downstream request body.\n    pub fn downstream_request_body(&self) -> BodyHandle {\n        self.downstream_req_body_handle\n    }\n\n    /// Access the header map that was copied from the original downstream request.\n    pub fn downstream_original_headers(\n        &self,\n        handle: RequestHandle,\n    ) -> Result<Option<&HeaderMap>, HandleError> {\n        Ok(self\n            .downstream_metadata(handle)?\n            .map(|md| &md.original_headers))\n    }\n\n    // ----- Downstream Response API -----\n\n    /// Send the downstream response.\n    ///\n    /// Yield an error if a response has already been sent.\n    ///\n    /// # Panics\n    ///\n    /// This method must only be called once per downstream request, after which attempting\n    /// to send another response will trigger a panic.\n    pub fn send_downstream_response(&mut self, resp: Response<Body>) -> Result<(), Error> {\n        self.downstream_resp.send(resp)\n    }\n\n    /// Redirect the downstream request to Pushpin.\n    ///\n    /// Yield an error if a response has already been sent.\n    ///\n    /// # Panics\n    ///\n    /// This method must only be called once per downstream request, after which attempting\n    /// to send another response will trigger a panic.\n    pub fn redirect_downstream_to_pushpin(\n        &mut self,\n        redirect_info: HandoffInfo,\n    ) -> Result<(), Error> {\n        self.downstream_resp.redirect_to_pushpin(redirect_info)\n    }\n\n    /// Redirect the downstream request to a backend.\n    ///\n    /// Yield an error if a response has already been sent.\n    ///\n    /// # Panics\n    ///\n    /// This method must only be called once per downstream request, after which attempting\n    /// to send another response will trigger a panic.\n    pub fn redirect_downstream_to_backend(\n        &mut self,\n        redirect_info: HandoffInfo,\n    ) -> Result<(), Error> {\n        self.downstream_resp.redirect_to_backend(redirect_info)\n    }\n\n    /// Ensure the downstream response sender is closed, and send the provided response if it\n    /// isn't.\n    pub fn close_downstream_response_sender(&mut self, resp: Response<Body>) {\n        let _ = self.downstream_resp.send(resp);\n    }\n\n    // ----- Bodies API -----\n\n    /// Insert a [`Body`][body] into the sandbox.\n    ///\n    /// This method returns the [`BodyHandle`][handle], which can then be used to access and mutate\n    /// the response parts.\n    ///\n    /// [handle]: ../wiggle_abi/types/struct.BodyHandle.html\n    /// [body]: ../body/struct.Body.html\n    pub fn insert_body(&mut self, body: Body) -> BodyHandle {\n        self.async_items.push(Some(AsyncItem::Body(body))).into()\n    }\n\n    /// Get a reference to a [`Body`][body], given its [`BodyHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a body in the sandbox.\n    ///\n    /// [body]: ../body/struct.Body.html\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.BodyHandle.html\n    pub fn body(&self, handle: BodyHandle) -> Result<&Body, HandleError> {\n        self.async_items\n            .get(handle.into())\n            .and_then(Option::as_ref)\n            .and_then(AsyncItem::as_body)\n            .ok_or(HandleError::InvalidBodyHandle(handle))\n    }\n\n    /// Get a mutable reference to a [`Body`][body], given its [`BodyHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a body in the sandbox.\n    ///\n    /// [body]: ../body/struct.Body.html\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.BodyHandle.html\n    pub fn body_mut(&mut self, handle: BodyHandle) -> Result<&mut Body, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::as_mut)\n            .and_then(AsyncItem::as_body_mut)\n            .ok_or(HandleError::InvalidBodyHandle(handle))\n    }\n\n    /// Take ownership of a [`Body`][body], given its [`BodyHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a body in the sandbox.\n    ///\n    /// [body]: ../body/struct.Body.html\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.BodyHandle.html\n    pub fn take_body(&mut self, handle: BodyHandle) -> Result<Body, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .and_then(AsyncItem::into_body)\n            .ok_or(HandleError::InvalidBodyHandle(handle))\n    }\n\n    /// Drop a [`Body`][crate::body::Body] from the [`Sandbox`], given its [`BodyHandle`][crate::wiggle_abi::types::BodyHandle].\n    ///\n    /// Returns a [`HandleError`][crate::error::HandleError] if the handle is not associated with a body in the sandbox.\n    pub fn drop_body(&mut self, handle: BodyHandle) -> Result<(), HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .map(drop)\n            .ok_or(HandleError::InvalidBodyHandle(handle))\n    }\n\n    /// Transition a normal [`Body`][body] into the write end of a streaming body, returning\n    /// the original body with the read end appended.\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a body in the sandbox.\n    ///\n    /// [body]: ../body/struct.Body.html\n    /// [err]: ../error/enum.HandleError.html\n    pub fn begin_streaming(&mut self, handle: BodyHandle) -> Result<Body, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::as_mut)\n            .and_then(AsyncItem::begin_streaming)\n            .ok_or(HandleError::InvalidBodyHandle(handle))\n    }\n\n    /// Returns `true` if and only if the provided `BodyHandle` is the downstream body being sent.\n    ///\n    /// To get a mutable reference to the streaming body `Sender`, see\n    /// [`Sandbox::streaming_body_mut`](struct.Sandbox.html#method.streaming_body_mut).\n    pub fn is_streaming_body(&self, handle: BodyHandle) -> bool {\n        if let Some(Some(body)) = self.async_items.get(handle.into()) {\n            body.is_streaming()\n        } else {\n            false\n        }\n    }\n\n    /// Get a mutable reference to the streaming body `Sender`, if and only if the provided\n    /// `BodyHandle` is the downstream body being sent.\n    ///\n    /// To check if a handle is the currently-streaming downstream response body, see\n    /// [`Sandbox::is_streaming_body`](struct.Sandbox.html#method.is_streaming_body).\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a body in the sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    pub fn streaming_body_mut(\n        &mut self,\n        handle: BodyHandle,\n    ) -> Result<&mut StreamingBody, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::as_mut)\n            .and_then(AsyncItem::as_streaming_mut)\n            .ok_or(HandleError::InvalidBodyHandle(handle))\n    }\n\n    /// Take ownership of a streaming body `Sender`, if and only if the provided\n    /// `BodyHandle` is the downstream body being sent.\n    ///\n    /// To check if a handle is the currently-streaming downstream response body, see\n    /// [`Sandbox::is_streaming_body`](struct.Sandbox.html#method.is_streaming_body).\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a body in the sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    pub fn take_streaming_body(\n        &mut self,\n        handle: BodyHandle,\n    ) -> Result<StreamingBody, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .and_then(AsyncItem::into_streaming)\n            .ok_or(HandleError::InvalidBodyHandle(handle))\n    }\n\n    // ----- Request Parts API -----\n\n    /// Insert the [`Parts`][parts] of a [`Request`][req] into the sandbox.\n    ///\n    /// This method returns a new [`RequestHandle`][handle], which can then be used to access\n    /// and mutate the request parts.\n    ///\n    /// [handle]: ../wiggle_abi/types/struct.RequestHandle.html\n    /// [parts]: https://docs.rs/http/latest/http/request/struct.Parts.html\n    /// [req]: https://docs.rs/http/latest/http/request/struct.Request.html\n    pub fn insert_request_parts(&mut self, parts: request::Parts) -> RequestHandle {\n        self.req_parts.push(RequestParts {\n            parts: Some(parts),\n            metadata: None,\n        })\n    }\n\n    /// Get a reference to the [`Parts`][parts] of a [`Request`][req], given its\n    /// [`RequestHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a request in the\n    /// sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.RequestHandle.html\n    /// [parts]: https://docs.rs/http/latest/http/request/struct.Parts.html\n    /// [req]: https://docs.rs/http/latest/http/request/struct.Request.html\n    pub fn request_parts(&self, handle: RequestHandle) -> Result<&request::Parts, HandleError> {\n        self.req_parts\n            .get(handle)\n            .and_then(|r| r.parts.as_ref())\n            .ok_or(HandleError::InvalidRequestHandle(handle))\n    }\n\n    /// Get a mutable reference to the [`Parts`][parts] of a [`Request`][req], given its\n    /// [`RequestHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a request in the\n    /// sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.RequestHandle.html\n    /// [parts]: https://docs.rs/http/latest/http/request/struct.Parts.html\n    /// [req]: https://docs.rs/http/latest/http/request/struct.Request.html\n    pub fn request_parts_mut(\n        &mut self,\n        handle: RequestHandle,\n    ) -> Result<&mut request::Parts, HandleError> {\n        self.req_parts\n            .get_mut(handle)\n            .and_then(|r| r.parts.as_mut())\n            .ok_or(HandleError::InvalidRequestHandle(handle))\n    }\n\n    /// Take ownership of the [`Parts`][parts] of a [`Request`][req], given its\n    /// [`RequestHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a request in the\n    /// sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.RequestHandle.html\n    /// [parts]: https://docs.rs/http/latest/http/request/struct.Parts.html\n    /// [req]: https://docs.rs/http/latest/http/request/struct.Request.html\n    pub fn take_request_parts(\n        &mut self,\n        handle: RequestHandle,\n    ) -> Result<request::Parts, HandleError> {\n        self.req_parts\n            .get_mut(handle)\n            .and_then(|r| r.parts.take())\n            .ok_or(HandleError::InvalidRequestHandle(handle))\n    }\n\n    // ----- Response Parts API -----\n\n    /// Insert the [`Parts`][parts] of a [`Response`][resp] into the sandbox.\n    ///\n    /// This method returns a new [`ResponseHandle`][handle], which can then be used to access\n    /// and mutate the response parts.\n    ///\n    /// [handle]: ../wiggle_abi/types/struct.ResponseHandle.html\n    /// [parts]: https://docs.rs/http/latest/http/response/struct.Parts.html\n    /// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n    pub fn insert_response_parts(&mut self, parts: response::Parts) -> ResponseHandle {\n        self.resp_parts.push(Some(parts))\n    }\n\n    /// Get a reference to the [`Parts`][parts] of a [`Response`][resp], given its\n    /// [`ResponseHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a response in the\n    /// sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.ResponseHandle.html\n    /// [parts]: https://docs.rs/http/latest/http/response/struct.Parts.html\n    /// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n    pub fn response_parts(&self, handle: ResponseHandle) -> Result<&response::Parts, HandleError> {\n        self.resp_parts\n            .get(handle)\n            .and_then(Option::as_ref)\n            .ok_or(HandleError::InvalidResponseHandle(handle))\n    }\n\n    /// Get a mutable reference to the [`Parts`][parts] of a [`Response`][resp], given its\n    /// [`ResponseHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a response in the\n    /// sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.ResponseHandle.html\n    /// [parts]: https://docs.rs/http/latest/http/response/struct.Parts.html\n    /// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n    pub fn response_parts_mut(\n        &mut self,\n        handle: ResponseHandle,\n    ) -> Result<&mut response::Parts, HandleError> {\n        self.resp_parts\n            .get_mut(handle)\n            .and_then(Option::as_mut)\n            .ok_or(HandleError::InvalidResponseHandle(handle))\n    }\n\n    /// Take ownership of the [`Parts`][parts] of a [`Response`][resp], given its\n    /// [`ResponseHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with a response in the\n    /// sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.ResponseHandle.html\n    /// [parts]: https://docs.rs/http/latest/http/response/struct.Parts.html\n    /// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n    pub fn take_response_parts(\n        &mut self,\n        handle: ResponseHandle,\n    ) -> Result<response::Parts, HandleError> {\n        self.resp_parts\n            .get_mut(handle)\n            .and_then(Option::take)\n            .ok_or(HandleError::InvalidResponseHandle(handle))\n    }\n\n    pub fn insert_response(&mut self, resp: Response<Body>) -> (ResponseHandle, BodyHandle) {\n        let (resp_parts, resp_body) = resp.into_parts();\n        let resp_handle = self.insert_response_parts(resp_parts);\n        let body_handle = self.insert_body(resp_body);\n        (resp_handle, body_handle)\n    }\n\n    // ----- Logging Endpoints API -----\n\n    /// Get an [`EndpointHandle`][handle] from the sandbox, corresponding to the provided\n    /// endpoint name. A new backing [`LogEndpoint`] will be created if one does not\n    /// already exist.\n    ///\n    /// [handle]: ../wiggle_abi/types/struct.EndpointHandle.html\n    /// [endpoint]: ../logging/struct.LogEndpoint.html\n    pub fn log_endpoint_handle(&mut self, name: &[u8]) -> EndpointHandle {\n        if let Some(handle) = self.log_endpoints_by_name.get(name).copied() {\n            return handle;\n        }\n        let endpoint = LogEndpoint::new(name, self.capture_logs.clone());\n        let handle = self.log_endpoints.push(endpoint);\n        self.log_endpoints_by_name.insert(name.to_owned(), handle);\n        handle\n    }\n\n    /// Get a reference to a [`LogEndpoint`][endpoint], given its [`EndpointHandle`][handle].\n    ///\n    /// Returns a [`HandleError`][err] if the handle is not associated with an endpoint in the\n    /// sandbox.\n    ///\n    /// [err]: ../error/enum.HandleError.html\n    /// [handle]: ../wiggle_abi/types/struct.EndpointHandle.html\n    /// [endpoint]: ../logging/struct.LogEndpoint.html\n    pub fn log_endpoint(&self, handle: EndpointHandle) -> Result<&LogEndpoint, HandleError> {\n        self.log_endpoints\n            .get(handle)\n            .ok_or(HandleError::InvalidEndpointHandle(handle))\n    }\n\n    // ----- ACLs API -----\n\n    pub fn acl_handle_by_name(&mut self, name: &str) -> Option<AclHandle> {\n        let acl = self.ctx.acls().get_acl(name)?;\n        Some(self.acl_handles.push(acl.clone()))\n    }\n\n    pub fn acl_by_handle(&self, handle: AclHandle) -> Option<Arc<Acl>> {\n        self.acl_handles.get(handle).map(Arc::clone)\n    }\n\n    // ----- Backends API -----\n\n    /// Get the collection of static backends.\n    pub fn backends(&self) -> &Backends {\n        self.ctx.backends()\n    }\n\n    /// Look up a backend by name.\n    pub fn backend(&self, name: &str) -> Option<&Arc<Backend>> {\n        // it doesn't actually matter what order we do this search, because\n        // the namespaces should be unique.\n        self.backends()\n            .get(name)\n            .or_else(|| self.dynamic_backends.get(name))\n    }\n\n    /// Look up a dynamic backend (only) by name.\n    pub fn dynamic_backend(&self, name: &str) -> Option<&Arc<Backend>> {\n        self.dynamic_backends.get(name)\n    }\n\n    /// Return the full list of static and dynamic backend names as an [`Iterator`].\n    pub fn backend_names(&self) -> impl Iterator<Item = &String> {\n        self.backends().keys().chain(self.dynamic_backends.keys())\n    }\n\n    /// Try to add a backend with the given name prefix to our set of current backends.\n    /// Upon success, return true. If the name already exists somewhere, return false;\n    /// the caller should signal an appropriate error.\n    pub fn add_backend(&mut self, name: &str, info: Backend) -> bool {\n        // if this name already exists, either as a built in or dynamic backend, say no\n        if self.backends().contains_key(name) || self.dynamic_backends.contains_key(name) {\n            return false;\n        }\n\n        self.dynamic_backends\n            .insert(name.to_string(), Arc::new(info));\n\n        true\n    }\n\n    // ----- TLS config -----\n\n    /// Access the TLS configuration.\n    pub fn tls_config(&self) -> &TlsConfig {\n        self.ctx.tls_config()\n    }\n\n    // ----- Device Detection API -----\n\n    pub fn device_detection_lookup(&self, user_agent: &str) -> Option<String> {\n        self.ctx\n            .device_detection()\n            .lookup(user_agent)\n            .map(|data| data.to_string())\n    }\n\n    // ----- Dictionaries API -----\n\n    /// Look up a dictionary-handle by name.\n    pub fn dictionary_handle(&mut self, name: &str) -> Result<DictionaryHandle, Error> {\n        if let Some(dict) = self.dictionaries().get(name) {\n            let loaded = dict.load().map_err(|err| Error::Other(err.into()))?;\n            Ok(self.loaded_dictionaries.push(loaded))\n        } else {\n            Err(Error::DictionaryError(\n                crate::wiggle_abi::DictionaryError::UnknownDictionary(name.to_owned()),\n            ))\n        }\n    }\n\n    /// Look up a dictionary by dictionary-handle.\n    pub fn dictionary(&self, handle: DictionaryHandle) -> Result<&LoadedDictionary, HandleError> {\n        self.loaded_dictionaries\n            .get(handle)\n            .ok_or(HandleError::InvalidDictionaryHandle(handle))\n    }\n\n    /// Access the dictionary map.\n    pub fn dictionaries(&self) -> &Dictionaries {\n        self.ctx.dictionaries()\n    }\n\n    // ----- Geolocation API -----\n\n    pub fn geolocation_lookup(&self, addr: &IpAddr) -> Option<String> {\n        self.ctx\n            .geolocation()\n            .lookup(addr)\n            .map(|data| data.to_string())\n    }\n\n    // ----- NGWAF Inspect API -----\n\n    /// Retrieve the compliance region that received the request for this sandbox.\n    pub fn ngwaf_response(&self) -> String {\n        format!(\n            r#\"{{\"waf_response\":200,\"redirect_url\":\"\",\"tags\":[],\"verdict\":\"{}\",\"decision_ms\":0}}\"#,\n            self.ngwaf_verdict\n        )\n    }\n\n    // ----- KV Store API -----\n\n    pub fn kv_store(&self) -> &ObjectStores {\n        self.ctx.object_store()\n    }\n\n    pub fn kv_store_handle(&mut self, key: &str) -> KvStoreHandle {\n        let obj_key = ObjectStoreKey::new(key);\n        self.kv_store_by_name.push(obj_key)\n    }\n\n    pub fn get_kv_store_key(&self, handle: KvStoreHandle) -> Option<&ObjectStoreKey> {\n        self.kv_store_by_name.get(handle)\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub fn kv_insert(\n        &self,\n        obj_store_key: ObjectStoreKey,\n        obj_key: ObjectKey,\n        obj: Vec<u8>,\n        mode: Option<KvInsertMode>,\n        generation: Option<u64>,\n        metadata: Option<String>,\n        ttl: Option<Duration>,\n    ) -> Result<(), KvStoreError> {\n        let mode = match mode {\n            None => KvInsertMode::Overwrite,\n            Some(m) => m,\n        };\n\n        self.kv_store()\n            .insert(obj_store_key, obj_key, obj, mode, generation, metadata, ttl)\n    }\n\n    /// Insert a [`PendingKvInsert`] into the sandbox.\n    ///\n    /// This method returns a new [`PendingKvInsertHandle`], which can then be used to access\n    /// and mutate the pending insert.\n    pub fn insert_pending_kv_insert(\n        &mut self,\n        pending: PendingKvInsertTask,\n    ) -> KvStoreInsertHandle {\n        self.async_items\n            .push(Some(AsyncItem::PendingKvInsert(pending)))\n            .into()\n    }\n\n    /// Take ownership of a [`PendingKvInsert`], given its [`PendingKvInsertHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a pending insert in the\n    /// sandbox.\n    pub fn take_pending_kv_insert(\n        &mut self,\n        handle: PendingKvInsertHandle,\n    ) -> Result<PendingKvInsertTask, HandleError> {\n        // check that this is a pending request before removing it\n        let _ = self.pending_kv_insert(handle)?;\n\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .and_then(AsyncItem::into_pending_kv_insert)\n            .ok_or(HandleError::InvalidPendingKvInsertHandle(handle))\n    }\n\n    /// Get a reference to a [`PendingInsert`], given its [`PendingKvInsertHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with an insert in the\n    /// sandbox.\n    pub fn pending_kv_insert(\n        &self,\n        handle: PendingKvInsertHandle,\n    ) -> Result<&PendingKvInsertTask, HandleError> {\n        self.async_items\n            .get(handle.into())\n            .and_then(Option::as_ref)\n            .and_then(AsyncItem::as_pending_kv_insert)\n            .ok_or(HandleError::InvalidPendingKvInsertHandle(handle))\n    }\n\n    pub fn kv_delete(\n        &self,\n        obj_store_key: ObjectStoreKey,\n        obj_key: ObjectKey,\n    ) -> Result<bool, KvStoreError> {\n        self.kv_store().delete(obj_store_key, obj_key)\n    }\n\n    /// Insert a [`PendingKvDelete`] into the sandbox.\n    ///\n    /// This method returns a new [`PendingKvDeleteHandle`], which can then be used to access\n    /// and mutate the pending delete.\n    pub fn insert_pending_kv_delete(\n        &mut self,\n        pending: PendingKvDeleteTask,\n    ) -> PendingKvDeleteHandle {\n        self.async_items\n            .push(Some(AsyncItem::PendingKvDelete(pending)))\n            .into()\n    }\n\n    /// Take ownership of a [`PendingKvDelete`], given its [`PendingKvDeleteHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a pending delete in the\n    /// sandbox.\n    pub fn take_pending_kv_delete(\n        &mut self,\n        handle: PendingKvDeleteHandle,\n    ) -> Result<PendingKvDeleteTask, HandleError> {\n        // check that this is a pending request before removing it\n        let _ = self.pending_kv_delete(handle)?;\n\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .and_then(AsyncItem::into_pending_kv_delete)\n            .ok_or(HandleError::InvalidPendingKvDeleteHandle(handle))\n    }\n\n    /// Get a reference to a [`PendingDelete`], given its [`PendingKvDeleteHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a delete in the\n    /// sandbox.\n    pub fn pending_kv_delete(\n        &self,\n        handle: PendingKvDeleteHandle,\n    ) -> Result<&PendingKvDeleteTask, HandleError> {\n        self.async_items\n            .get(handle.into())\n            .and_then(Option::as_ref)\n            .and_then(AsyncItem::as_pending_kv_delete)\n            .ok_or(HandleError::InvalidPendingKvDeleteHandle(handle))\n    }\n\n    pub fn obj_lookup(\n        &self,\n        obj_store_key: ObjectStoreKey,\n        obj_key: ObjectKey,\n    ) -> Result<Option<ObjectValue>, KvStoreError> {\n        self.kv_store().lookup(obj_store_key, obj_key)\n    }\n\n    /// Insert a [`PendingLookup`] into the sandbox.\n    ///\n    /// This method returns a new [`PendingKvLookupHandle`], which can then be used to access\n    /// and mutate the pending lookup.\n    pub fn insert_pending_kv_lookup(\n        &mut self,\n        pending: PendingKvLookupTask,\n    ) -> PendingKvLookupHandle {\n        self.async_items\n            .push(Some(AsyncItem::PendingKvLookup(pending)))\n            .into()\n    }\n\n    /// Take ownership of a [`PendingLookup`], given its [`PendingKvLookupHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a pending lookup in the\n    /// sandbox.\n    pub fn take_pending_kv_lookup(\n        &mut self,\n        handle: PendingKvLookupHandle,\n    ) -> Result<PendingKvLookupTask, HandleError> {\n        // check that this is a pending request before removing it\n        let _ = self.pending_kv_lookup(handle)?;\n\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .and_then(AsyncItem::into_pending_kv_lookup)\n            .ok_or(HandleError::InvalidPendingKvLookupHandle(handle))\n    }\n\n    /// Get a reference to a [`PendingLookup`], given its [`PendingKvLookupHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a lookup in the\n    /// sandbox.\n    pub fn pending_kv_lookup(\n        &self,\n        handle: PendingKvLookupHandle,\n    ) -> Result<&PendingKvLookupTask, HandleError> {\n        self.async_items\n            .get(handle.into())\n            .and_then(Option::as_ref)\n            .and_then(AsyncItem::as_pending_kv_lookup)\n            .ok_or(HandleError::InvalidPendingKvLookupHandle(handle))\n    }\n\n    pub fn kv_list(\n        &self,\n        obj_store_key: ObjectStoreKey,\n        cursor: Option<String>,\n        prefix: Option<String>,\n        limit: Option<u32>,\n    ) -> Result<Vec<u8>, KvStoreError> {\n        let limit = limit.unwrap_or(1000);\n\n        self.kv_store().list(obj_store_key, cursor, prefix, limit)\n    }\n\n    /// Insert a [`PendingList`] into the sandbox.\n    ///\n    /// This method returns a new [`PendingKvListHandle`], which can then be used to access\n    /// and mutate the pending list.\n    pub fn insert_pending_kv_list(&mut self, pending: PendingKvListTask) -> PendingKvListHandle {\n        self.async_items\n            .push(Some(AsyncItem::PendingKvList(pending)))\n            .into()\n    }\n\n    /// Take ownership of a [`PendingList`], given its [`PendingKvListHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a pending list in the\n    /// sandbox.\n    pub fn take_pending_kv_list(\n        &mut self,\n        handle: PendingKvListHandle,\n    ) -> Result<PendingKvListTask, HandleError> {\n        // check that this is a pending request before removing it\n        let _ = self.pending_kv_list(handle)?;\n\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .and_then(AsyncItem::into_pending_kv_list)\n            .ok_or(HandleError::InvalidPendingKvListHandle(handle))\n    }\n\n    /// Get a reference to a [`PendingList`], given its [`PendingKvListHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a list in the\n    /// sandbox.\n    pub fn pending_kv_list(\n        &self,\n        handle: PendingKvListHandle,\n    ) -> Result<&PendingKvListTask, HandleError> {\n        self.async_items\n            .get(handle.into())\n            .and_then(Option::as_ref)\n            .and_then(AsyncItem::as_pending_kv_list)\n            .ok_or(HandleError::InvalidPendingKvListHandle(handle))\n    }\n\n    // ----- Secret Store API -----\n\n    pub fn secret_store_handle(&mut self, name: &str) -> Option<SecretStoreHandle> {\n        self.secret_stores().get_store(name)?;\n        Some(self.secret_stores_by_name.push(name.to_string()))\n    }\n\n    pub fn secret_store_name(&self, handle: SecretStoreHandle) -> Option<String> {\n        self.secret_stores_by_name.get(handle).cloned()\n    }\n\n    pub fn secret_handle(&mut self, store_name: &str, secret_name: &str) -> Option<SecretHandle> {\n        self.secret_stores()\n            .get_store(store_name)?\n            .get_secret(secret_name)?;\n        Some(self.secrets_by_name.push(SecretLookup::Standard {\n            store_name: store_name.to_string(),\n            secret_name: secret_name.to_string(),\n        }))\n    }\n\n    pub fn secret_lookup(&self, handle: SecretHandle) -> Option<SecretLookup> {\n        self.secrets_by_name.get(handle).cloned()\n    }\n\n    pub fn add_secret(&mut self, plaintext: Vec<u8>) -> SecretHandle {\n        self.secrets_by_name\n            .push(SecretLookup::Injected { plaintext })\n    }\n\n    pub fn secret_stores(&self) -> &SecretStores {\n        self.ctx.secret_stores()\n    }\n\n    // ----- Pending Requests API -----\n\n    /// Insert a [`PendingRequest`] into the sandbox.\n    ///\n    /// This method returns a new [`PendingRequestHandle`], which can then be used to access\n    /// and mutate the pending request.\n    pub fn insert_pending_request(\n        &mut self,\n        pending: PeekableTask<Response<Body>>,\n    ) -> PendingRequestHandle {\n        self.async_items\n            .push(Some(AsyncItem::PendingReq(pending)))\n            .into()\n    }\n\n    /// Get a reference to a [`PendingRequest`], given its [`PendingRequestHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a request in the\n    /// sandbox.\n    pub fn pending_request(\n        &self,\n        handle: PendingRequestHandle,\n    ) -> Result<&PeekableTask<Response<Body>>, HandleError> {\n        self.async_items\n            .get(handle.into())\n            .and_then(Option::as_ref)\n            .and_then(AsyncItem::as_pending_req)\n            .ok_or(HandleError::InvalidPendingRequestHandle(handle))\n    }\n\n    /// Get a mutable reference to a [`PendingRequest`], given its [`PendingRequestHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a request in the\n    /// sandbox.\n    pub fn pending_request_mut(\n        &mut self,\n        handle: PendingRequestHandle,\n    ) -> Result<&mut PeekableTask<Response<Body>>, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::as_mut)\n            .and_then(AsyncItem::as_pending_req_mut)\n            .ok_or(HandleError::InvalidPendingRequestHandle(handle))\n    }\n\n    /// Take ownership of a [`PendingRequest`], given its [`PendingRequestHandle`].\n    ///\n    /// Returns a [`HandleError`] if the handle is not associated with a pending request in the\n    /// sandbox.\n    pub fn take_pending_request(\n        &mut self,\n        handle: PendingRequestHandle,\n    ) -> Result<PeekableTask<Response<Body>>, HandleError> {\n        // check that this is a pending request before removing it\n        let _ = self.pending_request(handle)?;\n\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .and_then(AsyncItem::into_pending_req)\n            .ok_or(HandleError::InvalidPendingRequestHandle(handle))\n    }\n\n    pub fn reinsert_pending_request(\n        &mut self,\n        handle: PendingRequestHandle,\n        pending_req: PeekableTask<Response<Body>>,\n    ) -> Result<(), HandleError> {\n        *self\n            .async_items\n            .get_mut(handle.into())\n            .ok_or(HandleError::InvalidPendingRequestHandle(handle))? =\n            Some(AsyncItem::PendingReq(pending_req));\n        Ok(())\n    }\n\n    // ------- Core Cache API ------\n\n    /// Insert a pending cache operation: CacheHandle or CacheBusyHandle\n    pub fn insert_cache_op(&mut self, task: PendingCacheTask) -> AsyncItemHandle {\n        self.async_items.push(Some(AsyncItem::PendingCache(task)))\n    }\n\n    /// Get mutable access to a cache entry, which may require blocking until the entry is\n    /// available.\n    pub(crate) async fn cache_entry_mut(\n        &mut self,\n        handle: CacheHandle,\n    ) -> Result<&mut CacheEntry, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::as_mut)\n            .and_then(AsyncItem::as_pending_cache_mut)\n            .map(PendingCacheTask::as_mut)\n            .ok_or(HandleError::InvalidCacheHandle(handle))?\n            .await\n            .as_mut()\n            .map_err(|e| {\n                tracing::error!(\"in completion of cache lookup: {e}\");\n                HandleError::InvalidCacheHandle(handle)\n            })\n    }\n\n    /// Get immutable access to a cache entry, which may require blocking until the entry is\n    /// available.\n    pub(crate) async fn cache_entry(\n        &mut self,\n        handle: CacheHandle,\n    ) -> Result<&CacheEntry, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::as_mut)\n            .and_then(AsyncItem::as_pending_cache_mut)\n            .map(PendingCacheTask::as_mut)\n            .ok_or(HandleError::InvalidCacheHandle(handle))?\n            .await\n            .as_ref()\n            .map_err(|e| {\n                tracing::error!(\"in completion of cache lookup: {e}\");\n                HandleError::InvalidCacheHandle(handle)\n            })\n    }\n\n    /// Take ownership of a `CacheEntry` given its handle.\n    ///\n    /// Returns a `HandleError` if the handle is not associated with a cache lookup.\n    pub(crate) fn take_cache_entry(\n        &mut self,\n        handle: CacheHandle,\n    ) -> Result<PendingCacheTask, HandleError> {\n        self.async_items\n            .get_mut(handle.into())\n            .and_then(Option::take)\n            .and_then(AsyncItem::into_pending_cache)\n            .ok_or(HandleError::InvalidCacheHandle(handle))\n    }\n\n    /// Access the cache.\n    pub fn cache(&self) -> &Arc<Cache> {\n        self.ctx.cache()\n    }\n\n    // -------- Scheduling APIs ----------\n\n    /// Take ownership of multiple AsyncItems in preparation for a `select`.\n    ///\n    /// Returns a [`HandleError`] if any of the handles are not associated with a pending\n    /// request in the sandbox.\n    pub fn prepare_select_targets(\n        &mut self,\n        handles: impl IntoIterator<Item = AsyncItemHandle>,\n    ) -> Result<Vec<SelectTarget>, HandleError> {\n        // Prepare a vector of targets from the given handles; if any of the handles are invalid,\n        // put back all the targets we've extracted so far\n        let mut targets = vec![];\n        for handle in handles {\n            match self.take_async_item(handle) {\n                Ok(item) => {\n                    targets.push(SelectTarget { handle, item });\n                }\n                _ => {\n                    self.reinsert_select_targets(targets);\n                    return Err(HandleError::InvalidPendingRequestHandle(handle.into()));\n                }\n            }\n        }\n        Ok(targets)\n    }\n\n    /// Put the given vector of `select` targets back into the pending request table, using the handles\n    /// stored within each [`SelectTarget`].\n    pub fn reinsert_select_targets(&mut self, targets: Vec<SelectTarget>) {\n        for target in targets {\n            self.reinsert_async_handle(target.handle, target.item);\n        }\n    }\n\n    pub fn reinsert_async_handle(&mut self, handle: AsyncItemHandle, item: AsyncItem) {\n        // Invalid handle, reinsert the item.\n        debug_assert!(self.async_items[handle].is_none());\n        self.async_items[handle] = Some(item);\n    }\n\n    pub fn new_ready(&mut self) -> AsyncItemHandle {\n        self.async_items.push(Some(AsyncItem::Ready))\n    }\n\n    /// Returns the unique identifier for the current sandbox.\n    ///\n    /// While this corresponds to the request ID for the initial request that spawned\n    /// the sandbox, subsequent downstream requests received by the sandbox will have\n    /// their own unique identifier. Care should be taken to not conflate the two, and\n    /// to use [Sandbox::downstream_request_id] whenever a request needs to be identified.\n    pub fn sandbox_id(&self) -> u64 {\n        self.sandbox_id\n    }\n\n    /// Access the path to the configuration file for this invocation.\n    pub fn config_path(&self) -> Option<&Path> {\n        self.ctx.config_path()\n    }\n\n    pub fn async_item_mut(\n        &mut self,\n        handle: AsyncItemHandle,\n    ) -> Result<&mut AsyncItem, HandleError> {\n        match self.async_items.get_mut(handle).and_then(|ai| ai.as_mut()) {\n            Some(item) => Ok(item),\n            None => Err(HandleError::InvalidAsyncItemHandle(handle.into()))?,\n        }\n    }\n\n    pub fn take_async_item(&mut self, handle: AsyncItemHandle) -> Result<AsyncItem, HandleError> {\n        // check that this is an async item before removing it\n        let _ = self.async_item_mut(handle)?;\n\n        let item = self\n            .async_items\n            .get_mut(handle)\n            .and_then(|tracked| tracked.take())\n            .ok_or(HandleError::InvalidAsyncItemHandle(handle.into()))?;\n\n        // We just took the handle out of the table, so if it was \"the\"\n        // downstream pending handle, it no longer is.\n        if let AsyncItem::PendingDownstream(_) = item {\n            self.downstream_pending_handle = None;\n        }\n\n        Ok(item)\n    }\n\n    pub async fn select_impl(\n        &mut self,\n        handles: impl IntoIterator<Item = AsyncItemHandle>,\n    ) -> Result<usize, Error> {\n        // we have to temporarily move the async items out of the sandbox table,\n        // because we need &mut borrows of all of them simultaneously.\n        let targets = self.prepare_select_targets(handles)?;\n        let mut selected = SelectedTargets::new(self, targets);\n        let done_index = selected.future().await;\n\n        Ok(done_index)\n    }\n\n    pub fn shielding_sites(&self) -> &ShieldingSites {\n        self.ctx.shielding_sites()\n    }\n\n    pub fn fake_valid_fastly_keys(&self) -> &crate::config::FakeValidFastlyKeys {\n        self.ctx.fake_valid_fastly_keys()\n    }\n\n    /// Check if a Fastly API key in the request is valid.\n    ///\n    /// Returns `true` if the request contains a `fastly-key` header whose value\n    /// matches one of the configured fake valid keys, `false` otherwise.\n    pub fn check_fastly_key(&self, handle: RequestHandle) -> Result<bool, Error> {\n        let fake_valid_fastly_keys = self.fake_valid_fastly_keys();\n        let parts = self.request_parts(handle)?;\n        let is_valid = parts\n            .headers\n            .get(\"fastly-key\")\n            .and_then(|v| v.to_str().ok())\n            .is_some_and(|key| fake_valid_fastly_keys.contains(key));\n        Ok(is_valid)\n    }\n\n    pub async fn register_pending_downstream_req(\n        &mut self,\n        timeout: Option<Duration>,\n    ) -> Result<AsyncItemHandle, Error> {\n        if self.downstream_pending_handle.is_some() {\n            return Err(Error::LimitExceeded {\n                msg: \"Too many pending downstream request handles have been created\",\n            });\n        }\n\n        let rx = if self.next_req_accepted < NEXT_REQ_ACCEPT_MAX {\n            self.ctx.register_pending_downstream().await\n        } else {\n            None\n        };\n\n        if rx.is_none() {\n            self.next_req_accepted = NEXT_REQ_ACCEPT_MAX;\n        } else {\n            self.next_req_accepted += 1;\n        }\n\n        let timeout = timeout.unwrap_or(NEXT_REQ_TIMEOUT).min(NEXT_REQ_TIMEOUT);\n        let task = PendingDownstreamReqTask::new(rx, timeout);\n        let handle = self.async_items.push(Some(AsyncItem::from(task)));\n        self.downstream_pending_handle = Some(handle);\n\n        Ok(handle)\n    }\n\n    pub fn take_pending_downstream_req(\n        &mut self,\n        handle: AsyncItemHandle,\n    ) -> Result<PendingDownstreamReqTask, HandleError> {\n        let task = self\n            .async_items\n            .get_mut(handle)\n            .and_then(|maybe_item| {\n                if maybe_item\n                    .as_mut()\n                    .and_then(AsyncItem::as_pending_downstream_req_mut)\n                    .is_some()\n                {\n                    maybe_item\n                        .take()\n                        .and_then(AsyncItem::into_pending_downstream_req)\n                } else {\n                    None\n                }\n            })\n            .ok_or_else(|| HandleError::InvalidPendingDownstreamHandle(handle.into()))?;\n\n        self.downstream_pending_handle = None;\n\n        Ok(task)\n    }\n\n    /// Wait for a [PendingDownstreamReqTask] to finish, then fetch its request and body handles.\n    pub async fn await_downstream_req(\n        &mut self,\n        handle: AsyncItemHandle,\n    ) -> Result<Option<(RequestHandle, BodyHandle)>, Error> {\n        if self.downstream_resp.is_unsent() {\n            return Err(Error::Unsupported {\n                msg: \"cannot accept requests w/o handling the outstanding one\",\n            });\n        }\n\n        let item = self.take_pending_downstream_req(handle)?;\n        let Some(downstream) = item.recv().await? else {\n            return Ok(None);\n        };\n\n        let (parts, body) = downstream.req.into_parts();\n        let body_handle = self.async_items.push(Some(AsyncItem::Body(body)));\n        let req_handle = self.req_parts.push(RequestParts {\n            parts: Some(parts),\n            metadata: Some(downstream.metadata),\n        });\n\n        self.downstream_resp = DownstreamResponseState::new(downstream.sender);\n        self.downstream_req_handle = req_handle;\n        self.downstream_req_body_handle = body_handle.into();\n\n        Ok(Some((req_handle, body_handle.into())))\n    }\n\n    pub fn abandon_pending_downstream_req(\n        &mut self,\n        handle: AsyncItemHandle,\n    ) -> Result<(), HandleError> {\n        self.take_pending_downstream_req(handle).map(|_| ())\n    }\n\n    pub fn ctx(&self) -> &Arc<ExecuteCtx> {\n        &self.ctx\n    }\n\n    /// Get the guest's heap usage in mebibytes.\n    ///\n    /// This rounds up to the nearest mebibyte, so that guests won't accidentally\n    /// rely on implementation details that may change over time.\n    pub fn get_heap_usage_mib(&self) -> u32 {\n        const MEBIBYTE: usize = 1024 * 1024;\n        let mb = self.limiter.memory_allocated.div_ceil(MEBIBYTE);\n        mb.try_into().unwrap_or(u32::MAX)\n    }\n\n    pub fn limiter(&self) -> &Limiter {\n        &self.limiter\n    }\n\n    pub fn limiter_mut(&mut self) -> &mut Limiter {\n        &mut self.limiter\n    }\n}\n\npub struct SelectedTargets<'sandbox> {\n    sandbox: &'sandbox mut Sandbox,\n    targets: Vec<SelectTarget>,\n}\n\nimpl<'sandbox> SelectedTargets<'sandbox> {\n    fn new(sandbox: &'sandbox mut Sandbox, targets: Vec<SelectTarget>) -> Self {\n        Self { sandbox, targets }\n    }\n\n    fn future(&mut self) -> Box<dyn Future<Output = usize> + Unpin + Send + Sync + '_> {\n        // for each target, we produce a future for checking on the \"readiness\"\n        // of the associated primary I/O operation\n        let mut futures = Vec::new();\n        for target in &mut *self.targets {\n            futures.push(Box::pin(target.item.await_ready()))\n        }\n        if futures.is_empty() {\n            // if there are no futures, we wait forever; this waiting will always be bounded by a timeout,\n            // since the `select` hostcall requires a timeout when no handles are given.\n            Box::new(future::pending())\n        } else {\n            Box::new(future::select_all(futures).map(|f| f.1))\n        }\n    }\n}\n\nimpl<'sandbox> Drop for SelectedTargets<'sandbox> {\n    fn drop(&mut self) {\n        let targets = std::mem::take(&mut self.targets);\n        self.sandbox.reinsert_select_targets(targets);\n    }\n}\n\n/// Additional Viceroy-specific metadata for requests.\n#[derive(Clone, Debug)]\npub struct ViceroyRequestMetadata {\n    pub auto_decompress_encodings: ContentEncodings,\n    pub framing_headers_mode: FramingHeadersMode,\n}\n\nimpl Default for ViceroyRequestMetadata {\n    fn default() -> Self {\n        ViceroyRequestMetadata {\n            auto_decompress_encodings: ContentEncodings::empty(),\n            framing_headers_mode: FramingHeadersMode::Automatic,\n        }\n    }\n}\n\n/// Additional Viceroy-specific metadata for responses.\n#[derive(Clone, Debug)]\npub struct ViceroyResponseMetadata {\n    pub framing_headers_mode: FramingHeadersMode,\n}\n\nimpl Default for ViceroyResponseMetadata {\n    fn default() -> Self {\n        ViceroyResponseMetadata {\n            framing_headers_mode: FramingHeadersMode::Automatic,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Eq, Hash, PartialEq)]\n#[repr(transparent)]\npub struct AsyncItemHandle(u32);\n\nentity_impl!(AsyncItemHandle, \"async_item\");\n\n// The ABI uses distinct entity types for each kind of async item because most host calls operate on\n// only one type at a type. But the underlying tables for all async items are combined, so the handles\n// are interchangeable. Keeping them as separate types helps ensure intentional view shifts between\n// them, using `.into()`.\n\nimpl From<BodyHandle> for AsyncItemHandle {\n    fn from(h: BodyHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for BodyHandle {\n    fn from(h: AsyncItemHandle) -> BodyHandle {\n        BodyHandle::from(h.as_u32())\n    }\n}\n\nimpl From<PendingRequestHandle> for AsyncItemHandle {\n    fn from(h: PendingRequestHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for PendingRequestHandle {\n    fn from(h: AsyncItemHandle) -> PendingRequestHandle {\n        PendingRequestHandle::from(h.as_u32())\n    }\n}\n\nimpl From<types::AsyncItemHandle> for AsyncItemHandle {\n    fn from(h: types::AsyncItemHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for types::AsyncItemHandle {\n    fn from(h: AsyncItemHandle) -> types::AsyncItemHandle {\n        types::AsyncItemHandle::from(h.as_u32())\n    }\n}\n\nimpl From<PendingKvLookupHandle> for AsyncItemHandle {\n    fn from(h: PendingKvLookupHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for PendingKvLookupHandle {\n    fn from(h: AsyncItemHandle) -> PendingKvLookupHandle {\n        PendingKvLookupHandle::from(h.as_u32())\n    }\n}\n\nimpl From<PendingKvInsertHandle> for AsyncItemHandle {\n    fn from(h: PendingKvInsertHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for PendingKvInsertHandle {\n    fn from(h: AsyncItemHandle) -> PendingKvInsertHandle {\n        PendingKvInsertHandle::from(h.as_u32())\n    }\n}\n\nimpl From<PendingKvDeleteHandle> for AsyncItemHandle {\n    fn from(h: PendingKvDeleteHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for PendingKvDeleteHandle {\n    fn from(h: AsyncItemHandle) -> PendingKvDeleteHandle {\n        PendingKvDeleteHandle::from(h.as_u32())\n    }\n}\n\nimpl From<PendingKvListHandle> for AsyncItemHandle {\n    fn from(h: PendingKvListHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for PendingKvListHandle {\n    fn from(h: AsyncItemHandle) -> PendingKvListHandle {\n        PendingKvListHandle::from(h.as_u32())\n    }\n}\n\nimpl From<KvStoreLookupHandle> for AsyncItemHandle {\n    fn from(h: KvStoreLookupHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for KvStoreLookupHandle {\n    fn from(h: AsyncItemHandle) -> KvStoreLookupHandle {\n        KvStoreLookupHandle::from(h.as_u32())\n    }\n}\n\nimpl From<KvStoreInsertHandle> for AsyncItemHandle {\n    fn from(h: KvStoreInsertHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for KvStoreInsertHandle {\n    fn from(h: AsyncItemHandle) -> KvStoreInsertHandle {\n        KvStoreInsertHandle::from(h.as_u32())\n    }\n}\n\nimpl From<KvStoreDeleteHandle> for AsyncItemHandle {\n    fn from(h: KvStoreDeleteHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for KvStoreDeleteHandle {\n    fn from(h: AsyncItemHandle) -> KvStoreDeleteHandle {\n        KvStoreDeleteHandle::from(h.as_u32())\n    }\n}\n\nimpl From<KvStoreListHandle> for AsyncItemHandle {\n    fn from(h: KvStoreListHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for KvStoreListHandle {\n    fn from(h: AsyncItemHandle) -> KvStoreListHandle {\n        KvStoreListHandle::from(h.as_u32())\n    }\n}\n\nimpl From<AsyncItemHandle> for CacheHandle {\n    fn from(h: AsyncItemHandle) -> CacheHandle {\n        CacheHandle::from(h.as_u32())\n    }\n}\n\nimpl From<CacheHandle> for AsyncItemHandle {\n    fn from(h: CacheHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for CacheBusyHandle {\n    fn from(h: AsyncItemHandle) -> CacheBusyHandle {\n        CacheBusyHandle::from(h.as_u32())\n    }\n}\n\nimpl From<CacheBusyHandle> for AsyncItemHandle {\n    fn from(h: CacheBusyHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\nimpl From<AsyncItemHandle> for RequestPromiseHandle {\n    fn from(h: AsyncItemHandle) -> RequestPromiseHandle {\n        RequestPromiseHandle::from(h.as_u32())\n    }\n}\n\nimpl From<RequestPromiseHandle> for AsyncItemHandle {\n    fn from(h: RequestPromiseHandle) -> AsyncItemHandle {\n        AsyncItemHandle::from_u32(h.into())\n    }\n}\n\n// CacheBusyHandle and CacheHandle are equivalent; CacheHandle is just a \"later\" resolution.\nimpl From<CacheBusyHandle> for CacheHandle {\n    fn from(h: CacheBusyHandle) -> CacheHandle {\n        let raw: u32 = h.into();\n        CacheHandle::from(raw)\n    }\n}\n"
  },
  {
    "path": "src/secret_store.rs",
    "content": "use {bytes::Bytes, std::collections::HashMap};\n\n#[derive(Clone, Debug, Default)]\npub struct SecretStores {\n    stores: HashMap<String, SecretStore>,\n}\n\nimpl SecretStores {\n    pub fn new() -> Self {\n        Self {\n            stores: HashMap::new(),\n        }\n    }\n\n    pub fn get_store(&self, name: &str) -> Option<&SecretStore> {\n        self.stores.get(name)\n    }\n\n    pub fn add_store(&mut self, name: String, store: SecretStore) {\n        self.stores.insert(name, store);\n    }\n}\n\n#[derive(Clone, Debug, Default)]\npub struct SecretStore {\n    secrets: HashMap<String, Secret>,\n}\n\nimpl SecretStore {\n    pub fn new() -> Self {\n        Self {\n            secrets: HashMap::new(),\n        }\n    }\n\n    pub fn get_secret(&self, name: &str) -> Option<&Secret> {\n        self.secrets.get(name)\n    }\n\n    pub fn add_secret(&mut self, name: String, secret: Bytes) {\n        self.secrets.insert(name, Secret { plaintext: secret });\n    }\n}\n\n#[derive(Clone, Debug, Default)]\npub struct Secret {\n    plaintext: Bytes,\n}\n\nimpl Secret {\n    pub fn plaintext(&self) -> &[u8] {\n        &self.plaintext\n    }\n}\n\n#[derive(Clone, Debug)]\npub enum SecretLookup {\n    Standard {\n        store_name: String,\n        secret_name: String,\n    },\n    Injected {\n        plaintext: Vec<u8>,\n    },\n}\n"
  },
  {
    "path": "src/service.rs",
    "content": "//! Service types.\n\nuse {\n    crate::{Error, body::Body, execute::ExecuteCtx},\n    futures::future::{self, Ready},\n    hyper::{\n        http::{Request, Response},\n        server::conn::AddrStream,\n        service::Service,\n    },\n    std::{\n        convert::Infallible,\n        future::Future,\n        net::SocketAddr,\n        pin::Pin,\n        sync::Arc,\n        task::{self, Poll},\n    },\n    tracing::{Level, event},\n};\n\n/// A Viceroy service uses a Wasm module and a handler function to respond to HTTP requests.\n///\n/// This service type is used to compile a Wasm [`Module`][mod], and perform the actions necessary\n/// to initialize a [`Server`][serv] and bind the service to a local port.\n///\n/// Each time a connection is received, a [`RequestService`][req-svc] will be created, to\n/// instantiate the module and return a [`Response`][resp].\n///\n/// [mod]: https://docs.rs/wasmtime/latest/wasmtime/struct.Module.html\n/// [req-svc]: struct.RequestService.html\n/// [resp]: https://docs.rs/http/latest/http/response/struct.Response.html\n/// [serv]: https://docs.rs/hyper/latest/hyper/server/struct.Server.html\npub struct ViceroyService {\n    ctx: Arc<ExecuteCtx>,\n}\n\nimpl ViceroyService {\n    /// Create a new Viceroy service, using the given handler function and module path.\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use std::collections::HashSet;\n    /// # use wasmtime::WasmFeatures;\n    /// use viceroy_lib::{Error, ExecuteCtx, ProfilingStrategy, ViceroyService};\n    /// # fn f() -> Result<(), Error> {\n    /// let adapt_core_wasm = false;\n    /// let wasm_features = WasmFeatures::default();\n    /// let ctx = ExecuteCtx::new(\n    ///     \"path/to/a/file.wasm\",\n    ///     ProfilingStrategy::None,\n    ///     HashSet::new(),\n    ///     None,\n    ///     Default::default(),\n    ///     adapt_core_wasm,\n    ///     wasm_features\n    /// )?;\n    /// let svc = ViceroyService::new(ctx);\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub fn new(ctx: Arc<ExecuteCtx>) -> Self {\n        Self { ctx }\n    }\n\n    /// An internal helper, create a [`RequestService`](struct.RequestService.html).\n    fn make_service(&self, remote: &AddrStream) -> RequestService {\n        RequestService::new(self.ctx.clone(), remote)\n    }\n\n    /// Bind this service to the given address and start serving responses.\n    ///\n    /// This will consume the service, using it to start a server that will execute the given module\n    /// each time a new request is sent. This function will only return if an error occurs.\n    // FIXME KTM 2020-06-22: Once `!` is stabilized, this should be `Result<!, hyper::Error>`.\n    pub async fn serve(self, addr: SocketAddr) -> Result<(), hyper::Error> {\n        let server = hyper::Server::bind(&addr).serve(self);\n        event!(Level::INFO, \"Listening on http://{}\", server.local_addr());\n        server.await?;\n        Ok(())\n    }\n}\n\nimpl<'addr> Service<&'addr AddrStream> for ViceroyService {\n    type Response = RequestService;\n    type Error = Infallible;\n    type Future = Ready<Result<Self::Response, Self::Error>>;\n\n    fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {\n        Poll::Ready(Ok(()))\n    }\n\n    fn call(&mut self, addr: &'addr AddrStream) -> Self::Future {\n        future::ok(self.make_service(addr))\n    }\n}\n\n/// A request service is responsible for handling a single request.\n///\n/// Most importantly, this structure implements the [`tower::Service`][service] trait, which allows\n/// it to be dispatched by [`ViceroyService`][viceroy] to handle a single request.\n///\n/// This object does not need to be used directly; users most likely should use\n/// [`ViceroyService::serve`][serve] to bind a service to a port, or\n/// [`ExecuteCtx::handle_request`][handle_request] to generate a response for a request when writing\n/// test cases.\n///\n/// [handle_request]: ../execute/struct.ExecuteCtx.html#method.handle_request\n/// [serve]: struct.ViceroyService.html#method.serve\n/// [service]: https://docs.rs/tower/latest/tower/trait.Service.html\n/// [viceroy]: struct.ViceroyService.html\n#[derive(Clone)]\npub struct RequestService {\n    ctx: Arc<ExecuteCtx>,\n    local_addr: SocketAddr,\n    remote_addr: SocketAddr,\n}\n\nimpl RequestService {\n    /// Create a new request service.\n    fn new(ctx: Arc<ExecuteCtx>, addr: &AddrStream) -> Self {\n        let local_addr = addr.local_addr();\n        let remote_addr = addr.remote_addr();\n\n        Self {\n            ctx,\n            local_addr,\n            remote_addr,\n        }\n    }\n}\n\ntype ServiceFuture = dyn Future<Output = Result<Response<Body>, Error>> + Send;\n\nimpl Service<Request<hyper::Body>> for RequestService {\n    type Response = Response<Body>;\n    type Error = Error;\n    type Future = Pin<Box<ServiceFuture>>;\n\n    fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {\n        Poll::Ready(Ok(()))\n    }\n\n    /// Process the request and return the response asynchronously.\n    fn call(&mut self, req: Request<hyper::Body>) -> Self::Future {\n        // Request handling currently takes ownership of the context, which is cheaply cloneable.\n        let ctx = self.ctx.clone();\n        let local = self.local_addr;\n        let remote = self.remote_addr;\n\n        // Now, use the execution context to handle the request.\n        Box::pin(async move {\n            ctx.handle_request(req, local, remote)\n                .await\n                .map(|result| result.0)\n        })\n    }\n}\n"
  },
  {
    "path": "src/shielding_site.rs",
    "content": "use crate::error::{FastlyConfigError, ShieldingSiteConfigError};\nuse std::collections::HashMap;\nuse toml::Value;\nuse url::Url;\n\n/// This structure tracks all the possible shielding targets we might\n/// use during execution.\n///\n/// This map will be provided in its entirety from fastly.toml, and will\n/// also help us know what POP we're running on.\n#[derive(Clone, Debug)]\npub struct ShieldingSites {\n    sites: HashMap<String, ShieldingSite>,\n}\n\nimpl Default for ShieldingSites {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl TryFrom<toml::value::Map<String, Value>> for ShieldingSites {\n    type Error = FastlyConfigError;\n\n    fn try_from(value: toml::value::Map<String, Value>) -> Result<Self, Self::Error> {\n        let mut result = ShieldingSites::new();\n\n        for (site_name, information) in value.into_iter() {\n            match information {\n                Value::String(value) => {\n                    if value.to_lowercase().as_str() != \"local\" {\n                        return Err(FastlyConfigError::InvalidShieldingSiteDefinition {\n                            name: site_name,\n                            err: ShieldingSiteConfigError::IllegalSiteString,\n                        });\n                    }\n\n                    result = result.with_local(site_name);\n                }\n\n                Value::Table(table) => {\n                    if table.len() != 2 {\n                        return Err(FastlyConfigError::InvalidShieldingSiteDefinition {\n                            name: site_name,\n                            err: ShieldingSiteConfigError::IllegalSiteDefinition,\n                        });\n                    }\n\n                    let Some(Value::String(encrypted_str)) = table.get(\"encrypted\") else {\n                        return Err(FastlyConfigError::InvalidShieldingSiteDefinition {\n                            name: site_name,\n                            err: ShieldingSiteConfigError::IllegalSiteDefinition,\n                        });\n                    };\n\n                    let Some(Value::String(unencrypted_str)) = table.get(\"unencrypted\") else {\n                        return Err(FastlyConfigError::InvalidShieldingSiteDefinition {\n                            name: site_name,\n                            err: ShieldingSiteConfigError::IllegalSiteDefinition,\n                        });\n                    };\n\n                    let encrypted = Url::parse(encrypted_str).map_err(|error| {\n                        FastlyConfigError::InvalidShieldingSiteDefinition {\n                            name: site_name.clone(),\n                            err: ShieldingSiteConfigError::IllegalUrl {\n                                url: encrypted_str.to_string(),\n                                error,\n                            },\n                        }\n                    })?;\n\n                    let unencrypted = Url::parse(unencrypted_str).map_err(|error| {\n                        FastlyConfigError::InvalidShieldingSiteDefinition {\n                            name: site_name.clone(),\n                            err: ShieldingSiteConfigError::IllegalUrl {\n                                url: encrypted_str.to_string(),\n                                error,\n                            },\n                        }\n                    })?;\n\n                    result = result.with_remote(site_name, unencrypted, encrypted);\n                }\n\n                _ => {\n                    return Err(FastlyConfigError::InvalidShieldingSiteDefinition {\n                        name: site_name,\n                        err: ShieldingSiteConfigError::IllegalSiteValue,\n                    });\n                }\n            }\n        }\n\n        Ok(result)\n    }\n}\n\n/// Information about a particular shielding site; specifically, if it's the\n/// \"local\" site that we're pretending to run on, or a remote site.\n///\n/// Remote sites can include different URLs for encrypted and unencrypted\n/// traffic. This mirrors the Fastly presentation of shields, in which users\n/// can choose to send their traffic encrypted or not (we generally recommend\n/// that people do send it encrypted).\n///\n/// Note, however, that we just take these locations as URLs, and don't\n/// actually check if the encrypted URL is HTTPS and the unencrypted one\n/// is HTTP. The whole point of this is just to provide avenues for testing,\n/// so it doesn't really matter, and it can be useful to abuse this flexibility\n/// when testing to avoid having to deal with certificates and such. (We do,\n/// actually; our tests check both URLs, but actually they're both HTTP.)\n#[derive(Clone, Debug)]\nenum ShieldingSite {\n    Local,\n    Remote { unencrypted: Url, encrypted: Url },\n}\n\nimpl ShieldingSites {\n    pub fn new() -> Self {\n        ShieldingSites {\n            sites: HashMap::new(),\n        }\n    }\n\n    pub fn with_local<S: ToString>(mut self, name: S) -> Self {\n        self.sites.insert(name.to_string(), ShieldingSite::Local);\n        self\n    }\n\n    pub fn with_remote<S: ToString>(mut self, name: S, unencrypted: Url, encrypted: Url) -> Self {\n        self.sites.insert(\n            name.to_string(),\n            ShieldingSite::Remote {\n                unencrypted,\n                encrypted,\n            },\n        );\n        self\n    }\n\n    pub fn is_local<S: AsRef<str>>(&self, name: S) -> bool {\n        self.sites\n            .get(name.as_ref())\n            .map(|x| matches!(x, ShieldingSite::Local))\n            .unwrap_or_default()\n    }\n\n    pub fn get_encrypted<S: AsRef<str>>(&self, name: S) -> Option<Url> {\n        self.sites.get(name.as_ref()).and_then(|x| match x {\n            ShieldingSite::Local => None,\n            ShieldingSite::Remote { encrypted, .. } => Some(encrypted.clone()),\n        })\n    }\n\n    pub fn get_unencrypted<S: AsRef<str>>(&self, name: S) -> Option<Url> {\n        self.sites.get(name.as_ref()).and_then(|x| match x {\n            ShieldingSite::Local => None,\n            ShieldingSite::Remote { unencrypted, .. } => Some(unencrypted.clone()),\n        })\n    }\n}\n"
  },
  {
    "path": "src/shift_mem.rs",
    "content": "/// This module takes the user wasm module (main module) and pushes the memory address\n/// by 2 pages. Concretely, we perform the following binary rewriting:\n///   1) Increase the initial memory size by 2 pages\n///   2) For active data section, move the offset address by 2 pages\n///   3) Instrument all the memory access instructions to add a 2 page offset.\n///      This includes `memory.grow`, `memory.size`, `memory.init`, `memory.fill`,\n///      `memory.copy`, `memory.load` and `memory.store`.\n///   4) When calling imported functions, the argument is passed unchanged.\n///      This means that the address passed to the imported function is the original address\n///      before the offset. Therefore, the receiver, i.e., adapter, needs to add the offset\n///      before accessing the passed in addresses. This is done in the adapter code.\n///      The comment at the top of `wasm_abi/adapter/src` details how this is accomplished.\nuse walrus::ir::*;\nuse walrus::*;\n\nconst OFFSET_PAGES: i32 = 2;\n// This const should always equal to the OFFSET defined in wasm_abi/adapter/src/lib.rs\nconst OFFSET: i32 = OFFSET_PAGES * 64 * 1024;\nfn shift_func(r#gen: &mut ModuleLocals, func: &mut LocalFunction) {\n    let start = func.entry_block();\n    let mut locals = vec![];\n    let mut stack = vec![start];\n    while let Some(seq_id) = stack.pop() {\n        let seq = func.block_mut(seq_id);\n        let mut instrs = Vec::with_capacity(seq.len());\n        for (instr, loc) in seq.instrs.iter() {\n            let instr = instr.clone();\n            let loc = *loc;\n            match instr {\n                Instr::Block(Block { seq }) | Instr::Loop(Loop { seq }) => {\n                    stack.push(seq);\n                    instrs.push((instr, loc));\n                }\n                Instr::IfElse(IfElse {\n                    consequent,\n                    alternative,\n                }) => {\n                    stack.push(consequent);\n                    stack.push(alternative);\n                    instrs.push((instr, loc));\n                }\n                Instr::MemorySize(_) | Instr::MemoryGrow(_) => {\n                    instrs.push((instr, loc));\n                    instrs.push((\n                        Instr::Const(Const {\n                            value: Value::I32(OFFSET_PAGES),\n                        }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::Binop(Binop {\n                            op: BinaryOp::I32Sub,\n                        }),\n                        Default::default(),\n                    ));\n                }\n                Instr::MemoryInit(_) | Instr::MemoryFill(_) => {\n                    let local1 = get_local(r#gen, &mut locals, 0);\n                    let local2 = get_local(r#gen, &mut locals, 1);\n                    instrs.push((\n                        Instr::LocalSet(LocalSet { local: local1 }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::LocalSet(LocalSet { local: local2 }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::Const(Const {\n                            value: Value::I32(OFFSET),\n                        }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::Binop(Binop {\n                            op: BinaryOp::I32Add,\n                        }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::LocalGet(LocalGet { local: local2 }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::LocalGet(LocalGet { local: local1 }),\n                        Default::default(),\n                    ));\n                    instrs.push((instr, loc));\n                }\n                Instr::MemoryCopy(_) => {\n                    let local1 = get_local(r#gen, &mut locals, 0);\n                    let local2 = get_local(r#gen, &mut locals, 1);\n                    instrs.push((\n                        Instr::LocalSet(LocalSet { local: local1 }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::LocalSet(LocalSet { local: local2 }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::Const(Const {\n                            value: Value::I32(OFFSET),\n                        }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::Binop(Binop {\n                            op: BinaryOp::I32Add,\n                        }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::LocalGet(LocalGet { local: local2 }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::Const(Const {\n                            value: Value::I32(OFFSET),\n                        }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::Binop(Binop {\n                            op: BinaryOp::I32Add,\n                        }),\n                        Default::default(),\n                    ));\n                    instrs.push((\n                        Instr::LocalGet(LocalGet { local: local1 }),\n                        Default::default(),\n                    ));\n                    instrs.push((instr, loc));\n                }\n                Instr::Load(Load {\n                    memory,\n                    kind,\n                    arg: MemArg { align, offset },\n                }) => {\n                    let offset = offset.checked_add(OFFSET as u32).unwrap();\n                    let instr = Instr::Load(Load {\n                        memory,\n                        kind,\n                        arg: MemArg { align, offset },\n                    });\n                    instrs.push((instr, loc));\n                }\n                Instr::Store(Store {\n                    memory,\n                    kind,\n                    arg: MemArg { align, offset },\n                }) => {\n                    let offset = offset.checked_add(OFFSET as u32).unwrap();\n                    let instr = Instr::Store(Store {\n                        memory,\n                        kind,\n                        arg: MemArg { align, offset },\n                    });\n                    instrs.push((instr, loc));\n                }\n                Instr::AtomicFence(_)\n                | Instr::AtomicNotify(_)\n                | Instr::AtomicWait(_)\n                | Instr::AtomicRmw(_)\n                | Instr::Cmpxchg(_) => todo!(),\n                Instr::LoadSimd(_) => todo!(),\n                _ => instrs.push((instr, loc)),\n            }\n        }\n        seq.instrs = instrs;\n    }\n}\nfn get_local(r#gen: &mut ModuleLocals, locals: &mut Vec<LocalId>, idx: usize) -> LocalId {\n    if idx < locals.len() {\n        locals[idx]\n    } else {\n        for _ in locals.len()..=idx {\n            locals.push(r#gen.add(ValType::I32));\n        }\n        locals[idx]\n    }\n}\n\npub fn shift_main_module(bytes: &[u8]) -> anyhow::Result<Vec<u8>> {\n    let mut module = Module::from_buffer(bytes)?;\n    // enlarge memory\n    if module.memories.is_empty() {\n        module\n            .memories\n            .add_local(false, false, OFFSET_PAGES as u64, None, None);\n    } else {\n        assert!(module.memories.len() == 1);\n        let mem = module.memories.iter_mut().next().unwrap();\n        mem.initial += OFFSET_PAGES as u64;\n        mem.maximum = mem\n            .maximum\n            .map(|m| m.checked_add(OFFSET_PAGES as u64).unwrap());\n    }\n    // shift data\n    let data_ids: Vec<_> = module.data.iter().map(|d| d.id()).collect();\n    for id in data_ids {\n        let data = module.data.get_mut(id);\n        match data.kind {\n            DataKind::Active {\n                memory,\n                offset: ConstExpr::Value(Value::I32(offset)),\n            } => {\n                let offset = offset.checked_add(OFFSET).unwrap();\n                data.kind = DataKind::Active {\n                    memory,\n                    offset: ConstExpr::Value(Value::I32(offset)),\n                };\n            }\n            DataKind::Active { .. } => unreachable!(),\n            DataKind::Passive => {}\n        }\n    }\n    // shift memory access\n    for (_, func) in module.funcs.iter_local_mut() {\n        shift_func(&mut module.locals, func);\n    }\n    Ok(module.emit_wasm())\n}\n"
  },
  {
    "path": "src/streaming_body.rs",
    "content": "//! Provides `StreamingBody`, an asynchronously-written body.\n//! See `Body` for a body that is asynchronously read.\n\nuse crate::{body::Chunk, error::Error};\nuse http::{HeaderMap, HeaderName, HeaderValue};\nuse tokio::sync::mpsc;\n\n// Note: this constant and comment is copied from xqd\n//\n// this isn't a tremendously useful size limiter, as it controls the number of chunks that can be in\n// flight before applying backpressure, as opposed to the size of data in those chunks\nconst STREAMING_CHANNEL_SIZE: usize = 8;\n\n/// The \"write end\" of a streaming body, used for writing to the body of a streaming upstream request\n/// or a streaming downstream response.\n///\n/// The corresponding \"read end\" can be found in the [`Chunk`] type.\n#[derive(Debug)]\npub struct StreamingBody {\n    sender: mpsc::Sender<StreamingBodyItem>,\n    pub(crate) trailers: HeaderMap,\n}\n\n/// The items sent over the `StreamingBody` channel.\n///\n/// These are either a [`Chunk`] corresponding to a write, or else a \"finish\" message. The purpose\n/// of the finish message is to ensure that we don't accidentally make incomplete messages appear\n/// complete.\n///\n/// If the streaming body is associated with a `content-length` request or response, the finish\n/// message is largely meaningless, as the content length provides the necessary framing information\n/// required for recipients to recognize an incomplete message.\n///\n/// The situation is more delicate with `transfer-encoding: chunked` requests and responses. In\n/// these cases, `hyper` will dutifully frame each chunk as it reads them from the `Body`. If the\n/// `Body` suddenly returns `Ok(None)`, it will apply the proper `0\\r\\n\\r\\n` termination to the\n/// message. The finish message ensures that this will only happen when the Wasm program\n/// affirmatively marks the body as finished.\n#[derive(Debug)]\npub enum StreamingBodyItem {\n    Chunk(Chunk),\n    Finished(HeaderMap),\n}\n\nimpl StreamingBody {\n    /// Create a new channel for streaming a body, returning write and read ends as a pair.\n    pub fn new() -> (StreamingBody, mpsc::Receiver<StreamingBodyItem>) {\n        let (sender, receiver) = mpsc::channel(STREAMING_CHANNEL_SIZE);\n        (\n            StreamingBody {\n                sender,\n                trailers: HeaderMap::new(),\n            },\n            receiver,\n        )\n    }\n\n    /// Send a single chunk along this body stream.\n    ///\n    /// Returns a `StreamingChunkSend` error if the underlying channel encounters an error\n    /// sending, e.g. due to the receive end being closed.\n    pub async fn send_chunk(&mut self, chunk: impl Into<Chunk>) -> Result<(), Error> {\n        self.sender\n            .send(StreamingBodyItem::Chunk(chunk.into()))\n            .await\n            .map_err(|_| Error::StreamingChunkSend)\n    }\n\n    /// Convenience method for appending trailers.\n    pub fn append_trailer(&mut self, name: HeaderName, value: HeaderValue) {\n        self.trailers.append(name, value);\n    }\n\n    /// Block until the body has room for writing additional chunks.\n    pub async fn await_ready(&mut self) {\n        let _ = self.sender.reserve().await;\n    }\n\n    /// Mark this streaming body as finished, so that it will be terminated correctly.\n    ///\n    /// This is important primarily for `Transfer-Encoding: chunked` bodies where a premature close\n    /// is only noticed if the chunked encoding is not properly terminated.\n    pub fn finish(self) -> Result<(), Error> {\n        match self\n            .sender\n            .try_send(StreamingBodyItem::Finished(self.trailers))\n        {\n            Ok(()) => Ok(()),\n            Err(mpsc::error::TrySendError::Closed(_)) => Ok(()),\n            Err(mpsc::error::TrySendError::Full(StreamingBodyItem::Finished(trailers))) => {\n                // If the channel is full, maybe the other end is just taking a while to receive all\n                // the bytes. Spawn a task that will send a `finish` message as soon as there's room\n                // in the channel.\n                tokio::task::spawn(async move {\n                    let _ = self\n                        .sender\n                        .send(StreamingBodyItem::Finished(trailers))\n                        .await;\n                });\n                Ok(())\n            }\n            Err(mpsc::error::TrySendError::Full(_)) => {\n                unreachable!(\"Only a StreamingBodyItem::Finished should be reachable\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/upstream.rs",
    "content": "use crate::{\n    body::{Body, Chunk},\n    cache::CacheOverride,\n    config::Backend,\n    error::Error,\n    framing::{content_length_is_valid, transfer_encoding_is_supported},\n    headers::filter_outgoing_headers,\n    sandbox::{AsyncItem, AsyncItemHandle, ViceroyRequestMetadata},\n    wiggle_abi::types::{ContentEncodings, FramingHeadersMode},\n};\nuse futures::Future;\nuse http::{HeaderValue, Version, uri};\nuse hyper::{Client, HeaderMap, Request, Response, Uri, client::HttpConnector, header};\nuse rustls::client::ServerName;\nuse std::{\n    io,\n    net::SocketAddr,\n    pin::Pin,\n    str::FromStr,\n    sync::Arc,\n    task::{self, Context, Poll},\n};\nuse tokio::{\n    io::{AsyncRead, AsyncWrite, ReadBuf},\n    net::TcpStream,\n};\nuse tokio_rustls::{TlsConnector, client::TlsStream};\nuse tracing::warn;\n\nstatic GZIP_VALUES: [HeaderValue; 2] = [\n    HeaderValue::from_static(\"gzip\"),\n    HeaderValue::from_static(\"x-gzip\"),\n];\n\n/// Viceroy's preloaded TLS configuration.\n///\n/// We now have too many options to fully precompute this value, so what this actually\n/// holds is a partially-complete TLS config builder, waiting for the point at which\n/// we decide whether or not to provide a client certificate and whether or not to use\n/// SNI.\n#[derive(Clone)]\npub struct TlsConfig {\n    pub(crate) partial_config: rustls::ConfigBuilder<rustls::ClientConfig, rustls::WantsVerifier>,\n    pub(crate) default_roots: rustls::RootCertStore,\n}\n\nimpl TlsConfig {\n    pub fn new() -> Result<TlsConfig, Error> {\n        let certs = rustls_native_certs::load_native_certs().map_err(Error::BadCerts)?;\n        let mut roots = rustls::RootCertStore::empty();\n        let (added, failed) =\n            roots.add_parsable_certificates(&certs.into_iter().map(|c| c.0).collect::<Vec<_>>());\n        if failed > 0 {\n            warn!(\n                \"failed to load {} certificate(s). attempting to continue with {} available certificate(s)\",\n                failed, added\n            );\n        }\n        if roots.is_empty() {\n            return Err(Error::TlsNoCAAvailable);\n        }\n\n        let partial_config = rustls::ClientConfig::builder().with_safe_defaults();\n\n        Ok(TlsConfig {\n            partial_config,\n            default_roots: roots,\n        })\n    }\n}\n\n/// A custom Hyper client connector, which is needed to override Hyper's default behavior of\n/// connecting to host specified by the request's URI; we instead want to connect to the host\n/// specified by our backend configuration, regardless of what the URI says.\n///\n/// This connector internally wraps Hyper's TLS connector, automatically providing TLS-based\n/// connections when indicated by the backend URI's scheme.\n#[derive(Clone)]\npub struct BackendConnector {\n    backend: Arc<Backend>,\n    http: HttpConnector,\n    tls_config: TlsConfig,\n}\n\nimpl BackendConnector {\n    pub fn new(backend: Arc<Backend>, tls_config: TlsConfig) -> Self {\n        let mut http = HttpConnector::new();\n        http.enforce_http(false);\n\n        Self {\n            http,\n            backend,\n            tls_config,\n        }\n    }\n}\n\ntype BoxError = Box<dyn std::error::Error + Send + Sync>;\n\npub enum Connection {\n    Http(TcpStream, ConnMetadata),\n    Https(Box<TlsStream<TcpStream>>, ConnMetadata),\n}\n\nimpl Connection {\n    fn metadata(&self) -> &ConnMetadata {\n        match self {\n            Connection::Http(_, md) => md,\n            Connection::Https(_, md) => md,\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct ConnMetadata {\n    pub direct_pass: bool,\n    pub remote_addr: SocketAddr,\n}\n\nimpl hyper::service::Service<Uri> for BackendConnector {\n    type Response = Connection;\n    type Error = BoxError;\n    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, BoxError>> + Send>>;\n\n    fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {\n        self.http.poll_ready(cx).map_err(Into::into)\n    }\n\n    // We ignore the URI argument and instead provide the backend's URI.\n    // NB this does _not_ affect the URI provided in the request itself.\n    fn call(&mut self, _: Uri) -> Self::Future {\n        let backend = self.backend.clone();\n        let config = self.tls_config.clone();\n\n        // the future for establishing the TCP connection. we create this outside of the `async`\n        // block to avoid capturing `http`\n        let connect_fut = self.http.call(backend.uri.clone());\n        let mut custom_roots = rustls::RootCertStore::empty();\n        let (added, ignored) = custom_roots.add_parsable_certificates(&self.backend.ca_certs);\n        if ignored > 0 {\n            tracing::warn!(\n                \"Ignored {} certificates in provided CA certificate.\",\n                ignored\n            );\n        }\n        if added == 0 && !self.backend.ca_certs.is_empty() {\n            return Box::pin(std::future::ready(Err(\n                Box::new(Error::TlsNoValidCACerts).into()\n            )));\n        }\n        let config = if self.backend.ca_certs.is_empty() {\n            config\n                .partial_config\n                .with_root_certificates(config.default_roots)\n        } else {\n            tracing::trace!(\"Using {} certificates from provided CA certificate.\", added);\n            config.partial_config.with_root_certificates(custom_roots)\n        };\n\n        Box::pin(async move {\n            let tcp = connect_fut.await?;\n\n            let remote_addr = tcp.peer_addr()?;\n            let metadata = ConnMetadata {\n                direct_pass: false,\n                remote_addr,\n            };\n\n            let conn = if backend.uri.scheme_str() == Some(\"https\") {\n                let mut config = if let Some(certed_key) = &backend.client_cert {\n                    config\n                        .with_client_auth_cert(certed_key.certs(), certed_key.key())\n                        .map_err(|_| {\n                            Error::InvalidClientCert(\n                                crate::config::ClientCertError::InvalidCertificateData(\n                                    \"Client certificate validation failed\".to_string(),\n                                ),\n                            )\n                        })?\n                } else {\n                    config.with_no_client_auth()\n                };\n                config.enable_sni = backend.use_sni;\n                if backend.grpc {\n                    config.alpn_protocols = vec![b\"h2\".to_vec()];\n                }\n                let connector = TlsConnector::from(Arc::new(config));\n\n                let cert_host = backend\n                    .cert_host\n                    .as_deref()\n                    .or_else(|| backend.uri.host())\n                    .ok_or(Error::TlsInvalidHost)?;\n\n                let dnsname = ServerName::try_from(cert_host).map_err(|_| {\n                    let err_msg = format!(\"Invalid DNS name: {}\", cert_host);\n                    tracing::error!(\"{}\", err_msg);\n                    Error::TlsInvalidHost\n                })?;\n\n                // Connect with proper validation\n                let tls = connector\n                    .connect(dnsname, tcp)\n                    .await\n                    .inspect_err(|e| {\n                        // Log detailed error information for certificate issues\n                        tracing::error!(\"TLS certificate validation failed: {}\", e);\n                    })\n                    .map_err(|e| {\n                        if e.to_string().contains(\"certificate validation failed\") {\n                            Error::TlsCertificateValidationFailed\n                        } else {\n                            Error::IoError(std::io::Error::other(format!(\n                                \"TLS connection error: {}\",\n                                e\n                            )))\n                        }\n                    })?;\n\n                if backend.grpc {\n                    let (_, tls_state) = tls.get_ref();\n\n                    match tls_state.alpn_protocol() {\n                        None => {\n                            tracing::warn!(\n                                \"Unexpected; request h2 for grpc, but got nothing back from ALPN\"\n                            );\n                        }\n\n                        Some(b\"h2\") => {}\n\n                        Some(other_value) => {\n                            return Err(Error::InvalidAlpnResponse(\n                                \"h2\",\n                                String::from_utf8_lossy(other_value).to_string(),\n                            )\n                            .into());\n                        }\n                    }\n                }\n\n                Connection::Https(Box::new(tls), metadata)\n            } else {\n                Connection::Http(tcp, metadata)\n            };\n\n            Ok(conn)\n        })\n    }\n}\n\nfn canonical_host_header(\n    original_headers: &HeaderMap,\n    original_uri: &Uri,\n    backend: &Backend,\n) -> HeaderValue {\n    backend\n        .override_host\n        .clone()\n        .or_else(|| original_headers.get(hyper::header::HOST).cloned())\n        .or_else(|| {\n            original_uri\n                .authority()\n                .and_then(|auth| HeaderValue::from_str(auth.as_str()).ok())\n        })\n        .or_else(|| {\n            backend\n                .uri\n                .host()\n                .and_then(|h| HeaderValue::from_str(h).ok())\n        })\n        .expect(\"Could not determine a Host header\")\n}\n\nfn canonical_uri(original_uri: &Uri, canonical_host: &str, backend: &Backend) -> Uri {\n    let original_path = original_uri\n        .path_and_query()\n        .map_or(\"/\", uri::PathAndQuery::as_str);\n\n    let mut canonical_uri = String::new();\n\n    // Hyper's `Client` API _requires_ a URI in \"absolute form\", including scheme, authority,\n    // and path.\n\n    // We start with the scheme, taken from the backend (which determines what we're actually\n    // communicating over).\n    canonical_uri.push_str(\n        backend\n            .uri\n            .scheme_str()\n            .expect(\"Backend URL is missing a scheme\"),\n    );\n    canonical_uri.push_str(\"://\");\n\n    // We get the authority from the canonical host. In some cases that might actually come\n    // from the `original_uri`, but usually it's from an explicit `Host` header.\n    canonical_uri.push_str(canonical_host);\n\n    // The path begins with the \"path prefix\" present in the backend's URI. This is often just\n    // an empty path or `/`.\n    canonical_uri.push_str(backend.uri.path());\n    if !canonical_uri.ends_with('/') {\n        canonical_uri.push('/');\n    }\n\n    // Finally we incorporate the requested path, taking care not to introduce extra `/`\n    // separators when gluing things together.\n    if let Some(stripped) = original_path.strip_prefix('/') {\n        canonical_uri.push_str(stripped)\n    } else {\n        canonical_uri.push_str(original_path)\n    }\n\n    Uri::from_str(&canonical_uri).expect(\"URI could be parsed\")\n}\n\n/// Sends the given request to the given backend.\n///\n/// Note that the backend's URI is used to determine which host to route the request to; the URI\n/// and any HOST header in `req` is _not_ used for routing. If the request does not contain a HOST\n/// header, one will be added, using the authority from the request's URI.\npub fn send_request(\n    mut req: Request<Body>,\n    backend: &Arc<Backend>,\n    backend_name: &str,\n    tls_config: &TlsConfig,\n) -> impl Future<Output = Result<Response<Body>, Error>> + use<> {\n    let connector = BackendConnector::new(backend.clone(), tls_config.clone());\n\n    let host = canonical_host_header(req.headers(), req.uri(), backend);\n    let uri = canonical_uri(\n        req.uri(),\n        std::str::from_utf8(host.as_bytes()).expect(\"Host was in UTF-8\"),\n        backend,\n    );\n\n    let try_decompression = req\n        .extensions()\n        .get::<ViceroyRequestMetadata>()\n        .map(|vrm| {\n            vrm.auto_decompress_encodings\n                .contains(ContentEncodings::GZIP)\n        })\n        .unwrap_or(false);\n\n    let mut framing_headers_mode = req\n        .extensions()\n        .get::<ViceroyRequestMetadata>()\n        .map(|vrm| vrm.framing_headers_mode)\n        .unwrap_or(FramingHeadersMode::Automatic);\n\n    if framing_headers_mode == FramingHeadersMode::ManuallyFromHeaders {\n        if !content_length_is_valid(req.headers()) {\n            warn!(\n                \"Backend request has malformed Content-Length header, falling back to automatic framing.\"\n            );\n            framing_headers_mode = FramingHeadersMode::Automatic;\n        } else if !transfer_encoding_is_supported(req.headers()) {\n            warn!(\n                \"Backend request has unsupported Transfer-Encoding header, falling back to automatic framing.\"\n            );\n            framing_headers_mode = FramingHeadersMode::Automatic;\n        } else if !req.headers().contains_key(header::CONTENT_LENGTH)\n            && !req.headers().contains_key(header::TRANSFER_ENCODING)\n        {\n            warn!(\n                \"Backend request has neither Content-Length nor Transfer-Encoding header, falling back to automatic framing.\"\n            );\n            framing_headers_mode = FramingHeadersMode::Automatic;\n        }\n    }\n    if framing_headers_mode != FramingHeadersMode::ManuallyFromHeaders {\n        filter_outgoing_headers(req.headers_mut());\n    }\n\n    req.headers_mut().insert(hyper::header::HOST, host);\n    *req.uri_mut() = uri;\n\n    let h2only = backend.grpc;\n    let backend_name = backend_name.to_string();\n    let backend_uri = backend.uri.to_string();\n    async move {\n        let mut builder = Client::builder();\n\n        if req.version() == Version::HTTP_2 {\n            builder.http2_only(true);\n        }\n\n        let is_pass = req\n            .extensions()\n            .get::<CacheOverride>()\n            .map(CacheOverride::is_pass)\n            .unwrap_or_default();\n\n        let mut basic_response = builder\n            .set_host(false)\n            .http2_only(h2only)\n            .build(connector)\n            .request(req)\n            .await\n            .map_err(|source| {\n                let err = Error::BackendConnectionError {\n                    backend_name: backend_name.clone(),\n                    uri: backend_uri.clone(),\n                    source,\n                };\n                tracing::error!(\"{}\", err);\n                err\n            })?;\n\n        if let Some(md) = basic_response.extensions_mut().get_mut::<ConnMetadata>() {\n            // This is used later to create similar behaviour between Compute and Viceroy.\n            md.direct_pass = is_pass;\n        }\n\n        if try_decompression\n            && basic_response\n                .headers()\n                .get(header::CONTENT_ENCODING)\n                .map(|x| GZIP_VALUES.contains(x))\n                .unwrap_or(false)\n        {\n            let mut decompressing_response =\n                basic_response.map(Chunk::compressed_body).map(Body::from);\n\n            decompressing_response\n                .headers_mut()\n                .remove(header::CONTENT_ENCODING);\n            decompressing_response\n                .headers_mut()\n                .remove(header::CONTENT_LENGTH);\n            Ok(decompressing_response)\n        } else {\n            Ok(basic_response.map(Body::from))\n        }\n    }\n}\n\n/// The type ultimately yielded by a `PendingRequest`.\n/// An asynchronous request awaiting a response.\n#[allow(unused)]\n#[derive(Debug)]\npub enum PendingRequest {\n    // NB: we use channels rather than a `JoinHandle` in order to support the `poll` API.\n}\n\n/// A pair of a pending request and the handle that pointed to it in the sandbox, suitable for\n/// invoking the futures select API.\n///\n/// We need this type because `future::select_all` does not guarantee anything about the order of\n/// the leftover futures. We have to build our own future to keep the handle-receiver association.\n#[derive(Debug)]\npub struct SelectTarget {\n    pub handle: AsyncItemHandle,\n    pub item: AsyncItem,\n}\n\n// Boilerplate forwarding implementations for `Connection`:\n\nimpl hyper::client::connect::Connection for Connection {\n    fn connected(&self) -> hyper::client::connect::Connected {\n        hyper::client::connect::Connected::new().extra(self.metadata().clone())\n    }\n}\n\nimpl AsyncRead for Connection {\n    fn poll_read(\n        self: Pin<&mut Self>,\n        cx: &mut Context,\n        buf: &mut ReadBuf<'_>,\n    ) -> Poll<Result<(), io::Error>> {\n        match Pin::get_mut(self) {\n            Connection::Http(s, _) => Pin::new(s).poll_read(cx, buf),\n            Connection::Https(s, _) => Pin::new(s).poll_read(cx, buf),\n        }\n    }\n}\n\nimpl AsyncWrite for Connection {\n    fn poll_write(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>,\n        buf: &[u8],\n    ) -> Poll<Result<usize, io::Error>> {\n        match Pin::get_mut(self) {\n            Connection::Http(s, _) => Pin::new(s).poll_write(cx, buf),\n            Connection::Https(s, _) => Pin::new(s).poll_write(cx, buf),\n        }\n    }\n\n    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {\n        match Pin::get_mut(self) {\n            Connection::Http(s, _) => Pin::new(s).poll_flush(cx),\n            Connection::Https(s, _) => Pin::new(s).poll_flush(cx),\n        }\n    }\n\n    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {\n        match Pin::get_mut(self) {\n            Connection::Http(s, _) => Pin::new(s).poll_shutdown(cx),\n            Connection::Https(s, _) => Pin::new(s).poll_shutdown(cx),\n        }\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/acl.rs",
    "content": "use crate::error::{Error, HandleError};\nuse crate::sandbox::Sandbox;\nuse crate::wiggle_abi::{fastly_acl, types};\nuse std::net::{IpAddr, Ipv4Addr, Ipv6Addr};\n\nimpl fastly_acl::FastlyAcl for Sandbox {\n    /// Open a handle to an ACL by its linked name.\n    fn open(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        acl_name: wiggle::GuestPtr<str>,\n    ) -> Result<types::AclHandle, Error> {\n        let acl_name = memory.as_str(acl_name)?.ok_or(Error::SharedMemory)?;\n        self.acl_handle_by_name(acl_name).ok_or(Error::ValueAbsent)\n    }\n\n    /// Perform an ACL lookup operation using the given ACL handle.\n    ///\n    /// There are two levels of errors returned by this function:\n    ///\n    ///   - Error: These are general hostcall errors, e.g. handle not found.\n    ///   - AclError: There are ACL-specific errors, e.g. 'no content'.\n    ///\n    /// It's the callers responsibility to check both errors.\n    async fn lookup(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        acl_handle: types::AclHandle,\n        ip_octets: wiggle::GuestPtr<u8>, // This should be either a 4 or 16-byte array.\n        ip_len: u32,                     // Either 4 or 16.\n        body_handle_out: wiggle::GuestPtr<types::BodyHandle>,\n        acl_error_out: wiggle::GuestPtr<types::AclError>,\n    ) -> Result<(), Error> {\n        let acl = self.acl_by_handle(acl_handle).ok_or(Error::HandleError(\n            HandleError::InvalidAclHandle(acl_handle),\n        ))?;\n\n        let ip: IpAddr = {\n            let ip_octets = memory.to_vec(ip_octets.as_array(ip_len))?;\n            match ip_len {\n                4 => IpAddr::V4(Ipv4Addr::from(\n                    TryInto::<[u8; 4]>::try_into(ip_octets).unwrap(),\n                )),\n                16 => IpAddr::V6(Ipv6Addr::from(\n                    TryInto::<[u8; 16]>::try_into(ip_octets).unwrap(),\n                )),\n                _ => return Err(Error::InvalidArgument),\n            }\n        };\n\n        match acl.lookup(ip) {\n            Some(entry) => {\n                let body =\n                    serde_json::to_vec_pretty(&entry).map_err(|err| Error::Other(err.into()))?;\n                let body_handle = self.insert_body(body.into());\n                memory.write(body_handle_out, body_handle)?;\n                memory.write(acl_error_out, types::AclError::Ok)?;\n                Ok(())\n            }\n            None => {\n                memory.write(acl_error_out, types::AclError::NoContent)?;\n                Ok(())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/backend_impl.rs",
    "content": "use super::fastly_backend::FastlyBackend;\nuse crate::{config::Backend, error::Error, sandbox::Sandbox};\n\nfn lookup_backend_definition<'sess>(\n    sandbox: &'sess Sandbox,\n    memory: &mut wiggle::GuestMemory<'_>,\n    backend: wiggle::GuestPtr<str>,\n) -> Result<&'sess Backend, Error> {\n    let name = memory.as_str(backend)?.ok_or(Error::SharedMemory)?;\n    sandbox\n        .backend(name)\n        .map(AsRef::as_ref)\n        .ok_or(Error::InvalidArgument)\n}\n\nimpl FastlyBackend for Sandbox {\n    fn exists(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::BackendExists, Error> {\n        if lookup_backend_definition(self, memory, backend).is_ok() {\n            Ok(1)\n        } else {\n            Ok(0)\n        }\n    }\n\n    fn is_healthy(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::BackendHealth, Error> {\n        let backend = lookup_backend_definition(self, memory, backend)?;\n        match &backend.health {\n            crate::config::BackendHealth::Unknown => Ok(super::types::BackendHealth::Unknown),\n            crate::config::BackendHealth::Healthy => Ok(super::types::BackendHealth::Healthy),\n            crate::config::BackendHealth::Unhealthy => Ok(super::types::BackendHealth::Unhealthy),\n        }\n    }\n\n    fn is_dynamic(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::IsDynamic, Error> {\n        let name = memory.as_str(backend)?.ok_or(Error::SharedMemory)?;\n\n        if self.dynamic_backend(name).is_some() {\n            Ok(1)\n        } else if self.backend(name).is_some() {\n            Ok(0)\n        } else {\n            Err(Error::InvalidArgument)\n        }\n    }\n\n    fn get_host(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n        value: wiggle::GuestPtr<u8>,\n        value_max_len: u32,\n        nwritten_out: wiggle::GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let backend = lookup_backend_definition(self, memory, backend)?;\n        let host = backend.uri.host().expect(\"backend uri has host\");\n\n        let host_len = host.len().try_into().expect(\"host.len() must fit in u32\");\n        if host_len > value_max_len {\n            memory.write(nwritten_out, host_len)?;\n            return Err(Error::BufferLengthError {\n                buf: \"host\",\n                len: \"host.len()\",\n            });\n        }\n\n        let host = host.as_bytes();\n        memory.copy_from_slice(host, value.as_array(host_len))?;\n        memory.write(nwritten_out, host_len)?;\n        Ok(())\n    }\n\n    fn get_override_host(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n        value: wiggle::GuestPtr<u8>,\n        value_max_len: u32,\n        nwritten_out: wiggle::GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let backend = lookup_backend_definition(self, memory, backend)?;\n        let host = backend\n            .override_host\n            .as_ref()\n            .ok_or(Error::ValueAbsent)?\n            .to_str()?;\n\n        let host_len = host.len().try_into().expect(\"host.len() must fit in u32\");\n        if host_len > value_max_len {\n            memory.write(nwritten_out, host_len)?;\n            return Err(Error::BufferLengthError {\n                buf: \"host\",\n                len: \"host.len()\",\n            });\n        }\n\n        let host = host.as_bytes();\n        memory.copy_from_slice(host, value.as_array(host_len))?;\n        memory.write(nwritten_out, host_len)?;\n        Ok(())\n    }\n\n    fn get_port(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::Port, Error> {\n        let backend = lookup_backend_definition(self, memory, backend)?;\n\n        match backend.uri.port_u16() {\n            Some(port) => Ok(port),\n            None if backend.uri.scheme() == Some(&http::uri::Scheme::HTTPS) => Ok(443),\n            _ => Ok(80),\n        }\n    }\n\n    fn get_connect_timeout_ms(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::TimeoutMs, Error> {\n        // just doing this to get a different error if the backend doesn't exist\n        let _ = lookup_backend_definition(self, memory, backend)?;\n        // health checks are not enabled in Viceroy :(\n        Err(Error::NotAvailable(\"Connection timing\"))\n    }\n\n    fn get_first_byte_timeout_ms(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::TimeoutMs, Error> {\n        // just doing this to get a different error if the backend doesn't exist\n        let _ = lookup_backend_definition(self, memory, backend)?;\n        // health checks are not enabled in Viceroy :(\n        Err(Error::NotAvailable(\"Connection timing\"))\n    }\n\n    fn get_between_bytes_timeout_ms(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::TimeoutMs, Error> {\n        // just doing this to get a different error if the backend doesn't exist\n        let _ = lookup_backend_definition(self, memory, backend)?;\n        // health checks are not enabled in Viceroy :(\n        Err(Error::NotAvailable(\"Connection timing\"))\n    }\n\n    fn get_ssl_min_version(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::TlsVersion, Error> {\n        // just doing this to get a different error if the backend doesn't exist\n        let _ = lookup_backend_definition(self, memory, backend)?;\n        // health checks are not enabled in Viceroy :(\n        Err(Error::NotAvailable(\"SSL version information\"))\n    }\n\n    fn get_ssl_max_version(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::TlsVersion, Error> {\n        // just doing this to get a different error if the backend doesn't exist\n        let _ = lookup_backend_definition(self, memory, backend)?;\n        // health checks are not enabled in Viceroy :(\n        Err(Error::NotAvailable(\"SSL version information\"))\n    }\n\n    fn get_http_keepalive_time(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<u32, Error> {\n        // Viceroy doesn't support pooling:\n        lookup_backend_definition(self, memory, backend).map(|_| 0)\n    }\n\n    fn get_tcp_keepalive_enable(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<u32, Error> {\n        // Viceroy doesn't support TCP keepalives:\n        lookup_backend_definition(self, memory, backend).map(|_| 0)\n    }\n\n    fn get_tcp_keepalive_interval(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<u32, Error> {\n        // Viceroy doesn't currently support TCP keepalives:\n        lookup_backend_definition(self, memory, backend).map(|_| 0)\n    }\n\n    fn get_tcp_keepalive_probes(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<u32, Error> {\n        // Viceroy doesn't currently support TCP keepalives:\n        lookup_backend_definition(self, memory, backend).map(|_| 0)\n    }\n\n    fn get_tcp_keepalive_time(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<u32, Error> {\n        // Viceroy doesn't currently support TCP keepalives:\n        lookup_backend_definition(self, memory, backend).map(|_| 0)\n    }\n\n    fn is_ssl(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        backend: wiggle::GuestPtr<str>,\n    ) -> Result<super::types::IsSsl, Error> {\n        lookup_backend_definition(self, memory, backend)\n            .map(|x| x.uri.scheme() == Some(&http::uri::Scheme::HTTPS))\n            .map(|x| if x { 1 } else { 0 })\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/body_impl.rs",
    "content": "//! fastly_body` hostcall implementations.\n\nuse http::{HeaderName, HeaderValue};\n\nuse crate::wiggle_abi::headers::HttpHeaders;\n\nuse {\n    crate::{\n        body::Body,\n        error::Error,\n        sandbox::Sandbox,\n        wiggle_abi::{\n            fastly_http_body::FastlyHttpBody,\n            types::{\n                BodyHandle, BodyLength, BodyWriteEnd, MultiValueCursor, MultiValueCursorResult,\n            },\n        },\n    },\n    std::convert::TryInto,\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyHttpBody for Sandbox {\n    async fn append(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        dest: BodyHandle,\n        src: BodyHandle,\n    ) -> Result<(), Error> {\n        // Take the `src` body out of the sandbox, and get a mutable reference\n        // to the `dest` body we will append to.\n        let mut src = self.take_body(src)?;\n        let trailers = std::mem::take(&mut src.trailers);\n\n        if self.is_streaming_body(dest) {\n            let dest = self.streaming_body_mut(dest)?;\n            for chunk in src {\n                dest.send_chunk(chunk).await?;\n            }\n            dest.trailers.extend(trailers);\n        } else {\n            let dest = self.body_mut(dest)?;\n            dest.trailers.extend(trailers);\n            dest.append(src);\n        }\n        Ok(())\n    }\n\n    fn new(&mut self, _memory: &mut GuestMemory<'_>) -> Result<BodyHandle, Error> {\n        Ok(self.insert_body(Body::empty()))\n    }\n\n    async fn read(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n    ) -> Result<u32, Error> {\n        // only normal bodies (not streaming bodies) can be read from\n        let body = self.body_mut(body_handle)?;\n\n        let array = buf.as_array(buf_len);\n        let slice = memory.as_slice_mut(array)?.ok_or(Error::SharedMemory)?;\n        let n = body\n            .read(slice)\n            .await?\n            .try_into()\n            .expect(\"guest buffer size must be less than u32\");\n        Ok(n)\n    }\n\n    async fn write(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n        buf: GuestPtr<[u8]>,\n        end: BodyWriteEnd,\n    ) -> Result<u32, Error> {\n        // Validate the body handle and the buffer.\n        let buf = memory.as_slice(buf)?.ok_or(Error::SharedMemory)?;\n\n        // Push the buffer onto the front or back of the body based on the `BodyWriteEnd` flag.\n        match end {\n            BodyWriteEnd::Front => {\n                // Only normal bodies can be front-written\n                self.body_mut(body_handle)?.push_front(buf);\n            }\n            BodyWriteEnd::Back => {\n                if self.is_streaming_body(body_handle) {\n                    self.streaming_body_mut(body_handle)?\n                        .send_chunk(buf)\n                        .await?;\n                } else {\n                    self.body_mut(body_handle)?.push_back(buf);\n                }\n            }\n        }\n        // Finally, return the number of bytes written, which is _always_ the full buffer\n        Ok(buf\n            .len()\n            .try_into()\n            .expect(\"the buffer length must fit into a u32\"))\n    }\n\n    fn close(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n    ) -> Result<(), Error> {\n        // Drop the body and pass up an error if the handle does not exist\n        if self.is_streaming_body(body_handle) {\n            // Make sure a streaming body gets a `finish` message\n            self.take_streaming_body(body_handle)?.finish()\n        } else {\n            Ok(self.drop_body(body_handle)?)\n        }\n    }\n\n    fn abandon(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n    ) -> Result<(), Error> {\n        // Drop the body without a `finish` message\n        Ok(self.drop_body(body_handle)?)\n    }\n\n    fn trailer_append(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        // Appending trailers is always allowed for bodies and streaming bodies.\n        if self.is_streaming_body(body_handle) {\n            let body = self.streaming_body_mut(body_handle)?;\n            let name = HeaderName::from_bytes(memory.as_slice(name)?.ok_or(Error::SharedMemory)?)?;\n            let value =\n                HeaderValue::from_bytes(memory.as_slice(value)?.ok_or(Error::SharedMemory)?)?;\n            body.append_trailer(name, value);\n            Ok(())\n        } else {\n            let body = self.body_mut(body_handle)?;\n            let trailers = &mut body.trailers;\n            HttpHeaders::append(trailers, memory, name, value)\n        }\n    }\n\n    fn trailer_names_get<'a>(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: MultiValueCursor,\n        ending_cursor_out: GuestPtr<MultiValueCursorResult>,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        // Read operations are not allowed on streaming bodies.\n        if self.is_streaming_body(body_handle) {\n            return Err(Error::InvalidArgument);\n        }\n\n        let body = self.body_mut(body_handle)?;\n        if body.trailers_ready {\n            let trailers = &body.trailers;\n            return multi_value_result!(\n                memory,\n                trailers.names_get(memory, buf, buf_len, cursor, nwritten_out),\n                ending_cursor_out\n            );\n        }\n        Err(Error::Again)\n    }\n\n    fn trailer_value_get<'a>(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<u8>,\n        value_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        // Read operations are not allowed on streaming bodies.\n        if self.is_streaming_body(body_handle) {\n            return Err(Error::InvalidArgument);\n        }\n\n        let body = &mut self.body_mut(body_handle)?;\n        if body.trailers_ready {\n            let trailers = &mut body.trailers;\n            return trailers.value_get(memory, name, value, value_max_len, nwritten_out);\n        }\n        Err(Error::Again)\n    }\n\n    fn trailer_values_get<'a>(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n        name: GuestPtr<[u8]>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: MultiValueCursor,\n        ending_cursor_out: GuestPtr<MultiValueCursorResult>,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        // Read operations are not allowed on streaming bodies.\n        if self.is_streaming_body(body_handle) {\n            return Err(Error::InvalidArgument);\n        }\n\n        let body = &mut self.body_mut(body_handle)?;\n        if body.trailers_ready {\n            let trailers = &mut body.trailers;\n            return multi_value_result!(\n                memory,\n                trailers.values_get(memory, name, buf, buf_len, cursor, nwritten_out),\n                ending_cursor_out\n            );\n        }\n        Err(Error::Again)\n    }\n\n    fn known_length(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        body_handle: BodyHandle,\n    ) -> Result<BodyLength, Error> {\n        if self.is_streaming_body(body_handle) {\n            Err(Error::ValueAbsent)\n        } else if let Some(len) = self.body_mut(body_handle)?.len() {\n            Ok(len)\n        } else {\n            Err(Error::ValueAbsent)\n        }\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/cache.rs",
    "content": "use core::str;\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse bytes::Bytes;\nuse http::HeaderMap;\n\nuse crate::body::Body;\nuse crate::cache::{CacheKey, SurrogateKeySet, VaryRule, WriteOptions};\nuse crate::sandbox::{PeekableTask, PendingCacheTask, Sandbox};\nuse crate::wiggle_abi::types::CacheWriteOptionsMask;\n\nuse super::fastly_cache::FastlyCache;\nuse super::{Error, types};\n\nfn load_cache_key(\n    memory: &wiggle::GuestMemory<'_>,\n    cache_key: wiggle::GuestPtr<[u8]>,\n) -> Result<CacheKey, Error> {\n    let bytes = memory.as_slice(cache_key)?.ok_or(Error::SharedMemory)?;\n    let key: CacheKey = bytes.try_into().map_err(|_| Error::InvalidArgument)?;\n    Ok(key)\n}\n\nfn load_write_options(\n    memory: &wiggle::GuestMemory<'_>,\n    mut options_mask: types::CacheWriteOptionsMask,\n    options: &types::CacheWriteOptions,\n) -> Result<WriteOptions, Error> {\n    // Headers must be handled before this:\n    assert!(\n        !options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS),\n        \"Viceroy bug! headers must be handled before load_write_options\"\n    );\n\n    // Clear each bit of options_mask as we handle it, to make sure we catch any unknown options.\n    let max_age = Duration::from_nanos(options.max_age_ns);\n\n    let initial_age = if options_mask.contains(CacheWriteOptionsMask::INITIAL_AGE_NS) {\n        Duration::from_nanos(options.initial_age_ns)\n    } else {\n        Duration::ZERO\n    };\n\n    options_mask &= !CacheWriteOptionsMask::INITIAL_AGE_NS;\n\n    let stale_while_revalidate =\n        if options_mask.contains(CacheWriteOptionsMask::STALE_WHILE_REVALIDATE_NS) {\n            Duration::from_nanos(options.stale_while_revalidate_ns)\n        } else {\n            Duration::ZERO\n        };\n    options_mask &= !CacheWriteOptionsMask::STALE_WHILE_REVALIDATE_NS;\n\n    let vary_rule = if options_mask.contains(CacheWriteOptionsMask::VARY_RULE) {\n        let slice = options.vary_rule_ptr.as_array(options.vary_rule_len);\n        let vary_rule_bytes = memory.as_slice(slice)?.ok_or(Error::SharedMemory)?;\n        let vary_rule_str = str::from_utf8(vary_rule_bytes).map_err(Error::Utf8Expected)?;\n        vary_rule_str.parse()?\n    } else {\n        VaryRule::default()\n    };\n    options_mask &= !CacheWriteOptionsMask::VARY_RULE;\n\n    let user_metadata = if options_mask.contains(CacheWriteOptionsMask::USER_METADATA) {\n        let slice = options\n            .user_metadata_ptr\n            .as_array(options.user_metadata_len);\n        let user_metadata_bytes = memory.as_slice(slice)?.ok_or(Error::SharedMemory)?;\n        Bytes::copy_from_slice(user_metadata_bytes)\n    } else {\n        Bytes::new()\n    };\n    options_mask &= !CacheWriteOptionsMask::USER_METADATA;\n\n    let length = if options_mask.contains(CacheWriteOptionsMask::LENGTH) {\n        Some(options.length)\n    } else {\n        None\n    };\n    options_mask &= !CacheWriteOptionsMask::LENGTH;\n\n    let sensitive_data = options_mask.contains(CacheWriteOptionsMask::SENSITIVE_DATA);\n    options_mask &= !CacheWriteOptionsMask::SENSITIVE_DATA;\n\n    // SERVICE_ID differences are observable- but we don't implement that behavior. Error explicitly.\n    if options_mask.contains(CacheWriteOptionsMask::SERVICE_ID) {\n        return Err(Error::Unsupported {\n            msg: \"cache on_behalf_of is not supported in Viceroy\",\n        });\n    }\n    options_mask &= !CacheWriteOptionsMask::SERVICE_ID;\n\n    let edge_max_age = if options_mask.contains(CacheWriteOptionsMask::EDGE_MAX_AGE_NS) {\n        Duration::from_nanos(options.edge_max_age_ns)\n    } else {\n        max_age\n    };\n    if edge_max_age > max_age {\n        tracing::error!(\n            \"deliver node max age {} must be less than TTL {}\",\n            edge_max_age.as_secs(),\n            max_age.as_secs()\n        );\n        return Err(Error::InvalidArgument);\n    }\n    options_mask &= !CacheWriteOptionsMask::EDGE_MAX_AGE_NS;\n\n    let surrogate_keys = if options_mask.contains(CacheWriteOptionsMask::SURROGATE_KEYS) {\n        let slice = options\n            .surrogate_keys_ptr\n            .as_array(options.surrogate_keys_len);\n        let surrogate_keys_bytes = memory.as_slice(slice)?.ok_or(Error::SharedMemory)?;\n        surrogate_keys_bytes.try_into()?\n    } else {\n        SurrogateKeySet::default()\n    };\n    options_mask &= !CacheWriteOptionsMask::SURROGATE_KEYS;\n\n    if !options_mask.is_empty() {\n        return Err(Error::NotAvailable(\"unknown cache write option\"));\n    }\n\n    Ok(WriteOptions {\n        max_age,\n        initial_age,\n        stale_while_revalidate,\n        vary_rule,\n        user_metadata,\n        length,\n        sensitive_data,\n        edge_max_age,\n        surrogate_keys,\n    })\n}\n\nstruct LookupOptions {\n    headers: HeaderMap,\n    always_use_requested_range: bool,\n}\n\nfn load_lookup_options(\n    sandbox: &Sandbox,\n    memory: &wiggle::GuestMemory<'_>,\n    mut options_mask: types::CacheLookupOptionsMask,\n    options: wiggle::GuestPtr<types::CacheLookupOptions>,\n) -> Result<LookupOptions, Error> {\n    let options = memory.read(options)?;\n    let headers = if options_mask.contains(types::CacheLookupOptionsMask::REQUEST_HEADERS) {\n        let handle = options.request_headers;\n        let parts = sandbox.request_parts(handle)?;\n        parts.headers.clone()\n    } else {\n        HeaderMap::default()\n    };\n\n    options_mask &= !types::CacheLookupOptionsMask::REQUEST_HEADERS;\n\n    if options_mask.contains(types::CacheLookupOptionsMask::SERVICE_ID) {\n        // TODO: Support service-ID-keyed hashes, for testing internal services at Fastly\n        return Err(Error::Unsupported {\n            msg: \"service ID in cache lookup is not supported in Viceroy\",\n        });\n    }\n\n    let always_use_requested_range =\n        options_mask.contains(types::CacheLookupOptionsMask::ALWAYS_USE_REQUESTED_RANGE);\n    options_mask &= !types::CacheLookupOptionsMask::ALWAYS_USE_REQUESTED_RANGE;\n\n    if !options_mask.is_empty() {\n        return Err(Error::NotAvailable(\"unknown cache lookup option\"));\n    }\n\n    Ok(LookupOptions {\n        headers,\n        always_use_requested_range,\n    })\n}\n\n#[allow(unused_variables)]\nimpl FastlyCache for Sandbox {\n    async fn lookup(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_key: wiggle::GuestPtr<[u8]>,\n        options_mask: types::CacheLookupOptionsMask,\n        options: wiggle::GuestPtr<types::CacheLookupOptions>,\n    ) -> Result<types::CacheHandle, Error> {\n        let LookupOptions {\n            headers,\n            always_use_requested_range,\n        } = load_lookup_options(self, memory, options_mask, options)?;\n        let key = load_cache_key(memory, cache_key)?;\n        let cache = Arc::clone(self.cache());\n\n        let task = PeekableTask::spawn(Box::pin(async move {\n            Ok(cache\n                .lookup(&key, &headers)\n                .await\n                .with_always_use_requested_range(always_use_requested_range))\n        }))\n        .await;\n        let task = PendingCacheTask::new(task);\n        let handle = self.insert_cache_op(task);\n        Ok(handle.into())\n    }\n\n    async fn insert(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_key: wiggle::GuestPtr<[u8]>,\n        options_mask: types::CacheWriteOptionsMask,\n        options: wiggle::GuestPtr<types::CacheWriteOptions>,\n    ) -> Result<types::BodyHandle, Error> {\n        let key = load_cache_key(memory, cache_key)?;\n        let guest_options = memory.read(options)?;\n\n        // This is the only method that accepts REQUEST_HEADERS in the options mask.\n        let request_headers = if options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS) {\n            let handle = guest_options.request_headers;\n            let parts = self.request_parts(handle)?;\n            parts.headers.clone()\n        } else {\n            HeaderMap::default()\n        };\n        let options = load_write_options(\n            memory,\n            options_mask & !CacheWriteOptionsMask::REQUEST_HEADERS,\n            &guest_options,\n        )?;\n        let cache = Arc::clone(self.cache());\n\n        let handle = self.insert_body(Body::empty());\n        let read_body = self.begin_streaming(handle)?;\n        cache\n            .insert(&key, request_headers, options, read_body)\n            .await;\n        Ok(handle)\n    }\n\n    async fn replace(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_key: wiggle::GuestPtr<[u8]>,\n        options_mask: types::CacheReplaceOptionsMask,\n        abi_options: wiggle::GuestPtr<types::CacheReplaceOptions>,\n    ) -> Result<types::CacheReplaceHandle, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_get_age_ns(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_get_body(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n        options_mask: types::CacheGetBodyOptionsMask,\n        options: &types::CacheGetBodyOptions,\n    ) -> Result<types::BodyHandle, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_get_hits(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n    ) -> Result<u64, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_get_length(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n    ) -> Result<u64, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_get_max_age_ns(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_get_stale_while_revalidate_ns(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_get_state(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n    ) -> Result<types::CacheLookupState, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_get_user_metadata(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n        out_ptr: wiggle::GuestPtr<u8>,\n        out_len: u32,\n        nwritten_out: wiggle::GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn replace_insert(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_handle: types::CacheReplaceHandle,\n        options_mask: types::CacheWriteOptionsMask,\n        abi_options: wiggle::GuestPtr<types::CacheWriteOptions>,\n    ) -> Result<types::BodyHandle, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn transaction_lookup(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_key: wiggle::GuestPtr<[u8]>,\n        options_mask: types::CacheLookupOptionsMask,\n        options: wiggle::GuestPtr<types::CacheLookupOptions>,\n    ) -> Result<types::CacheHandle, Error> {\n        let h = self\n            .transaction_lookup_async(memory, cache_key, options_mask, options)\n            .await?;\n        self.cache_busy_handle_wait(memory, h).await\n    }\n\n    async fn transaction_lookup_async(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        cache_key: wiggle::GuestPtr<[u8]>,\n        options_mask: types::CacheLookupOptionsMask,\n        options: wiggle::GuestPtr<types::CacheLookupOptions>,\n    ) -> Result<types::CacheBusyHandle, Error> {\n        let LookupOptions {\n            headers,\n            always_use_requested_range,\n        } = load_lookup_options(self, memory, options_mask, options)?;\n        let key = load_cache_key(memory, cache_key)?;\n        let cache = Arc::clone(self.cache());\n\n        // Look up once, joining the transaction only if obligated:\n        let e = cache\n            .transaction_lookup(&key, &headers, false)\n            .await\n            .with_always_use_requested_range(always_use_requested_range);\n        let ready = e.found().is_some() || e.go_get().is_some();\n        // If we already got _something_, we can provide an already-complete PeekableTask.\n        // Otherwise we need to spawn it and let it block in the background.\n        let task = if ready {\n            PeekableTask::complete(e)\n        } else {\n            PeekableTask::spawn(Box::pin(async move {\n                Ok(cache\n                    .transaction_lookup(&key, &headers, true)\n                    .await\n                    .with_always_use_requested_range(always_use_requested_range))\n            }))\n            .await\n        };\n\n        let task = PendingCacheTask::new(task);\n        let handle = self.insert_cache_op(task);\n        Ok(handle.into())\n    }\n\n    async fn cache_busy_handle_wait(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheBusyHandle,\n    ) -> Result<types::CacheHandle, Error> {\n        let handle = handle.into();\n        // Swap out for a distinct handle, so we don't hit a repeated `close`+`close_busy`:\n        let entry = self.cache_entry_mut(handle).await?;\n        let mut other_entry = entry.stub();\n        std::mem::swap(entry, &mut other_entry);\n        let task = PeekableTask::spawn(Box::pin(async move { Ok(other_entry) })).await;\n        Ok(self.insert_cache_op(PendingCacheTask::new(task)).into())\n    }\n\n    async fn transaction_insert(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n        options_mask: types::CacheWriteOptionsMask,\n        options: wiggle::GuestPtr<types::CacheWriteOptions>,\n    ) -> Result<types::BodyHandle, Error> {\n        let (body, cache_handle) = self\n            .transaction_insert_and_stream_back(memory, handle, options_mask, options)\n            .await?;\n        // Ignore the \"stream back\" handle\n        let _ = self.take_cache_entry(cache_handle)?;\n        Ok(body)\n    }\n\n    async fn transaction_insert_and_stream_back(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n        options_mask: types::CacheWriteOptionsMask,\n        options: wiggle::GuestPtr<types::CacheWriteOptions>,\n    ) -> Result<(types::BodyHandle, types::CacheHandle), Error> {\n        let guest_options = memory.read(options)?;\n        // No request headers here; request headers come from the original lookup.\n        if options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS) {\n            return Err(Error::InvalidArgument);\n        }\n        let options = load_write_options(memory, options_mask, &guest_options)?;\n\n        // Optimistically start a body, so we don't have to reborrow &mut self\n        let body_handle = self.insert_body(Body::empty());\n        let read_body = self.begin_streaming(body_handle)?;\n\n        let e = self\n            .cache_entry_mut(handle)\n            .await?\n            .insert(options, read_body)?;\n\n        // Return a new handle for the read end.\n        let handle = self.insert_cache_op(PendingCacheTask::new(PeekableTask::complete(e)));\n\n        Ok((body_handle, handle.into()))\n    }\n\n    async fn transaction_update(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n        options_mask: types::CacheWriteOptionsMask,\n        options: wiggle::GuestPtr<types::CacheWriteOptions>,\n    ) -> Result<(), Error> {\n        let guest_options = memory.read(options)?;\n        // No request headers here; request headers come from the original lookup.\n        if options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS) {\n            return Err(Error::InvalidArgument);\n        }\n        let options = load_write_options(memory, options_mask, &guest_options)?;\n\n        let entry = self.cache_entry_mut(handle).await?;\n        // The path here is:\n        // InvalidCacheHandle -> FastlyStatus::BADF -> (ABI boundary) ->\n        // CacheError::InvalidOperation\n        entry.update(options).await?;\n\n        Ok(())\n    }\n\n    async fn transaction_cancel(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n    ) -> Result<(), Error> {\n        let entry = self.cache_entry_mut(handle).await?;\n        if entry.cancel() {\n            Ok(())\n        } else {\n            Err(Error::CacheError(crate::cache::Error::CannotWrite))\n        }\n    }\n\n    async fn close_busy(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheBusyHandle,\n    ) -> Result<(), Error> {\n        // Don't wait for the transaction to complete; drop the future to cancel.\n        let _ = self.take_cache_entry(handle.into())?;\n        Ok(())\n    }\n\n    async fn close(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n    ) -> Result<(), Error> {\n        let _ = self.take_cache_entry(handle)?.task().recv().await?;\n        Ok(())\n    }\n\n    async fn get_state(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n    ) -> Result<types::CacheLookupState, Error> {\n        let entry = self.cache_entry_mut(handle).await?;\n\n        let mut state = types::CacheLookupState::empty();\n        if let Some(found) = entry.found() {\n            // At the moment, Compute only returns FOUND if the object is fresh.\n            // We adopt the same behavior here, as SDKs (including old SDK versions) do not check\n            // the USABLE bit.\n            if found.meta().is_usable() {\n                state |= types::CacheLookupState::USABLE;\n                state |= types::CacheLookupState::FOUND;\n\n                if !found.meta().is_fresh() {\n                    state |= types::CacheLookupState::STALE;\n                }\n            }\n        }\n        if entry.go_get().is_some() {\n            state |= types::CacheLookupState::MUST_INSERT_OR_UPDATE;\n        }\n\n        Ok(state)\n    }\n\n    async fn get_user_metadata(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n        user_metadata_out_ptr: wiggle::GuestPtr<u8>,\n        user_metadata_out_len: u32,\n        nwritten_out: wiggle::GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let entry = self.cache_entry(handle).await?;\n\n        let md_bytes = entry\n            .found()\n            .map(|found| found.meta().user_metadata())\n            .ok_or(crate::Error::CacheError(crate::cache::Error::Missing))?;\n        let len: u32 = md_bytes\n            .len()\n            .try_into()\n            .expect(\"user metadata must be shorter than u32 can indicate\");\n        if len > user_metadata_out_len {\n            memory.write(nwritten_out, len)?;\n            return Err(Error::BufferLengthError {\n                buf: \"user_metadata_out_ptr\",\n                len: \"user_metadata_out_len\",\n            });\n        }\n        let user_metadata = memory\n            .as_slice_mut(user_metadata_out_ptr.as_array(user_metadata_out_len))?\n            .ok_or(Error::SharedMemory)?;\n        user_metadata[..(len as usize)].copy_from_slice(&md_bytes);\n        memory.write(nwritten_out, len)?;\n\n        Ok(())\n    }\n\n    async fn get_body(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n        mut options_mask: types::CacheGetBodyOptionsMask,\n        options: &types::CacheGetBodyOptions,\n    ) -> Result<types::BodyHandle, Error> {\n        let from = if options_mask.contains(types::CacheGetBodyOptionsMask::FROM) {\n            Some(options.from)\n        } else {\n            None\n        };\n        options_mask &= !types::CacheGetBodyOptionsMask::FROM;\n        let to = if options_mask.contains(types::CacheGetBodyOptionsMask::TO) {\n            Some(options.to)\n        } else {\n            None\n        };\n        options_mask &= !types::CacheGetBodyOptionsMask::TO;\n\n        if !options_mask.is_empty() {\n            return Err(Error::NotAvailable(\"unknown cache get_body option\"));\n        }\n\n        // We wind up re-borrowing `found` and `self.sandbox` several times here, to avoid\n        // borrowing the both of them at once.\n        // (It possible that inserting a body would change the address of Found, by re-shuffling\n        // the AsyncItems table; we have to live by borrowck's rules.)\n        //\n        // We have an exclusive borrow &mut self.sandbox for the lifetime of this call,\n        // so even though we're re-borrowing/repeating lookups, we know we won't run into TOCTOU.\n\n        let entry = self.cache_entry(handle).await?;\n\n        // Preemptively (optimistically) start a read. Don't worry, the Drop impl for Body will\n        // clean up the copying task.\n        // We have to do this to allow `found`'s lifetime to end before self.sandbox.body, which\n        // has to re-borrow self.self.sandbox.\n        let body = entry.body(from, to).await?;\n\n        let found = entry\n            .found()\n            .ok_or(Error::CacheError(crate::cache::Error::Missing))?;\n\n        if let Some(prev_handle) = found.last_body_handle {\n            // Check if they're still reading the previous handle.\n            if self.body(prev_handle).is_ok() {\n                return Err(Error::CacheError(crate::cache::Error::HandleBodyUsed));\n            }\n        };\n\n        let body_handle = self.insert_body(body);\n        // Finalize by committing the handle as \"the last read\".\n        // We have to borrow `found` again, this time as mutable.\n        self.cache_entry_mut(handle)\n            .await?\n            .found_mut()\n            .unwrap()\n            .last_body_handle = Some(body_handle);\n\n        Ok(body_handle)\n    }\n\n    async fn get_length(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n    ) -> Result<types::CacheObjectLength, Error> {\n        let found = self\n            .cache_entry(handle)\n            .await?\n            .found()\n            .ok_or(Error::CacheError(crate::cache::Error::Missing))?;\n        found\n            .length()\n            .ok_or(Error::CacheError(crate::cache::Error::Missing))\n    }\n\n    async fn get_max_age_ns(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        let entry = self.cache_entry_mut(handle).await?;\n        if let Some(found) = entry.found() {\n            Ok(found.meta().max_age().as_nanos().try_into().unwrap())\n        } else {\n            Err(Error::CacheError(crate::cache::Error::Missing))\n        }\n    }\n\n    async fn get_stale_while_revalidate_ns(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n\n    async fn get_age_ns(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        let entry = self.cache_entry_mut(handle).await?;\n        if let Some(found) = entry.found() {\n            Ok(found.meta().age().as_nanos().try_into().unwrap())\n        } else {\n            Err(Error::CacheError(crate::cache::Error::Missing))\n        }\n    }\n\n    async fn get_hits(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        handle: types::CacheHandle,\n    ) -> Result<types::CacheHitCount, Error> {\n        Err(Error::NotAvailable(\"Cache API primitives\"))\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/compute_runtime.rs",
    "content": "use crate::error::Error;\nuse crate::sandbox::Sandbox;\nuse crate::wiggle_abi::fastly_compute_runtime::FastlyComputeRuntime;\nuse std::sync::atomic::Ordering;\nuse wiggle::GuestMemory;\n\nimpl FastlyComputeRuntime for Sandbox {\n    fn get_vcpu_ms(&mut self, _memory: &mut GuestMemory<'_>) -> Result<u64, Error> {\n        // we internally track microseconds, because our wasmtime tick length\n        // is too short for ms to work. but we want to shrink this to ms to\n        // try to minimize timing attacks.\n        Ok(self.active_cpu_time_us.load(Ordering::SeqCst) / 1000)\n    }\n\n    fn get_heap_mib(&mut self, _memory: &mut GuestMemory<'_>) -> Result<u32, Error> {\n        Ok(self.get_heap_usage_mib())\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/config_store.rs",
    "content": "use super::{\n    fastly_config_store::FastlyConfigStore,\n    fastly_dictionary::FastlyDictionary,\n    types::{ConfigStoreHandle, DictionaryHandle},\n};\nuse crate::{Error, sandbox::Sandbox};\nuse wiggle::{GuestMemory, GuestPtr};\n\nimpl FastlyConfigStore for Sandbox {\n    fn open(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<str>,\n    ) -> Result<ConfigStoreHandle, Error> {\n        let dict_answer = <Self as FastlyDictionary>::open(self, memory, name)?;\n        Ok(ConfigStoreHandle::from(unsafe { dict_answer.inner() }))\n    }\n\n    fn get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        config_store: ConfigStoreHandle,\n        key: GuestPtr<str>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let dict_handle = DictionaryHandle::from(unsafe { config_store.inner() });\n        <Self as FastlyDictionary>::get(self, memory, dict_handle, key, buf, buf_len, nwritten_out)\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/device_detection_impl.rs",
    "content": "//! fastly_device_detection` hostcall implementations.\n\nuse crate::error::Error;\nuse crate::wiggle_abi::{FastlyStatus, Sandbox, fastly_device_detection::FastlyDeviceDetection};\nuse std::convert::TryFrom;\nuse wiggle::{GuestMemory, GuestPtr};\n\n#[derive(Debug, thiserror::Error)]\npub enum DeviceDetectionError {\n    /// Device detection data for given user_agent not found.\n    #[error(\"No device detection data: {0}\")]\n    NoDeviceDetectionData(String),\n}\n\nimpl DeviceDetectionError {\n    /// Convert to an error code representation suitable for passing across the ABI boundary.\n    pub fn to_fastly_status(&self) -> FastlyStatus {\n        use DeviceDetectionError::*;\n        match self {\n            NoDeviceDetectionData(_) => FastlyStatus::None,\n        }\n    }\n}\n\nimpl FastlyDeviceDetection for Sandbox {\n    fn lookup(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        user_agent: GuestPtr<str>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let result = {\n            let user_agent_slice = memory\n                .as_slice(user_agent.as_bytes())?\n                .ok_or(Error::SharedMemory)?;\n            let user_agent_str = std::str::from_utf8(user_agent_slice)?;\n\n            self.device_detection_lookup(user_agent_str)\n                .ok_or_else(|| {\n                    DeviceDetectionError::NoDeviceDetectionData(user_agent_str.to_string())\n                })?\n        };\n\n        if result.len() > buf_len as usize {\n            memory.write(nwritten_out, u32::try_from(result.len()).unwrap_or(0))?;\n            return Err(Error::BufferLengthError {\n                buf: \"device_detection_lookup\",\n                len: \"device_detection_lookup_max_len\",\n            });\n        }\n\n        let result_len =\n            u32::try_from(result.len()).expect(\"smaller than buf_len means it must fit\");\n\n        memory.copy_from_slice(result.as_bytes(), buf.as_array(result_len))?;\n\n        memory.write(nwritten_out, result_len)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/dictionary_impl.rs",
    "content": "//! fastly_dictionary` hostcall implementations.\n\nuse {\n    crate::{\n        error::Error,\n        sandbox::Sandbox,\n        wiggle_abi::{\n            fastly_dictionary::FastlyDictionary,\n            types::{DictionaryHandle, FastlyStatus},\n        },\n    },\n    wiggle::{GuestMemory, GuestPtr},\n};\n\n#[derive(Debug, thiserror::Error)]\npub enum DictionaryError {\n    /// A dictionary item with the given key was not found.\n    #[error(\"Unknown dictionary item: {0}\")]\n    UnknownDictionaryItem(String),\n    /// A dictionary with the given name was not found.\n    #[error(\"Unknown dictionary: {0}\")]\n    UnknownDictionary(String),\n}\n\nimpl DictionaryError {\n    /// Convert to an error code representation suitable for passing across the ABI boundary.\n    pub fn to_fastly_status(&self) -> FastlyStatus {\n        use DictionaryError::*;\n        match self {\n            UnknownDictionaryItem(_) => FastlyStatus::None,\n            UnknownDictionary(_) => FastlyStatus::Badf,\n        }\n    }\n}\n\nimpl FastlyDictionary for Sandbox {\n    fn open(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<str>,\n    ) -> Result<DictionaryHandle, Error> {\n        self.dictionary_handle(memory.as_str(name)?.ok_or(Error::SharedMemory)?)\n    }\n\n    fn get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        dictionary: DictionaryHandle,\n        key: GuestPtr<str>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let dict = &self.dictionary(dictionary)?.contents;\n\n        let item_bytes = {\n            let key = memory.as_str(key)?.ok_or(Error::SharedMemory)?;\n            dict.get(key)\n                .ok_or_else(|| DictionaryError::UnknownDictionaryItem(key.to_owned()))?\n                .as_bytes()\n        };\n\n        if item_bytes.len() > usize::try_from(buf_len).expect(\"buf_len must fit in usize\") {\n            // Write out the number of bytes necessary to fit this item, or zero on overflow to\n            // signal an error condition. This is probably unnecessary, as config store entries\n            // may be at most 8000 utf-8 characters large.\n            memory.write(nwritten_out, u32::try_from(item_bytes.len()).unwrap_or(0))?;\n            return Err(Error::BufferLengthError {\n                buf: \"dictionary_item\",\n                len: \"dictionary_item_max_len\",\n            });\n        }\n\n        // We know the conversion of item_bytes.len() to u32 will succeed, as it's <= buf_len.\n        let item_len = u32::try_from(item_bytes.len()).unwrap();\n\n        memory.write(nwritten_out, item_len)?;\n        memory.copy_from_slice(item_bytes, buf.as_array(item_len))?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/entity.rs",
    "content": "//! [`cranelift_entity::EntityRef`][ref] implementations for ABI types.\n//!\n//! [ref]: https://docs.rs/cranelift-entity/latest/cranelift_entity/trait.EntityRef.html\n\nuse super::types::{\n    AclHandle, AsyncItemHandle, BodyHandle, DictionaryHandle, EndpointHandle, KvStoreHandle,\n    ObjectStoreHandle, PendingRequestHandle, RequestHandle, ResponseHandle, SecretHandle,\n    SecretStoreHandle,\n};\n\n/// Macro which implements a 32-bit entity reference for handles generated by Wiggle.\n///\n/// This is for all intents and purposes, a use-case specific version of the [`entity_impl`][impl]\n/// macro provided by [`cranelift-entity`][entity]. For handles generated by a call to\n/// [`wiggle::from_witx`][from-witx], we have to implement entity reference trait slightly\n/// differently than normal, due to these types having a private constructor. Instead, we use their\n/// `From<u32>` trait implementations to convert back and forth from `usize` values.\n///\n/// [entity]: https://docs.rs/cranelift-entity/latest/cranelift_entity/\n/// [from-witx]: https://docs.rs/wiggle/latest/wiggle/macro.from_witx.html\n/// [impl]: https://docs.rs/cranelift-entity/latest/cranelift_entity/macro.entity_impl.html\n// TODO KTM 2020-06-29: If this ever becomes a maintenance burden, this could be submitted upstream\n// as an alternative mode for the `cranelift_entity::entity_impl` macro.\nmacro_rules! wiggle_entity {\n    ($entity:ident) => {\n        /// `EntityRef` allows a small integer type to be used as the key to an entity map, such as\n        /// `PrimaryMap`, `SecondaryMap`, or `SparseMap`.\n        impl cranelift_entity::EntityRef for $entity {\n            /// Create a new entity reference from a small integer.\n            fn new(index: usize) -> Self {\n                debug_assert!(index < (u32::MAX as usize));\n                (index as u32).into()\n            }\n            /// Get the index that was used to create this entity reference.\n            fn index(self) -> usize {\n                let i: u32 = self.into();\n                i as usize\n            }\n        }\n    };\n}\n\nwiggle_entity!(AclHandle);\nwiggle_entity!(AsyncItemHandle);\nwiggle_entity!(BodyHandle);\nwiggle_entity!(DictionaryHandle);\nwiggle_entity!(EndpointHandle);\nwiggle_entity!(KvStoreHandle);\nwiggle_entity!(ObjectStoreHandle);\nwiggle_entity!(PendingRequestHandle);\nwiggle_entity!(RequestHandle);\nwiggle_entity!(ResponseHandle);\nwiggle_entity!(SecretHandle);\nwiggle_entity!(SecretStoreHandle);\n"
  },
  {
    "path": "src/wiggle_abi/erl_impl.rs",
    "content": "use crate::{\n    error::Error,\n    sandbox::Sandbox,\n    wiggle_abi::fastly_erl::FastlyErl,\n    wiggle_abi::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyErl for Sandbox {\n    fn check_rate(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _rc: GuestPtr<str>,\n        _entry: GuestPtr<str>,\n        _delta: u32,\n        _window: u32,\n        _limit: u32,\n        _pb: GuestPtr<str>,\n        _ttl: u32,\n    ) -> std::result::Result<u32, Error> {\n        Ok(0)\n    }\n\n    fn ratecounter_increment(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _rc: GuestPtr<str>,\n        _entry: GuestPtr<str>,\n        _delta: u32,\n    ) -> std::result::Result<(), Error> {\n        Ok(())\n    }\n\n    fn ratecounter_lookup_rate(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _rc: GuestPtr<str>,\n        _entry: GuestPtr<str>,\n        _window: u32,\n    ) -> std::result::Result<u32, Error> {\n        Ok(0)\n    }\n\n    fn ratecounter_lookup_count(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _rc: GuestPtr<str>,\n        _entry: GuestPtr<str>,\n        _duration: u32,\n    ) -> std::result::Result<u32, Error> {\n        Ok(0)\n    }\n\n    fn penaltybox_add(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _pb: GuestPtr<str>,\n        _entry: GuestPtr<str>,\n        _ttl: u32,\n    ) -> std::result::Result<(), Error> {\n        Ok(())\n    }\n\n    fn penaltybox_has(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _pb: GuestPtr<str>,\n        _entry: GuestPtr<str>,\n    ) -> std::result::Result<u32, Error> {\n        Ok(0)\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/fastly_purge_impl.rs",
    "content": "//! fastly_purge` hostcall implementations.\n\nuse {\n    super::types::{PurgeOptions, PurgeOptionsMask},\n    crate::{error::Error, sandbox::Sandbox, wiggle_abi::fastly_purge::FastlyPurge},\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyPurge for Sandbox {\n    fn purge_surrogate_key(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        surrogate_key: GuestPtr<str>,\n        mut options_mask: PurgeOptionsMask,\n        _options: GuestPtr<PurgeOptions>,\n    ) -> Result<(), Error> {\n        let soft_purge = options_mask.contains(PurgeOptionsMask::SOFT_PURGE);\n        options_mask &= !PurgeOptionsMask::SOFT_PURGE;\n\n        if options_mask != PurgeOptionsMask::empty() {\n            return Err(Error::Unsupported {\n                msg: \"unsupported purge option\",\n            });\n        }\n\n        let key = memory\n            .as_str(surrogate_key)?\n            .ok_or(Error::SharedMemory)?\n            .parse()?;\n        let purged = self.cache().purge(key, soft_purge);\n        tracing::debug!(\"{purged} variants purged\");\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/geo_impl.rs",
    "content": "//! fastly_geo` hostcall implementations.\n\nuse std::{\n    convert::TryInto,\n    net::{IpAddr, Ipv4Addr, Ipv6Addr},\n};\n\nuse {\n    crate::{error::Error, sandbox::Sandbox, wiggle_abi::fastly_geo::FastlyGeo},\n    std::convert::TryFrom,\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyGeo for Sandbox {\n    fn lookup(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        addr_octets: GuestPtr<u8>,\n        addr_len: u32,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let octets = memory.to_vec(addr_octets.as_array(addr_len))?;\n\n        let ip_addr: IpAddr = match addr_len {\n            4 => IpAddr::V4(Ipv4Addr::from(\n                TryInto::<[u8; 4]>::try_into(octets).unwrap(),\n            )),\n            16 => IpAddr::V6(Ipv6Addr::from(\n                TryInto::<[u8; 16]>::try_into(octets).unwrap(),\n            )),\n            _ => return Err(Error::InvalidArgument),\n        };\n\n        let result = self.geolocation_lookup(&ip_addr).unwrap_or_default();\n\n        if result.len() > buf_len as usize {\n            memory.write(nwritten_out, u32::try_from(result.len()).unwrap_or(0))?;\n            return Err(Error::BufferLengthError {\n                buf: \"geolocation_lookup\",\n                len: \"geolocation_lookup_max_len\",\n            });\n        }\n\n        let result_len =\n            u32::try_from(result.len()).expect(\"smaller than value_max_len means it must fit\");\n\n        memory.copy_from_slice(result.as_bytes(), buf.as_array(result_len))?;\n        memory.write(nwritten_out, result_len)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/headers.rs",
    "content": "use {\n    crate::{error::Error, wiggle_abi::MultiValueWriter, wiggle_abi::types},\n    http::{HeaderMap, HeaderValue, header::HeaderName},\n    wiggle::{GuestMemory, GuestPtr},\n};\n\n/// This constant reflects a similar constant within Hyper, which will panic\n/// if given header names longer than this value.\npub const MAX_HEADER_NAME_LEN: u32 = (1 << 16) - 1;\n\npub(crate) trait HttpHeaders {\n    fn names_get(\n        &self,\n        memory: &mut GuestMemory<'_>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: types::MultiValueCursor,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<types::MultiValueCursorResult, Error>;\n\n    fn value_get(\n        &self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<u8>,\n        value_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error>;\n\n    fn values_get(\n        &self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: types::MultiValueCursor,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<types::MultiValueCursorResult, Error>;\n\n    fn values_set(\n        &mut self,\n        memory: &GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        values: GuestPtr<[u8]>,\n    ) -> Result<(), Error>;\n\n    fn insert(\n        &mut self,\n        memory: &GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error>;\n\n    fn append(\n        &mut self,\n        memory: &GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error>;\n\n    fn remove(&mut self, memory: &GuestMemory<'_>, name: GuestPtr<[u8]>) -> Result<(), Error>;\n}\n\nimpl HttpHeaders for HeaderMap<HeaderValue> {\n    fn names_get(\n        &self,\n        memory: &mut GuestMemory<'_>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: types::MultiValueCursor,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<types::MultiValueCursorResult, Error> {\n        // order consistency: \"The iteration order is arbitrary, but consistent across platforms for the\n        // same crate version.\"\n        let mut names_iter = self.keys();\n        // Write the values to guest memory\n        names_iter.write_values(memory, b'\\0', buf.as_array(buf_len), cursor, nwritten_out)\n    }\n\n    fn value_get(\n        &self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        value_ptr: GuestPtr<u8>,\n        value_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument);\n        }\n\n        let value = {\n            let name = HeaderName::from_bytes(memory.as_slice(name)?.ok_or(Error::SharedMemory)?)?;\n            self.get(&name).ok_or(Error::InvalidArgument)?\n        };\n\n        let value_bytes = value.as_ref();\n        if value_bytes.len() > value_max_len as usize {\n            // Write out the number of bytes necessary to fit this header value, or zero on overflow\n            // to signal an error condition.\n            memory.write(nwritten_out, value_bytes.len().try_into().unwrap_or(0))?;\n            return Err(Error::BufferLengthError {\n                buf: \"value\",\n                len: \"value_max_len\",\n            });\n        }\n        let value_len =\n            u32::try_from(value_bytes.len()).expect(\"smaller than value_max_len means it must fit\");\n        memory.copy_from_slice(value_bytes, value_ptr.as_array(value_len))?;\n        memory.write(nwritten_out, value_len)?;\n\n        Ok(())\n    }\n\n    fn values_get(\n        &self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: types::MultiValueCursor,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<types::MultiValueCursorResult, Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument);\n        }\n\n        let mut values_iter = {\n            let name = HeaderName::from_bytes(memory.as_slice(name)?.ok_or(Error::SharedMemory)?)?;\n            self.get_all(&name).iter()\n        };\n\n        values_iter.write_values(memory, b'\\0', buf.as_array(buf_len), cursor, nwritten_out)\n    }\n\n    fn values_set(\n        &mut self,\n        memory: &GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        values: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument);\n        }\n\n        let name = HeaderName::from_bytes(memory.as_slice(name)?.ok_or(Error::SharedMemory)?)?;\n        let values = {\n            let values_bytes = memory.as_slice(values)?.ok_or(Error::SharedMemory)?;\n            // split slice along nul bytes\n            let mut iter = values_bytes.split(|b| *b == 0);\n            // drop the empty item at the end\n            iter.next_back();\n            iter.map(HeaderValue::from_bytes)\n                .collect::<Result<Vec<HeaderValue>, _>>()?\n        };\n\n        // Remove any values if they exist\n        if let http::header::Entry::Occupied(e) = self.entry(&name) {\n            e.remove_entry_mult();\n        }\n\n        // Add all the new values\n        for value in values {\n            self.append(&name, value);\n        }\n        Ok(())\n    }\n\n    fn insert(\n        &mut self,\n        memory: &GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument);\n        }\n\n        let name = HeaderName::from_bytes(memory.as_slice(name)?.ok_or(Error::SharedMemory)?)?;\n        let value = HeaderValue::from_bytes(memory.as_slice(value)?.ok_or(Error::SharedMemory)?)?;\n        self.insert(name, value);\n        Ok(())\n    }\n\n    fn append(\n        &mut self,\n        memory: &GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument);\n        }\n\n        let name = HeaderName::from_bytes(memory.as_slice(name)?.ok_or(Error::SharedMemory)?)?;\n        let value = HeaderValue::from_bytes(memory.as_slice(value)?.ok_or(Error::SharedMemory)?)?;\n        self.append(name, value);\n        Ok(())\n    }\n\n    fn remove(&mut self, memory: &GuestMemory<'_>, name: GuestPtr<[u8]>) -> Result<(), Error> {\n        if name.len() > MAX_HEADER_NAME_LEN {\n            return Err(Error::InvalidArgument);\n        }\n\n        let name = HeaderName::from_bytes(memory.as_slice(name)?.ok_or(Error::SharedMemory)?)?;\n        let _ = self.remove(name).ok_or(Error::InvalidArgument)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/http_cache.rs",
    "content": "use crate::sandbox::Sandbox;\n\nuse super::fastly_http_cache::FastlyHttpCache;\nuse super::{Error, types};\n\nuse wiggle::{GuestMemory, GuestPtr};\n\n#[allow(unused_variables)]\nimpl FastlyHttpCache for Sandbox {\n    async fn lookup(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        request: types::RequestHandle,\n        options_mask: types::HttpCacheLookupOptionsMask,\n        options: GuestPtr<types::HttpCacheLookupOptions>,\n    ) -> Result<types::HttpCacheHandle, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn transaction_lookup(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        request: types::RequestHandle,\n        options_mask: types::HttpCacheLookupOptionsMask,\n        options: GuestPtr<types::HttpCacheLookupOptions>,\n    ) -> Result<types::HttpCacheHandle, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn transaction_insert(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        response_handle: types::ResponseHandle,\n        options_mask: types::HttpCacheWriteOptionsMask,\n        abi_options: GuestPtr<types::HttpCacheWriteOptions>,\n    ) -> Result<types::BodyHandle, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn transaction_insert_and_stream_back(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        response_handle: types::ResponseHandle,\n        options_mask: types::HttpCacheWriteOptionsMask,\n        abi_options: GuestPtr<types::HttpCacheWriteOptions>,\n    ) -> Result<(types::BodyHandle, types::HttpCacheHandle), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn transaction_update(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        response_handle: types::ResponseHandle,\n        options_mask: types::HttpCacheWriteOptionsMask,\n        abi_options: GuestPtr<types::HttpCacheWriteOptions>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn transaction_update_and_return_fresh(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        response_handle: types::ResponseHandle,\n        options_mask: types::HttpCacheWriteOptionsMask,\n        abi_options: GuestPtr<types::HttpCacheWriteOptions>,\n    ) -> Result<types::HttpCacheHandle, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn transaction_record_not_cacheable(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        options_mask: types::HttpCacheWriteOptionsMask,\n        abi_options: GuestPtr<types::HttpCacheWriteOptions>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn transaction_abandon(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn transaction_choose_stale(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn close(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    fn is_request_cacheable(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        request_handle: types::RequestHandle,\n    ) -> Result<u32, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    fn get_suggested_cache_key(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        request_handle: types::RequestHandle,\n        key_out_ptr: GuestPtr<u8>,\n        key_out_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_suggested_backend_request(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::RequestHandle, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_suggested_cache_options(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        response_handle: types::ResponseHandle,\n        options_wanted: types::HttpCacheWriteOptionsMask,\n        pointers: GuestPtr<types::HttpCacheWriteOptions>,\n        pointer_mask_out: GuestPtr<types::HttpCacheWriteOptionsMask>,\n        options_out: GuestPtr<types::HttpCacheWriteOptions>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn prepare_response_for_storage(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        response_handle: types::ResponseHandle,\n    ) -> Result<(types::HttpStorageAction, types::ResponseHandle), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_found_response(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        transform_for_client: u32,\n    ) -> Result<(types::ResponseHandle, types::BodyHandle), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_state(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::CacheLookupState, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_length(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::CacheObjectLength, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_max_age_ns(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_stale_while_revalidate_ns(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_stale_if_error_ns(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_age_ns(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::CacheDurationNs, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_hits(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::CacheHitCount, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_sensitive_data(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n    ) -> Result<types::IsSensitive, Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_surrogate_keys(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        surrogate_keys_out_ptr: GuestPtr<u8>,\n        surrogate_keys_out_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n\n    async fn get_vary_rule(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cache_handle: types::HttpCacheHandle,\n        vary_rule_out_ptr: GuestPtr<u8>,\n        vary_rule_out_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"HTTP Cache API primitives\"))\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/http_downstream.rs",
    "content": "use std::net::IpAddr;\nuse std::time::Duration;\n\nuse crate::error::Error;\nuse crate::sandbox::{AsyncItemHandle, Sandbox};\nuse crate::wiggle_abi::fastly_http_downstream::FastlyHttpDownstream;\nuse crate::wiggle_abi::headers::HttpHeaders;\nuse crate::wiggle_abi::types::{\n    BodyHandle, ClientCertVerifyResult, MultiValueCursor, MultiValueCursorResult,\n    NextRequestOptions, NextRequestOptionsMask, RequestHandle, RequestPromiseHandle,\n};\n\nuse wiggle::{GuestMemory, GuestPtr};\n\nimpl FastlyHttpDownstream for Sandbox {\n    async fn next_request(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        options_mask: NextRequestOptionsMask,\n        options: GuestPtr<NextRequestOptions>,\n    ) -> Result<RequestPromiseHandle, Error> {\n        let options = memory.read(options)?;\n        let timeout = options_mask\n            .contains(NextRequestOptionsMask::TIMEOUT)\n            .then(|| Duration::from_millis(options.timeout_ms));\n        let handle = self.register_pending_downstream_req(timeout).await?;\n        Ok(handle.as_u32().into())\n    }\n\n    async fn next_request_abandon(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestPromiseHandle,\n    ) -> Result<(), Error> {\n        let handle = AsyncItemHandle::from_u32(handle.into());\n        self.abandon_pending_downstream_req(handle)?;\n        Ok(())\n    }\n\n    async fn next_request_wait(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestPromiseHandle,\n    ) -> Result<(RequestHandle, BodyHandle), Error> {\n        let handle = AsyncItemHandle::from_u32(handle.into());\n        let Some((req, body)) = self.await_downstream_req(handle).await? else {\n            return Err(Error::ValueAbsent);\n        };\n        Ok((req, body))\n    }\n\n    fn downstream_original_header_names(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: MultiValueCursor,\n        ending_cursor_out: GuestPtr<MultiValueCursorResult>,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let headers = self\n            .downstream_original_headers(handle)?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n\n        multi_value_result!(\n            memory,\n            headers.names_get(memory, buf, buf_len, cursor, nwritten_out),\n            ending_cursor_out\n        )\n    }\n\n    fn downstream_original_header_count(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        let headers = self\n            .downstream_original_headers(handle)?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n\n        Ok(headers\n            .len()\n            .try_into()\n            .expect(\"More than u32::MAX headers\"))\n    }\n\n    fn downstream_server_ip_addr(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        // Must be a 16-byte array:\n        addr_octets_ptr: GuestPtr<u8>,\n    ) -> Result<u32, Error> {\n        let ip = self\n            .downstream_server_ip(handle)?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n\n        match ip {\n            IpAddr::V4(addr) => {\n                let octets = addr.octets();\n                let octets_bytes = octets.len() as u32;\n                debug_assert_eq!(octets_bytes, 4);\n                memory.copy_from_slice(&octets, addr_octets_ptr.as_array(octets_bytes))?;\n                Ok(octets_bytes)\n            }\n            IpAddr::V6(addr) => {\n                let octets = addr.octets();\n                let octets_bytes = octets.len() as u32;\n                debug_assert_eq!(octets_bytes, 16);\n                memory.copy_from_slice(&octets, addr_octets_ptr.as_array(octets_bytes))?;\n                Ok(octets_bytes)\n            }\n        }\n    }\n\n    fn downstream_client_ip_addr(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        // Must be a 16-byte array:\n        addr_octets_ptr: GuestPtr<u8>,\n    ) -> Result<u32, Error> {\n        let ip = self\n            .downstream_client_ip(handle)?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n\n        match ip {\n            IpAddr::V4(addr) => {\n                let octets = addr.octets();\n                let octets_bytes = octets.len() as u32;\n                debug_assert_eq!(octets_bytes, 4);\n                memory.copy_from_slice(&octets, addr_octets_ptr.as_array(octets_bytes))?;\n                Ok(octets_bytes)\n            }\n            IpAddr::V6(addr) => {\n                let octets = addr.octets();\n                let octets_bytes = octets.len() as u32;\n                debug_assert_eq!(octets_bytes, 16);\n                memory.copy_from_slice(&octets, addr_octets_ptr.as_array(octets_bytes))?;\n                Ok(octets_bytes)\n            }\n        }\n    }\n\n    fn downstream_client_h2_fingerprint(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _h2fp_out: GuestPtr<u8>,\n        _h2fp_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_client_request_id(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        reqid_out: GuestPtr<u8>,\n        reqid_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let reqid = self\n            .downstream_request_id(handle)?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n        let reqid_bytes = format!(\"{:032x}\", reqid).into_bytes();\n\n        if reqid_bytes.len() > reqid_max_len as usize {\n            // Write out the number of bytes necessary to fit the value, or zero on overflow to\n            // signal an error condition.\n            memory.write(nwritten_out, reqid_bytes.len().try_into().unwrap_or(0))?;\n            return Err(Error::BufferLengthError {\n                buf: \"reqid_out\",\n                len: \"reqid_max_len\",\n            });\n        }\n\n        let reqid_len =\n            u32::try_from(reqid_bytes.len()).expect(\"smaller u32::MAX means it must fit\");\n\n        memory.copy_from_slice(&reqid_bytes, reqid_out.as_array(reqid_len))?;\n        memory.write(nwritten_out, reqid_len)?;\n        Ok(())\n    }\n\n    fn downstream_client_oh_fingerprint(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _ohfp_out: GuestPtr<u8>,\n        _ohfp_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_client_ddos_detected(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        Ok(0)\n    }\n\n    fn downstream_tls_cipher_openssl_name(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _cipher_out: GuestPtr<u8>,\n        _cipher_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_tls_protocol(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _protocol_out: GuestPtr<u8>,\n        _protocol_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_tls_client_servername(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _sni_out: GuestPtr<u8>,\n        _sni_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_tls_client_hello(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _chello_out: GuestPtr<u8>,\n        _chello_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_tls_raw_client_certificate(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _cert_out: GuestPtr<u8>,\n        _cert_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_tls_client_cert_verify_result(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<ClientCertVerifyResult, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_tls_ja3_md5(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _ja3_md5_out: GuestPtr<u8>,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_tls_ja4(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _ja4_out: GuestPtr<u8>,\n        _ja4_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_compliance_region(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        // Must be a 16-byte array:\n        region_out: GuestPtr<u8>,\n        region_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let region = Sandbox::downstream_compliance_region(self, handle)?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n        let region_len = region.len();\n\n        match u32::try_from(region_len) {\n            Ok(region_len) if region_len <= region_max_len => {\n                memory.copy_from_slice(region.as_bytes(), region_out.as_array(region_len))?;\n                memory.write(nwritten_out, region_len)?;\n\n                Ok(())\n            }\n            too_large => {\n                memory.write(nwritten_out, too_large.unwrap_or(0))?;\n\n                Err(Error::BufferLengthError {\n                    buf: \"region_out\",\n                    len: \"region_max_len\",\n                })\n            }\n        }\n    }\n\n    fn fastly_key_is_valid(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.check_fastly_key(handle).map(u32::from)\n    }\n\n    fn downstream_bot_analyzed(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        Ok(0)\n    }\n\n    fn downstream_bot_detected(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        Ok(0)\n    }\n\n    fn downstream_bot_name(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _bot_name_out: GuestPtr<u8>,\n        _bot_name_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_bot_category(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _bot_category_out: GuestPtr<u8>,\n        _bot_category_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_bot_category_kind(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_bot_verified(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_anonymous(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_anonymous_vpn(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_hosting_provider(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_proxy_over_vpn(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_public_proxy(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_relay_proxy(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_residential_proxy(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_smart_dns_proxy(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_tor_exit_node(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_is_vpn_datacenter(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n    ) -> Result<u32, Error> {\n        self.absent_metadata_value(handle)\n    }\n\n    fn downstream_resvpnproxy_vpn_service_name(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        handle: RequestHandle,\n        _name_out: GuestPtr<u8>,\n        _name_max_len: u32,\n        _nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        self.absent_metadata_value(handle)\n    }\n}\n\ntrait MetadataView {\n    /// Stub for metadata that Viceroy does not support. Validates the handle normally, but always returns Error::ValueAbsent rather than a meaningful value.\n    fn absent_metadata_value<T>(&self, handle: RequestHandle) -> Result<T, Error>;\n}\nimpl MetadataView for Sandbox {\n    fn absent_metadata_value<T>(&self, handle: RequestHandle) -> Result<T, Error> {\n        let _ = self\n            .downstream_metadata(handle)?\n            .ok_or(Error::MissingDownstreamMetadata)?;\n        Err(Error::ValueAbsent)\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/image_optimizer.rs",
    "content": "use crate::error::Error;\nuse crate::sandbox::Sandbox;\nuse crate::wiggle_abi::{fastly_image_optimizer, types};\nuse wiggle::{GuestMemory, GuestPtr};\n\nimpl fastly_image_optimizer::FastlyImageOptimizer for Sandbox {\n    async fn transform_image_optimizer_request(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _origin_image_request: types::RequestHandle,\n        _origin_image_request_body: types::BodyHandle,\n        _origin_image_request_backend: GuestPtr<str>,\n        _io_transform_config_mask: types::ImageOptimizerTransformConfigOptions,\n        _io_transform_config: GuestPtr<types::ImageOptimizerTransformConfig>,\n        _io_error_detail: GuestPtr<types::ImageOptimizerErrorDetail>,\n    ) -> Result<(types::ResponseHandle, types::BodyHandle), Error> {\n        Err(Error::Unsupported {\n            msg: \"image optimizer unsupported in Viceroy\",\n        })\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/kv_store_impl.rs",
    "content": "//! fastly_obj_store` hostcall implementations.\n\nuse crate::object_store::KvStoreError;\nuse crate::sandbox::PeekableTask;\nuse crate::sandbox::{\n    PendingKvDeleteTask, PendingKvInsertTask, PendingKvListTask, PendingKvLookupTask,\n};\n\nuse {\n    crate::{\n        error::Error,\n        object_store::{ObjectKey, ObjectStoreError},\n        sandbox::Sandbox,\n        wiggle_abi::{\n            fastly_kv_store::FastlyKvStore,\n            types::{\n                BodyHandle, KvDeleteConfig, KvDeleteConfigOptions, KvError, KvInsertConfig,\n                KvInsertConfigOptions, KvListConfig, KvListConfigOptions, KvLookupConfig,\n                KvLookupConfigOptions, KvStoreDeleteHandle, KvStoreHandle, KvStoreInsertHandle,\n                KvStoreListHandle, KvStoreLookupHandle,\n            },\n        },\n    },\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyKvStore for Sandbox {\n    fn open(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<str>,\n    ) -> Result<KvStoreHandle, Error> {\n        let name = memory.as_str(name)?.ok_or(Error::SharedMemory)?;\n        if self.kv_store().store_exists(name)? {\n            Ok(self.kv_store_handle(name))\n        } else {\n            Err(Error::ObjectStoreError(\n                ObjectStoreError::UnknownObjectStore(name.to_owned()),\n            ))\n        }\n    }\n\n    async fn lookup(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        store: KvStoreHandle,\n        key: GuestPtr<str>,\n        _lookup_config_mask: KvLookupConfigOptions,\n        _lookup_configuration: GuestPtr<KvLookupConfig>,\n        handle_out: GuestPtr<KvStoreLookupHandle>,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store).unwrap();\n        let key = ObjectKey::new(memory.as_str(key)?.ok_or(Error::SharedMemory)?.to_string())\n            .map_err(|_| KvStoreError::BadRequest)?;\n        // just create a future that's already ready\n        let fut = futures::future::ok(self.obj_lookup(store.clone(), key));\n        let task = PeekableTask::spawn(fut).await;\n        memory.write(\n            handle_out,\n            self.insert_pending_kv_lookup(PendingKvLookupTask::new(task))\n                .into(),\n        )?;\n        Ok(())\n    }\n\n    async fn lookup_wait(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_kv_lookup_handle: KvStoreLookupHandle,\n        body_handle_out: GuestPtr<BodyHandle>,\n        metadata_buf: GuestPtr<u8>,\n        metadata_buf_len: u32,\n        nwritten_out: GuestPtr<u32>,\n        generation_out: GuestPtr<u32>,\n        kv_error_out: GuestPtr<KvError>,\n    ) -> Result<(), Error> {\n        let resp = self\n            .take_pending_kv_lookup(pending_kv_lookup_handle.into())?\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(Some(value)) => {\n                let body_handle = self.insert_body(value.body.into());\n\n                memory.write(body_handle_out, body_handle)?;\n                match value.metadata_len {\n                    0 => memory.write(nwritten_out, 0)?,\n                    len => {\n                        let meta_len_u32 =\n                            u32::try_from(len).expect(\"metadata len is outside the bounds of u32\");\n                        memory.write(nwritten_out, meta_len_u32)?;\n                        if meta_len_u32 > metadata_buf_len {\n                            return Err(Error::BufferLengthError {\n                                buf: \"metadata\",\n                                len: \"specified length\",\n                            });\n                        }\n                        memory.copy_from_slice(\n                            value.metadata.as_bytes(),\n                            metadata_buf.as_array(meta_len_u32),\n                        )?;\n                    }\n                }\n                memory.write(generation_out, 0)?;\n                memory.write(kv_error_out, KvError::Ok)?;\n                Ok(())\n            }\n            Ok(None) => {\n                memory.write(kv_error_out, KvError::NotFound)?;\n                Ok(())\n            }\n            Err(e) => {\n                memory.write(kv_error_out, (&e).into())?;\n                Ok(())\n            }\n        }\n    }\n\n    async fn lookup_wait_v2(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_kv_lookup_handle: KvStoreLookupHandle,\n        body_handle_out: GuestPtr<BodyHandle>,\n        metadata_buf: GuestPtr<u8>,\n        metadata_buf_len: u32,\n        nwritten_out: GuestPtr<u32>,\n        generation_out: GuestPtr<u64>,\n        kv_error_out: GuestPtr<KvError>,\n    ) -> Result<(), Error> {\n        let resp = self\n            .take_pending_kv_lookup(pending_kv_lookup_handle.into())?\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(Some(value)) => {\n                let body_handle = self.insert_body(value.body.into());\n\n                memory.write(body_handle_out, body_handle)?;\n                match value.metadata_len {\n                    0 => memory.write(nwritten_out, 0)?,\n                    len => {\n                        let meta_len_u32 =\n                            u32::try_from(len).expect(\"metadata len is outside the bounds of u32\");\n                        memory.write(nwritten_out, meta_len_u32)?;\n                        if meta_len_u32 > metadata_buf_len {\n                            return Err(Error::BufferLengthError {\n                                buf: \"metadata\",\n                                len: \"specified length\",\n                            });\n                        }\n                        memory.copy_from_slice(\n                            value.metadata.as_bytes(),\n                            metadata_buf.as_array(meta_len_u32),\n                        )?;\n                    }\n                }\n                memory.write(generation_out, value.generation)?;\n                memory.write(kv_error_out, KvError::Ok)?;\n                Ok(())\n            }\n            Ok(None) => {\n                memory.write(kv_error_out, KvError::NotFound)?;\n                Ok(())\n            }\n            Err(e) => {\n                memory.write(kv_error_out, (&e).into())?;\n                Ok(())\n            }\n        }\n    }\n\n    async fn insert(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        store: KvStoreHandle,\n        key: GuestPtr<str>,\n        body_handle: BodyHandle,\n        insert_config_mask: KvInsertConfigOptions,\n        insert_configuration: GuestPtr<KvInsertConfig>,\n        pending_handle_out: GuestPtr<KvStoreInsertHandle>,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store).unwrap().clone();\n        let key = ObjectKey::new(memory.as_str(key)?.ok_or(Error::SharedMemory)?.to_string())\n            .map_err(|_| KvStoreError::BadRequest)?;\n        let body = self.take_body(body_handle)?.read_into_vec().await?;\n\n        let config = memory.read(insert_configuration)?;\n\n        let config_str_or_none = |flag, str_field: GuestPtr<u8>, len_field| {\n            if insert_config_mask.contains(flag) {\n                if len_field == 0 {\n                    return Err(Error::InvalidArgument);\n                }\n\n                Ok(Some(memory.to_vec(str_field.as_array(len_field))?))\n            } else {\n                Ok(None)\n            }\n        };\n\n        let mode = config.mode;\n\n        // won't actually do anything in viceroy\n        // let bgf = insert_config_mask.contains(KvInsertConfigOptions::BACKGROUND_FETCH);\n\n        let igm = if insert_config_mask.contains(KvInsertConfigOptions::IF_GENERATION_MATCH) {\n            Some(config.if_generation_match)\n        } else {\n            None\n        };\n\n        let meta = config_str_or_none(\n            KvInsertConfigOptions::METADATA,\n            config.metadata,\n            config.metadata_len,\n        )?;\n        let meta = if let Some(meta) = meta {\n            Some(String::from_utf8(meta).map_err(|_| Error::InvalidArgument)?)\n        } else {\n            None\n        };\n\n        let ttl = if insert_config_mask.contains(KvInsertConfigOptions::TIME_TO_LIVE_SEC) {\n            Some(std::time::Duration::from_secs(\n                config.time_to_live_sec as u64,\n            ))\n        } else {\n            None\n        };\n\n        let fut = futures::future::ok(self.kv_insert(store, key, body, Some(mode), igm, meta, ttl));\n        let task = PeekableTask::spawn(fut).await;\n        memory.write(\n            pending_handle_out,\n            self.insert_pending_kv_insert(PendingKvInsertTask::new(task)),\n        )?;\n\n        Ok(())\n    }\n\n    async fn insert_wait(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_insert_handle: KvStoreInsertHandle,\n        kv_error_out: GuestPtr<KvError>,\n    ) -> Result<(), Error> {\n        let resp = self\n            .take_pending_kv_insert(pending_insert_handle.into())?\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(_) => {\n                memory.write(kv_error_out, KvError::Ok)?;\n                Ok(())\n            }\n            Err(e) => {\n                memory.write(kv_error_out, (&e).into())?;\n                Ok(())\n            }\n        }\n    }\n\n    async fn delete(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        store: KvStoreHandle,\n        key: GuestPtr<str>,\n        _delete_config_mask: KvDeleteConfigOptions,\n        _delete_configuration: GuestPtr<KvDeleteConfig>,\n        pending_handle_out: GuestPtr<KvStoreDeleteHandle>,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store).unwrap().clone();\n        let key = ObjectKey::new(memory.as_str(key)?.ok_or(Error::SharedMemory)?.to_string())\n            .map_err(|_| KvStoreError::BadRequest)?;\n        let fut = futures::future::ok(self.kv_delete(store, key));\n        let task = PeekableTask::spawn(fut).await;\n        memory.write(\n            pending_handle_out,\n            self.insert_pending_kv_delete(PendingKvDeleteTask::new(task))\n                .into(),\n        )?;\n        Ok(())\n    }\n\n    async fn delete_wait(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_delete_handle: KvStoreDeleteHandle,\n        kv_error_out: GuestPtr<KvError>,\n    ) -> Result<(), Error> {\n        let resp = self\n            .take_pending_kv_delete(pending_delete_handle.into())?\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(_) => {\n                memory.write(kv_error_out, KvError::Ok)?;\n                Ok(())\n            }\n            Err(e) => {\n                memory.write(kv_error_out, (&e).into())?;\n                Ok(())\n            }\n        }\n    }\n\n    async fn list(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        store: KvStoreHandle,\n        list_config_mask: KvListConfigOptions,\n        list_configuration: GuestPtr<KvListConfig>,\n        pending_handle_out: GuestPtr<KvStoreListHandle>,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store).unwrap().clone();\n\n        let config = memory.read(list_configuration)?;\n\n        let config_string_or_none = |flag, str_field: GuestPtr<u8>, len_field| {\n            if list_config_mask.contains(flag) {\n                if len_field == 0 {\n                    return Err(Error::InvalidArgument);\n                }\n\n                let byte_vec = memory.to_vec(str_field.as_array(len_field))?;\n\n                Ok(Some(\n                    String::from_utf8(byte_vec).map_err(|_| Error::InvalidArgument)?,\n                ))\n            } else {\n                Ok(None)\n            }\n        };\n\n        let cursor = config_string_or_none(\n            KvListConfigOptions::CURSOR,\n            config.cursor,\n            config.cursor_len,\n        )?;\n\n        let prefix = config_string_or_none(\n            KvListConfigOptions::PREFIX,\n            config.prefix,\n            config.prefix_len,\n        )?;\n\n        let limit = match list_config_mask.contains(KvListConfigOptions::LIMIT) {\n            true => Some(config.limit),\n            false => None,\n        };\n\n        let fut = futures::future::ok(self.kv_list(store, cursor, prefix, limit));\n        let task = PeekableTask::spawn(fut).await;\n        memory.write(\n            pending_handle_out,\n            self.insert_pending_kv_list(PendingKvListTask::new(task))\n                .into(),\n        )?;\n        Ok(())\n    }\n\n    async fn list_wait(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_kv_list_handle: KvStoreListHandle,\n        body_handle_out: GuestPtr<BodyHandle>,\n        kv_error_out: GuestPtr<KvError>,\n    ) -> Result<(), Error> {\n        let resp = self\n            .take_pending_kv_list(pending_kv_list_handle.into())?\n            .task()\n            .recv()\n            .await?;\n\n        match resp {\n            Ok(value) => {\n                let body_handle = self.insert_body(value.into());\n\n                memory.write(body_handle_out, body_handle)?;\n\n                memory.write(kv_error_out, KvError::Ok)?;\n                Ok(())\n            }\n            Err(e) => {\n                memory.write(kv_error_out, (&e).into())?;\n                Ok(())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/log_impl.rs",
    "content": "//! fastly_log` hostcall implementations.\n\nuse {\n    crate::{\n        error::Error,\n        sandbox::Sandbox,\n        wiggle_abi::{fastly_log::FastlyLog, types::EndpointHandle},\n    },\n    lazy_static::lazy_static,\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nfn is_reserved_endpoint(name: &[u8]) -> bool {\n    use regex::bytes::{RegexSet, RegexSetBuilder};\n    const RESERVED_ENDPOINTS: &[&str] = &[\"^stdout$\", \"^stderr$\", \"^fst_managed_\"];\n    lazy_static! {\n        static ref RESERVED_ENDPOINT_RE: RegexSet = RegexSetBuilder::new(RESERVED_ENDPOINTS)\n            .case_insensitive(true)\n            .build()\n            .unwrap();\n    }\n    RESERVED_ENDPOINT_RE.is_match(name)\n}\n\nimpl FastlyLog for Sandbox {\n    fn endpoint_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<[u8]>,\n    ) -> Result<EndpointHandle, Error> {\n        let name = memory.as_slice(name)?.ok_or(Error::SharedMemory)?;\n\n        if is_reserved_endpoint(name) {\n            return Err(Error::InvalidArgument);\n        }\n\n        Ok(self.log_endpoint_handle(name))\n    }\n\n    fn write(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        endpoint_handle: EndpointHandle,\n        msg: GuestPtr<[u8]>,\n    ) -> Result<u32, Error> {\n        let endpoint = self.log_endpoint(endpoint_handle)?;\n        let msg = memory.as_slice(msg)?.ok_or(Error::SharedMemory)?;\n\n        // The log API is infallible, so if we get an error, warn about it\n        // rather than bubbling it up through the log API.\n        match endpoint.write_entry(msg) {\n            Ok(()) => {}\n            Err(err) => tracing::error!(\"Error writing log message: {:?}\", err),\n        }\n\n        Ok(msg.len().try_into().unwrap())\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/obj_store_impl.rs",
    "content": "//! fastly_obj_store` hostcall implementations.\n\nuse super::types::{PendingKvDeleteHandle, PendingKvInsertHandle, PendingKvLookupHandle};\nuse crate::sandbox::PeekableTask;\nuse crate::sandbox::{PendingKvDeleteTask, PendingKvInsertTask, PendingKvLookupTask};\n\nuse {\n    crate::{\n        body::Body,\n        error::Error,\n        object_store::{ObjectKey, ObjectStoreError},\n        sandbox::Sandbox,\n        wiggle_abi::{\n            fastly_object_store::FastlyObjectStore,\n            types::{BodyHandle, ObjectStoreHandle},\n        },\n    },\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyObjectStore for Sandbox {\n    fn open(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<str>,\n    ) -> Result<ObjectStoreHandle, Error> {\n        let name = memory.as_str(name)?.ok_or(Error::SharedMemory)?;\n        if self.kv_store().store_exists(name)? {\n            Ok(self.kv_store_handle(name).into())\n        } else {\n            Err(Error::ObjectStoreError(\n                ObjectStoreError::UnknownObjectStore(name.to_owned()),\n            ))\n        }\n    }\n\n    fn lookup(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        store: ObjectStoreHandle,\n        key: GuestPtr<str>,\n        opt_body_handle_out: GuestPtr<BodyHandle>,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store.into()).unwrap();\n        let key = ObjectKey::new(memory.as_str(key)?.ok_or(Error::SharedMemory)?.to_string())?;\n        match self.obj_lookup(store.clone(), key) {\n            Ok(Some(obj)) => {\n                let new_handle = self.insert_body(Body::from(obj.body));\n                memory.write(opt_body_handle_out, new_handle)?;\n                Ok(())\n            }\n            // Don't write to the invalid handle as the SDK will return Ok(None)\n            // if the object does not exist. We need to return `Ok(())` here to\n            // make sure Viceroy does not crash\n            Ok(None) => Ok(()),\n            Err(err) => Err(err.into()),\n        }\n    }\n\n    async fn lookup_async(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        store: ObjectStoreHandle,\n        key: GuestPtr<str>,\n        opt_pending_body_handle_out: GuestPtr<PendingKvLookupHandle>,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store.into()).unwrap();\n        let key = ObjectKey::new(memory.as_str(key)?.ok_or(Error::SharedMemory)?.to_string())?;\n        // just create a future that's already ready\n        let fut = futures::future::ok(self.obj_lookup(store.clone(), key));\n        let task = PeekableTask::spawn(fut).await;\n        memory.write(\n            opt_pending_body_handle_out,\n            self.insert_pending_kv_lookup(PendingKvLookupTask::new(task)),\n        )?;\n        Ok(())\n    }\n\n    async fn pending_lookup_wait(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        pending_body_handle: PendingKvLookupHandle,\n        opt_body_handle_out: GuestPtr<BodyHandle>,\n    ) -> Result<(), Error> {\n        let pending_obj = self\n            .take_pending_kv_lookup(pending_body_handle)?\n            .task()\n            .recv()\n            .await?;\n        // proceed with the normal match from lookup()\n        match pending_obj {\n            Ok(Some(obj)) => {\n                let new_handle = self.insert_body(Body::from(obj.body));\n                memory.write(opt_body_handle_out, new_handle)?;\n                Ok(())\n            }\n            Ok(None) => Ok(()),\n            Err(err) => Err(err.into()),\n        }\n    }\n\n    async fn insert(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        store: ObjectStoreHandle,\n        key: GuestPtr<str>,\n        body_handle: BodyHandle,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store.into()).unwrap().clone();\n        let key = ObjectKey::new(memory.as_str(key)?.ok_or(Error::SharedMemory)?.to_string())?;\n        let bytes = self.take_body(body_handle)?.read_into_vec().await?;\n        self.kv_insert(store, key, bytes, None, None, None, None)?;\n\n        Ok(())\n    }\n\n    async fn insert_async(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        store: ObjectStoreHandle,\n        key: GuestPtr<str>,\n        body_handle: BodyHandle,\n        opt_pending_body_handle_out: GuestPtr<PendingKvInsertHandle>,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store.into()).unwrap().clone();\n        let key = ObjectKey::new(memory.as_str(key)?.ok_or(Error::SharedMemory)?.to_string())?;\n        let bytes = self.take_body(body_handle)?.read_into_vec().await?;\n        let fut = futures::future::ok(self.kv_insert(store, key, bytes, None, None, None, None));\n        let task = PeekableTask::spawn(fut).await;\n        memory.write(\n            opt_pending_body_handle_out,\n            self.insert_pending_kv_insert(PendingKvInsertTask::new(task))\n                .into(),\n        )?;\n        Ok(())\n    }\n\n    async fn pending_insert_wait(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        pending_insert_handle: PendingKvInsertHandle,\n    ) -> Result<(), Error> {\n        Ok((self\n            .take_pending_kv_insert(pending_insert_handle)?\n            .task()\n            .recv()\n            .await?)?)\n    }\n\n    async fn delete_async(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        store: ObjectStoreHandle,\n        key: GuestPtr<str>,\n        opt_pending_delete_handle_out: GuestPtr<PendingKvDeleteHandle>,\n    ) -> Result<(), Error> {\n        let store = self.get_kv_store_key(store.into()).unwrap().clone();\n        let key = ObjectKey::new(memory.as_str(key)?.ok_or(Error::SharedMemory)?.to_string())?;\n        let fut = futures::future::ok(self.kv_delete(store, key));\n        let task = PeekableTask::spawn(fut).await;\n        memory.write(\n            opt_pending_delete_handle_out,\n            self.insert_pending_kv_delete(PendingKvDeleteTask::new(task)),\n        )?;\n        Ok(())\n    }\n\n    async fn pending_delete_wait(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        pending_delete_handle: PendingKvDeleteHandle,\n    ) -> Result<(), Error> {\n        if !(self\n            .take_pending_kv_delete(pending_delete_handle)?\n            .task()\n            .recv()\n            .await?)?\n        {\n            Err(Error::ValueAbsent)\n        } else {\n            Ok(())\n        }\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/req_impl.rs",
    "content": "//! fastly_req` hostcall implementations.\nuse std::net::IpAddr;\n\nuse super::SecretStoreError;\nuse super::types::SendErrorDetail;\nuse crate::cache::CacheOverride;\nuse crate::config::ClientCertInfo;\nuse crate::secret_store::SecretLookup;\n\nuse {\n    crate::{\n        config::Backend,\n        error::Error,\n        handoff::{HandoffInfo, HandoffRequestInfo},\n        sandbox::{AsyncItem, PeekableTask, Sandbox, ViceroyRequestMetadata},\n        upstream,\n        wiggle_abi::{\n            fastly_http_downstream::FastlyHttpDownstream,\n            fastly_http_req::FastlyHttpReq,\n            headers::HttpHeaders,\n            types::{\n                BackendConfigOptions, BodyHandle, CacheOverrideTag, ClientCertVerifyResult,\n                ContentEncodings, DynamicBackendConfig, FramingHeadersMode, HttpVersion,\n                InspectInfo, InspectInfoMask, MultiValueCursor, MultiValueCursorResult,\n                PendingRequestHandle, RequestHandle, ResponseHandle,\n            },\n        },\n    },\n    fastly_shared::{INVALID_BODY_HANDLE, INVALID_REQUEST_HANDLE, INVALID_RESPONSE_HANDLE},\n    http::{HeaderValue, Method, Uri},\n    hyper::http::request::Request,\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyHttpReq for Sandbox {\n    fn body_downstream_get(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n    ) -> Result<(RequestHandle, BodyHandle), Error> {\n        let req_handle = self.downstream_request();\n        let body_handle = self.downstream_request_body();\n        Ok((req_handle, body_handle))\n    }\n\n    fn cache_override_set(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        tag: CacheOverrideTag,\n        ttl: u32,\n        stale_while_revalidate: u32,\n    ) -> Result<(), Error> {\n        let overrides = CacheOverride::from_abi(u32::from(tag), ttl, stale_while_revalidate, None)\n            .ok_or(Error::InvalidArgument)?;\n\n        self.request_parts_mut(req_handle)?\n            .extensions\n            .insert(overrides);\n\n        Ok(())\n    }\n\n    fn cache_override_v2_set(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        tag: CacheOverrideTag,\n        ttl: u32,\n        stale_while_revalidate: u32,\n        sk: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let sk = if sk.len() > 0 {\n            let sk = memory.as_slice(sk)?.ok_or(Error::SharedMemory)?;\n            let sk = HeaderValue::from_bytes(sk).map_err(|_| Error::InvalidArgument)?;\n            Some(sk)\n        } else {\n            None\n        };\n\n        let overrides = CacheOverride::from_abi(u32::from(tag), ttl, stale_while_revalidate, sk)\n            .ok_or(Error::InvalidArgument)?;\n\n        self.request_parts_mut(req_handle)?\n            .extensions\n            .insert(overrides);\n\n        Ok(())\n    }\n\n    fn downstream_server_ip_addr(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        // Must be a 16-byte array:\n        addr_octets_ptr: GuestPtr<u8>,\n    ) -> Result<u32, Error> {\n        FastlyHttpDownstream::downstream_server_ip_addr(\n            self,\n            memory,\n            self.downstream_request(),\n            addr_octets_ptr,\n        )\n    }\n\n    fn downstream_client_ip_addr(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        // Must be a 16-byte array:\n        addr_octets_ptr: GuestPtr<u8>,\n    ) -> Result<u32, Error> {\n        FastlyHttpDownstream::downstream_client_ip_addr(\n            self,\n            memory,\n            self.downstream_request(),\n            addr_octets_ptr,\n        )\n    }\n\n    fn downstream_client_h2_fingerprint(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        h2fp_out: GuestPtr<u8>,\n        h2fp_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_client_h2_fingerprint(\n            self,\n            memory,\n            self.downstream_request(),\n            h2fp_out,\n            h2fp_max_len,\n            nwritten_out,\n        )\n    }\n\n    fn downstream_client_request_id(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        reqid_out: GuestPtr<u8>,\n        reqid_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_client_request_id(\n            self,\n            memory,\n            self.downstream_request(),\n            reqid_out,\n            reqid_max_len,\n            nwritten_out,\n        )\n    }\n\n    fn downstream_client_oh_fingerprint(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        ohfp_out: GuestPtr<u8>,\n        ohfp_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_client_oh_fingerprint(\n            self,\n            memory,\n            self.downstream_request(),\n            ohfp_out,\n            ohfp_max_len,\n            nwritten_out,\n        )\n    }\n\n    fn downstream_client_ddos_detected(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n    ) -> Result<u32, Error> {\n        FastlyHttpDownstream::downstream_client_ddos_detected(\n            self,\n            memory,\n            self.downstream_request(),\n        )\n    }\n\n    fn downstream_tls_cipher_openssl_name(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cipher_out: GuestPtr<u8>,\n        cipher_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_client_oh_fingerprint(\n            self,\n            memory,\n            self.downstream_request(),\n            cipher_out,\n            cipher_max_len,\n            nwritten_out,\n        )\n    }\n\n    #[allow(unused_variables)] // FIXME ACF 2022-05-03: Remove this directive once implemented.\n    fn upgrade_websocket(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        backend_name: GuestPtr<str>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"WebSocket upgrade\"))\n    }\n\n    #[allow(unused_variables)] // FIXME ACF 2022-10-03: Remove this directive once implemented.\n    fn redirect_to_websocket_proxy(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        backend_name: GuestPtr<str>,\n    ) -> Result<(), Error> {\n        let backend_name = memory\n            .as_str(backend_name)?\n            .ok_or(Error::SharedMemory)?\n            .to_string();\n        let redirect_info = HandoffInfo {\n            backend_name,\n            request_info: None,\n        };\n\n        self.redirect_downstream_to_backend(redirect_info)?;\n        Ok(())\n    }\n\n    #[allow(unused_variables)] // FIXME ACF 2022-10-03: Remove this directive once implemented.\n    fn redirect_to_grip_proxy(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        backend_name: GuestPtr<str>,\n    ) -> Result<(), Error> {\n        let backend_name = memory\n            .as_str(backend_name)?\n            .ok_or(Error::SharedMemory)?\n            .to_string();\n        let redirect_info = HandoffInfo {\n            backend_name,\n            request_info: None,\n        };\n\n        self.redirect_downstream_to_pushpin(redirect_info)?;\n        Ok(())\n    }\n\n    fn redirect_to_websocket_proxy_v2(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        backend_name: GuestPtr<str>,\n    ) -> Result<(), Error> {\n        let backend_name = memory\n            .as_str(backend_name)?\n            .ok_or(Error::SharedMemory)?\n            .to_string();\n        let req = self.request_parts(req_handle)?;\n        let redirect_info = HandoffInfo {\n            backend_name,\n            request_info: Some(HandoffRequestInfo::from_parts(req)),\n        };\n\n        self.redirect_downstream_to_backend(redirect_info)?;\n        Ok(())\n    }\n\n    fn redirect_to_grip_proxy_v2(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        backend_name: GuestPtr<str>,\n    ) -> Result<(), Error> {\n        let backend_name = memory\n            .as_str(backend_name)?\n            .ok_or(Error::SharedMemory)?\n            .to_string();\n        let req = self.request_parts(req_handle)?;\n        let redirect_info = HandoffInfo {\n            backend_name,\n            request_info: Some(HandoffRequestInfo::from_parts(req)),\n        };\n\n        self.redirect_downstream_to_pushpin(redirect_info)?;\n        Ok(())\n    }\n\n    fn downstream_tls_protocol(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        protocol_out: GuestPtr<u8>,\n        protocol_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_tls_protocol(\n            self,\n            memory,\n            self.downstream_request(),\n            protocol_out,\n            protocol_max_len,\n            nwritten_out,\n        )\n    }\n\n    fn downstream_tls_client_hello(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        chello_out: GuestPtr<u8>,\n        chello_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_tls_client_hello(\n            self,\n            memory,\n            self.downstream_request(),\n            chello_out,\n            chello_max_len,\n            nwritten_out,\n        )\n    }\n\n    fn downstream_tls_raw_client_certificate(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        cert_out: GuestPtr<u8>,\n        cert_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_tls_raw_client_certificate(\n            self,\n            memory,\n            self.downstream_request(),\n            cert_out,\n            cert_max_len,\n            nwritten_out,\n        )\n    }\n\n    fn downstream_tls_client_cert_verify_result(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n    ) -> Result<ClientCertVerifyResult, Error> {\n        FastlyHttpDownstream::downstream_tls_client_cert_verify_result(\n            self,\n            memory,\n            self.downstream_request(),\n        )\n    }\n\n    fn downstream_tls_ja3_md5(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        ja3_md5_out: GuestPtr<u8>,\n    ) -> Result<u32, Error> {\n        FastlyHttpDownstream::downstream_tls_ja3_md5(\n            self,\n            memory,\n            self.downstream_request(),\n            ja3_md5_out,\n        )\n    }\n\n    fn downstream_tls_ja4(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        ja4_out: GuestPtr<u8>,\n        ja4_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_tls_ja4(\n            self,\n            memory,\n            self.downstream_request(),\n            ja4_out,\n            ja4_max_len,\n            nwritten_out,\n        )\n    }\n\n    fn downstream_compliance_region(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        // Must be a 16-byte array:\n        region_out: GuestPtr<u8>,\n        region_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_compliance_region(\n            self,\n            memory,\n            self.downstream_request(),\n            region_out,\n            region_max_len,\n            nwritten_out,\n        )\n    }\n\n    fn framing_headers_mode_set(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        mode: FramingHeadersMode,\n    ) -> Result<(), Error> {\n        let extensions = &mut self.request_parts_mut(req_handle)?.extensions;\n\n        match extensions.get_mut::<ViceroyRequestMetadata>() {\n            None => {\n                extensions.insert(ViceroyRequestMetadata {\n                    framing_headers_mode: mode,\n                    ..Default::default()\n                });\n            }\n            Some(vrm) => {\n                vrm.framing_headers_mode = mode;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn register_dynamic_backend(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<str>,\n        upstream_dynamic: GuestPtr<str>,\n        backend_info_mask: BackendConfigOptions,\n        backend_info: GuestPtr<DynamicBackendConfig>,\n    ) -> Result<(), Error> {\n        let name = {\n            let name_slice = memory.to_vec(name.as_bytes())?;\n            String::from_utf8(name_slice).map_err(|_| Error::InvalidArgument)?\n        };\n        let origin_name = {\n            let origin_name_slice = memory.to_vec(upstream_dynamic.as_bytes())?;\n            String::from_utf8(origin_name_slice).map_err(|_| Error::InvalidArgument)?\n        };\n        let config = memory.read(backend_info)?;\n\n        // If someone set our reserved bit, error. We might need it, and we don't\n        // want anyone it early.\n        if backend_info_mask.contains(BackendConfigOptions::RESERVED) {\n            return Err(Error::InvalidArgument);\n        }\n\n        // If someone has set any bits we don't know about, let's also return false,\n        // as there's either bad data or an API compatibility problem.\n        if backend_info_mask != BackendConfigOptions::from_bits_truncate(backend_info_mask.bits()) {\n            return Err(Error::InvalidArgument);\n        }\n\n        let override_host = if backend_info_mask.contains(BackendConfigOptions::HOST_OVERRIDE) {\n            if config.host_override_len == 0 {\n                return Err(Error::InvalidArgument);\n            }\n\n            if config.host_override_len > 1024 {\n                return Err(Error::InvalidArgument);\n            }\n\n            let byte_slice =\n                memory.to_vec(config.host_override.as_array(config.host_override_len))?;\n\n            let string = String::from_utf8(byte_slice).map_err(|_| Error::InvalidArgument)?;\n\n            Some(HeaderValue::from_str(&string)?)\n        } else {\n            None\n        };\n\n        let scheme = if backend_info_mask.contains(BackendConfigOptions::USE_SSL) {\n            \"https\"\n        } else {\n            \"http\"\n        };\n\n        let ca_certs =\n            if (scheme == \"https\") && backend_info_mask.contains(BackendConfigOptions::CA_CERT) {\n                if config.ca_cert_len == 0 {\n                    return Err(Error::InvalidArgument);\n                }\n\n                if config.ca_cert_len > (64 * 1024) {\n                    return Err(Error::InvalidArgument);\n                }\n\n                let byte_slice = memory\n                    .as_slice(config.ca_cert.as_array(config.ca_cert_len))?\n                    .ok_or(Error::SharedMemory)?;\n                let mut byte_cursor = std::io::Cursor::new(byte_slice);\n                rustls_pemfile::certs(&mut byte_cursor)?\n                    .drain(..)\n                    .map(rustls::Certificate)\n                    .collect()\n            } else {\n                vec![]\n            };\n\n        let mut cert_host = if backend_info_mask.contains(BackendConfigOptions::CERT_HOSTNAME) {\n            if config.cert_hostname_len == 0 {\n                return Err(Error::InvalidArgument);\n            }\n\n            if config.cert_hostname_len > 1024 {\n                return Err(Error::InvalidArgument);\n            }\n\n            let byte_slice = memory\n                .as_slice(config.cert_hostname.as_array(config.cert_hostname_len))?\n                .ok_or(Error::SharedMemory)?;\n\n            Some(std::str::from_utf8(byte_slice)?.to_owned())\n        } else {\n            None\n        };\n\n        let use_sni = if backend_info_mask.contains(BackendConfigOptions::SNI_HOSTNAME) {\n            if config.sni_hostname_len == 0 {\n                false\n            } else if config.sni_hostname_len > 1024 {\n                return Err(Error::InvalidArgument);\n            } else {\n                let byte_slice = memory\n                    .as_slice(config.sni_hostname.as_array(config.sni_hostname_len))?\n                    .ok_or(Error::SharedMemory)?;\n                let sni_hostname = std::str::from_utf8(byte_slice)?;\n                if let Some(cert_host) = &cert_host {\n                    if cert_host != sni_hostname {\n                        // because we're using rustls, we cannot support distinct SNI and cert hostnames\n                        return Err(Error::InvalidArgument);\n                    }\n                } else {\n                    cert_host = Some(sni_hostname.to_owned())\n                }\n\n                true\n            }\n        } else {\n            true\n        };\n\n        let client_cert = if backend_info_mask.contains(BackendConfigOptions::CLIENT_CERT) {\n            let cert_slice = memory\n                .as_slice(\n                    config\n                        .client_certificate\n                        .as_array(config.client_certificate_len),\n                )?\n                .ok_or(Error::SharedMemory)?;\n            let key_lookup =\n                self.secret_lookup(config.client_key)\n                    .ok_or(Error::SecretStoreError(\n                        SecretStoreError::InvalidSecretHandle(config.client_key),\n                    ))?;\n            let key = match &key_lookup {\n                SecretLookup::Standard {\n                    store_name,\n                    secret_name,\n                } => self\n                    .secret_stores()\n                    .get_store(store_name)\n                    .ok_or(Error::SecretStoreError(\n                        SecretStoreError::InvalidSecretHandle(config.client_key),\n                    ))?\n                    .get_secret(secret_name)\n                    .ok_or(Error::SecretStoreError(\n                        SecretStoreError::InvalidSecretHandle(config.client_key),\n                    ))?\n                    .plaintext(),\n\n                SecretLookup::Injected { plaintext } => plaintext,\n            };\n\n            Some(ClientCertInfo::new(cert_slice, key)?)\n        } else {\n            None\n        };\n\n        let grpc = backend_info_mask.contains(BackendConfigOptions::GRPC);\n\n        let new_backend = Backend {\n            uri: Uri::builder()\n                .scheme(scheme)\n                .authority(origin_name)\n                .path_and_query(\"/\")\n                .build()?,\n            override_host,\n            cert_host,\n            use_sni,\n            grpc,\n            client_cert,\n            ca_certs,\n            health: crate::config::BackendHealth::Unknown,\n        };\n\n        if !self.add_backend(&name, new_backend) {\n            return Err(Error::BackendNameRegistryError(name));\n        }\n\n        Ok(())\n    }\n\n    fn new(&mut self, _memory: &mut GuestMemory<'_>) -> Result<RequestHandle, Error> {\n        let (parts, _) = Request::new(()).into_parts();\n        Ok(self.insert_request_parts(parts))\n    }\n\n    fn header_names_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: MultiValueCursor,\n        ending_cursor_out: GuestPtr<MultiValueCursorResult>,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let headers = &self.request_parts(req_handle)?.headers;\n        multi_value_result!(\n            memory,\n            headers.names_get(memory, buf, buf_len, cursor, nwritten_out),\n            ending_cursor_out\n        )\n    }\n\n    fn original_header_names_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: MultiValueCursor,\n        ending_cursor_out: GuestPtr<MultiValueCursorResult>,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        FastlyHttpDownstream::downstream_original_header_names(\n            self,\n            memory,\n            self.downstream_request(),\n            buf,\n            buf_len,\n            cursor,\n            ending_cursor_out,\n            nwritten_out,\n        )\n    }\n\n    fn original_header_count(&mut self, memory: &mut GuestMemory<'_>) -> Result<u32, Error> {\n        FastlyHttpDownstream::downstream_original_header_count(\n            self,\n            memory,\n            self.downstream_request(),\n        )\n    }\n\n    fn header_value_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<u8>,\n        value_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let headers = &self.request_parts(req_handle)?.headers;\n        headers.value_get(memory, name, value, value_max_len, nwritten_out)\n    }\n\n    fn header_values_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        name: GuestPtr<[u8]>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: MultiValueCursor,\n        ending_cursor_out: GuestPtr<MultiValueCursorResult>,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let headers = &self.request_parts(req_handle)?.headers;\n        multi_value_result!(\n            memory,\n            headers.values_get(memory, name, buf, buf_len, cursor, nwritten_out),\n            ending_cursor_out\n        )\n    }\n\n    fn header_values_set(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        name: GuestPtr<[u8]>,\n        values: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let headers = &mut self.request_parts_mut(req_handle)?.headers;\n        headers.values_set(memory, name, values)\n    }\n\n    fn header_insert(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let headers = &mut self.request_parts_mut(req_handle)?.headers;\n        HttpHeaders::insert(headers, memory, name, value)\n    }\n\n    fn header_append(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let headers = &mut self.request_parts_mut(req_handle)?.headers;\n        HttpHeaders::append(headers, memory, name, value)\n    }\n\n    fn header_remove(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        name: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let headers = &mut self.request_parts_mut(req_handle)?.headers;\n        HttpHeaders::remove(headers, memory, name)\n    }\n\n    fn method_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let req = self.request_parts(req_handle)?;\n        let req_method = &req.method;\n        let req_method_bytes = req_method.to_string().into_bytes();\n\n        if req_method_bytes.len() > buf_len as usize {\n            // Write out the number of bytes necessary to fit this method, or zero on overflow to\n            // signal an error condition.\n            memory.write(nwritten_out, req_method_bytes.len().try_into().unwrap_or(0))?;\n            return Err(Error::BufferLengthError {\n                buf: \"method\",\n                len: \"method_max_len\",\n            });\n        }\n\n        let req_method_len = u32::try_from(req_method_bytes.len())\n            .expect(\"smaller than method_max_len means it must fit\");\n\n        memory.copy_from_slice(&req_method_bytes, buf.as_array(req_method_len))?;\n        memory.write(nwritten_out, req_method_len)?;\n\n        Ok(())\n    }\n\n    fn method_set(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        method: GuestPtr<str>,\n    ) -> Result<(), Error> {\n        let method_ref = &mut self.request_parts_mut(req_handle)?.method;\n        let method_slice = memory\n            .as_slice(method.as_bytes())?\n            .ok_or(Error::SharedMemory)?;\n        *method_ref = Method::from_bytes(method_slice)?;\n\n        Ok(())\n    }\n\n    fn uri_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let req = self.request_parts(req_handle)?;\n        let req_uri_bytes = req.uri.to_string().into_bytes();\n\n        if req_uri_bytes.len() > buf_len as usize {\n            // Write out the number of bytes necessary to fit this method, or zero on overflow to\n            // signal an error condition.\n            memory.write(nwritten_out, req_uri_bytes.len().try_into().unwrap_or(0))?;\n            return Err(Error::BufferLengthError {\n                buf: \"uri\",\n                len: \"uri_max_len\",\n            });\n        }\n        let req_uri_len =\n            u32::try_from(req_uri_bytes.len()).expect(\"smaller than uri_max_len means it must fit\");\n\n        memory.copy_from_slice(&req_uri_bytes, buf.as_array(req_uri_len))?;\n        memory.write(nwritten_out, req_uri_len)?;\n\n        Ok(())\n    }\n\n    fn uri_set(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        uri: GuestPtr<str>,\n    ) -> Result<(), Error> {\n        let uri_ref = &mut self.request_parts_mut(req_handle)?.uri;\n        let req_uri_bytes = memory\n            .as_slice(uri.as_bytes())?\n            .ok_or(Error::SharedMemory)?;\n\n        *uri_ref = Uri::try_from(req_uri_bytes)?;\n        Ok(())\n    }\n\n    fn version_get(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n    ) -> Result<HttpVersion, Error> {\n        let req = self.request_parts(req_handle)?;\n        HttpVersion::try_from(req.version).map_err(|msg| Error::Unsupported { msg })\n    }\n\n    fn version_set(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        version: HttpVersion,\n    ) -> Result<(), Error> {\n        let req = self.request_parts_mut(req_handle)?;\n\n        let version = hyper::Version::from(version);\n        req.version = version;\n        Ok(())\n    }\n\n    async fn send(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend_bytes: GuestPtr<str>,\n    ) -> Result<(ResponseHandle, BodyHandle), Error> {\n        let backend_bytes_slice = memory\n            .as_slice(backend_bytes.as_bytes())?\n            .ok_or(Error::SharedMemory)?;\n        let backend_name = std::str::from_utf8(backend_bytes_slice)?;\n\n        // prepare the request\n        let req_parts = self.take_request_parts(req_handle)?;\n        let req_body = self.take_body(body_handle)?;\n        let req = Request::from_parts(req_parts, req_body);\n        let backend = self\n            .backend(backend_name)\n            .ok_or_else(|| Error::UnknownBackend(backend_name.to_owned()))?;\n\n        // synchronously send the request\n        let resp = upstream::send_request(req, backend, backend_name, self.tls_config()).await?;\n        Ok(self.insert_response(resp))\n    }\n\n    async fn send_v2(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend_bytes: GuestPtr<str>,\n        _error_detail: GuestPtr<SendErrorDetail>,\n    ) -> Result<(ResponseHandle, BodyHandle), Error> {\n        // This initial implementation ignores the error detail field\n        self.send(memory, req_handle, body_handle, backend_bytes)\n            .await\n    }\n\n    async fn send_v3(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend_bytes: GuestPtr<str>,\n        error_detail: GuestPtr<SendErrorDetail>,\n    ) -> Result<(ResponseHandle, BodyHandle), Error> {\n        self.send_v2(memory, req_handle, body_handle, backend_bytes, error_detail)\n            .await\n    }\n\n    async fn send_async(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend_bytes: GuestPtr<str>,\n    ) -> Result<PendingRequestHandle, Error> {\n        let backend_bytes_slice = memory\n            .as_slice(backend_bytes.as_bytes())?\n            .ok_or(Error::SharedMemory)?;\n        let backend_name = std::str::from_utf8(backend_bytes_slice)?;\n\n        // prepare the request\n        let req_parts = self.take_request_parts(req_handle)?;\n        let req_body = self.take_body(body_handle)?;\n        let req = Request::from_parts(req_parts, req_body);\n        let backend = self\n            .backend(backend_name)\n            .ok_or_else(|| Error::UnknownBackend(backend_name.to_owned()))?;\n\n        // asynchronously send the request\n        let task = PeekableTask::spawn(upstream::send_request(\n            req,\n            backend,\n            backend_name,\n            self.tls_config(),\n        ))\n        .await;\n\n        // return a handle to the pending task\n        Ok(self.insert_pending_request(task))\n    }\n\n    async fn send_async_v2(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend_bytes: GuestPtr<str>,\n        streaming: u32,\n    ) -> Result<PendingRequestHandle, Error> {\n        if streaming == 1 {\n            self.send_async_streaming(memory, req_handle, body_handle, backend_bytes)\n                .await\n        } else {\n            self.send_async(memory, req_handle, body_handle, backend_bytes)\n                .await\n        }\n    }\n\n    async fn send_async_streaming(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend_bytes: GuestPtr<str>,\n    ) -> Result<PendingRequestHandle, Error> {\n        let backend_bytes_slice = memory\n            .as_slice(backend_bytes.as_bytes())?\n            .ok_or(Error::SharedMemory)?;\n        let backend_name = std::str::from_utf8(backend_bytes_slice)?;\n\n        // prepare the request\n        let req_parts = self.take_request_parts(req_handle)?;\n        let req_body = self.begin_streaming(body_handle)?;\n        let req = Request::from_parts(req_parts, req_body);\n        let backend = self\n            .backend(backend_name)\n            .ok_or_else(|| Error::UnknownBackend(backend_name.to_owned()))?;\n\n        // asynchronously send the request\n        let task = PeekableTask::spawn(upstream::send_request(\n            req,\n            backend,\n            backend_name,\n            self.tls_config(),\n        ))\n        .await;\n\n        // return a handle to the pending task\n        Ok(self.insert_pending_request(task))\n    }\n\n    // note: The first value in the return tuple represents whether the request is done: 0 when not\n    // done, 1 when done.\n    async fn pending_req_poll(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        pending_req_handle: PendingRequestHandle,\n    ) -> Result<(u32, ResponseHandle, BodyHandle), Error> {\n        if self.async_item_mut(pending_req_handle.into())?.is_ready() {\n            let resp = self\n                .take_pending_request(pending_req_handle)?\n                .recv()\n                .await?;\n            let (resp_handle, resp_body_handle) = self.insert_response(resp);\n            Ok((1, resp_handle, resp_body_handle))\n        } else {\n            Ok((0, INVALID_REQUEST_HANDLE.into(), INVALID_BODY_HANDLE.into()))\n        }\n    }\n\n    async fn pending_req_poll_v2(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_req_handle: PendingRequestHandle,\n        _error_detail: GuestPtr<SendErrorDetail>,\n    ) -> Result<(u32, ResponseHandle, BodyHandle), Error> {\n        // This initial implementation ignores the error detail field\n        self.pending_req_poll(memory, pending_req_handle).await\n    }\n\n    async fn pending_req_wait(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        pending_req_handle: PendingRequestHandle,\n    ) -> Result<(ResponseHandle, BodyHandle), Error> {\n        let pending_req = self\n            .take_pending_request(pending_req_handle)?\n            .recv()\n            .await?;\n        Ok(self.insert_response(pending_req))\n    }\n\n    async fn pending_req_wait_v2(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_req_handle: PendingRequestHandle,\n        _error_detail: GuestPtr<SendErrorDetail>,\n    ) -> Result<(ResponseHandle, BodyHandle), Error> {\n        // This initial implementation ignores the error detail field\n        self.pending_req_wait(memory, pending_req_handle).await\n    }\n\n    // First element of return tuple is the \"done index\"\n    async fn pending_req_select(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_req_handles: GuestPtr<[PendingRequestHandle]>,\n    ) -> Result<(u32, ResponseHandle, BodyHandle), Error> {\n        if pending_req_handles.len() == 0 {\n            return Err(Error::InvalidArgument);\n        }\n        let pending_req_handles = pending_req_handles.cast::<[u32]>();\n\n        // perform the select operation\n        let done_index = self\n            .select_impl(\n                memory\n                    // TODO: `GuestMemory::as_slice` only supports guest pointers to u8 slices in\n                    // wiggle 22.0.0, but `GuestMemory::to_vec` supports guest pointers to slices\n                    // of arbitrary types. As `GuestMemory::to_vec` will copy the contents of the\n                    // slice out of guest memory, we should switch this to `GuestMemory::as_slice`\n                    // once it is polymorphic in the element type of the slice.\n                    .to_vec(pending_req_handles)?\n                    .into_iter()\n                    .map(|handle| PendingRequestHandle::from(handle).into()),\n            )\n            .await? as u32;\n\n        let item = self.take_async_item(\n            PendingRequestHandle::from(memory.read(pending_req_handles.get(done_index).unwrap())?)\n                .into(),\n        )?;\n\n        let outcome = match item {\n            AsyncItem::PendingReq(task) => match task {\n                PeekableTask::Complete(res) => match res {\n                    Ok(res) => {\n                        let (resp_handle, body_handle) = self.insert_response(res);\n                        (done_index, resp_handle, body_handle)\n                    }\n                    Err(_) => (\n                        done_index,\n                        INVALID_RESPONSE_HANDLE.into(),\n                        INVALID_BODY_HANDLE.into(),\n                    ),\n                },\n                _ => panic!(\"Pending request was not completed\"),\n            },\n            _ => panic!(\"AsyncItem was not a pending request\"),\n        };\n\n        Ok(outcome)\n    }\n\n    async fn pending_req_select_v2(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        pending_req_handles: GuestPtr<[PendingRequestHandle]>,\n        _error_detail: GuestPtr<SendErrorDetail>,\n    ) -> Result<(u32, ResponseHandle, BodyHandle), Error> {\n        // This initial implementation ignores the error detail field\n        self.pending_req_select(memory, pending_req_handles).await\n    }\n\n    fn fastly_key_is_valid(&mut self, memory: &mut GuestMemory<'_>) -> Result<u32, Error> {\n        FastlyHttpDownstream::fastly_key_is_valid(self, memory, self.downstream_request())\n    }\n\n    fn close(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n    ) -> Result<(), Error> {\n        // We don't do anything with the parts, but we do pass the error up if\n        // the handle given doesn't exist\n        self.take_request_parts(req_handle)?;\n        Ok(())\n    }\n\n    fn auto_decompress_response_set(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        req_handle: RequestHandle,\n        encodings: ContentEncodings,\n    ) -> Result<(), Error> {\n        // NOTE: We're going to hide this flag in the extensions of the request in order to decrease\n        // the book-keeping burden inside Sandbox. The flag will get picked up later, in `send_request`.\n        let extensions = &mut self.request_parts_mut(req_handle)?.extensions;\n\n        match extensions.get_mut::<ViceroyRequestMetadata>() {\n            None => {\n                extensions.insert(ViceroyRequestMetadata {\n                    auto_decompress_encodings: encodings,\n                    ..Default::default()\n                });\n            }\n            Some(vrm) => {\n                vrm.auto_decompress_encodings = encodings;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn inspect(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        ds_req: RequestHandle,\n        ds_body: BodyHandle,\n        info_mask: InspectInfoMask,\n        info: GuestPtr<InspectInfo>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n    ) -> Result<u32, Error> {\n        // Make sure we're given valid handles, even though we won't use them.\n        let _ = self.request_parts(ds_req)?;\n        let _ = self.body(ds_body)?;\n\n        // Make sure the InspectInfo looks good, even though we won't use it.\n        let info = memory.read(info)?;\n        let info_string_or_err = |flag, str_field: GuestPtr<u8>, len_field| {\n            if info_mask.contains(flag) {\n                if len_field == 0 {\n                    return Err(Error::InvalidArgument);\n                }\n\n                let byte_vec = memory.to_vec(str_field.as_array(len_field))?;\n                let s = String::from_utf8(byte_vec).map_err(|_| Error::InvalidArgument)?;\n\n                Ok(s)\n            } else {\n                // For now, corp and workspace arguments are required to actually generate the hostname,\n                // but in the future, the lookaside service will be generated using the customer ID, and\n                // it will be okay for them to be unspecified or empty.\n                Err(Error::InvalidArgument)\n            }\n        };\n\n        let _ = info_string_or_err(InspectInfoMask::CORP, info.corp, info.corp_len)?;\n        let _ = info_string_or_err(\n            InspectInfoMask::WORKSPACE,\n            info.workspace,\n            info.workspace_len,\n        )?;\n\n        if info_mask.contains(InspectInfoMask::OVERRIDE_CLIENT_IP) {\n            let _ = read_guest_ip(\n                memory,\n                &info.override_client_ip_ptr,\n                info.override_client_ip_len,\n            )?;\n        }\n\n        // Return the mock NGWAF response.\n        let ngwaf_resp = self.ngwaf_response();\n        let ngwaf_resp_len = ngwaf_resp.len();\n\n        match u32::try_from(ngwaf_resp_len) {\n            Ok(ngwaf_resp_len) if ngwaf_resp_len <= buf_len => {\n                memory.copy_from_slice(ngwaf_resp.as_bytes(), buf.as_array(ngwaf_resp_len))?;\n\n                Ok(ngwaf_resp_len)\n            }\n            _ => Err(Error::BufferLengthError {\n                buf: \"buf\",\n                len: \"buf_len\",\n            }),\n        }\n    }\n\n    fn on_behalf_of(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _ds_req: RequestHandle,\n        _service: GuestPtr<str>,\n    ) -> Result<(), Error> {\n        Err(Error::Unsupported {\n            msg: \"on_behalf_of is not supported in Viceroy\",\n        })\n    }\n}\n\nfn try_ip_from_bytes<const N: usize>(bytes: Option<&[u8]>) -> Result<IpAddr, Error>\nwhere\n    IpAddr: From<[u8; N]>,\n{\n    bytes\n        .and_then(|bs| <[u8; N]>::try_from(bs).ok())\n        .map(IpAddr::from)\n        .ok_or(Error::InvalidArgument)\n}\n\nfn read_guest_ip(\n    memory: &mut GuestMemory<'_>,\n    bytes: &GuestPtr<u8>,\n    len: u32,\n) -> Result<Option<IpAddr>, Error> {\n    let bytes = memory.as_slice(bytes.as_array(len))?;\n\n    match len {\n        0 => Ok(None),\n        4 => try_ip_from_bytes::<4>(bytes).map(Some),\n        16 => try_ip_from_bytes::<16>(bytes).map(Some),\n        _ => Err(Error::InvalidArgument),\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/resp_impl.rs",
    "content": "//! fastly_resp` hostcall implementations.\n\nuse {\n    crate::{\n        error::Error,\n        sandbox::{Sandbox, ViceroyResponseMetadata},\n        upstream,\n        wiggle_abi::{\n            fastly_http_resp::FastlyHttpResp,\n            headers::HttpHeaders,\n            types::{\n                BodyHandle, FramingHeadersMode, HttpKeepaliveMode, HttpStatus, HttpVersion,\n                MultiValueCursor, MultiValueCursorResult, ResponseHandle,\n            },\n        },\n    },\n    cfg_if::cfg_if,\n    hyper::http::response::Response,\n    std::net::IpAddr,\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyHttpResp for Sandbox {\n    fn new(&mut self, _memory: &mut GuestMemory<'_>) -> Result<ResponseHandle, Error> {\n        // KTM: Unfortunately `response::Parts` doesn't expose a constructor. This is a workaround.\n        let (parts, _) = Response::new(()).into_parts();\n        Ok(self.insert_response_parts(parts))\n    }\n\n    fn header_names_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: MultiValueCursor,\n        ending_cursor_out: GuestPtr<MultiValueCursorResult>,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let headers = &self.response_parts(resp_handle)?.headers;\n        multi_value_result!(\n            memory,\n            headers.names_get(memory, buf, buf_len, cursor, nwritten_out),\n            ending_cursor_out\n        )\n    }\n\n    fn header_value_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<u8>,\n        value_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let headers = &self.response_parts(resp_handle)?.headers;\n        headers.value_get(memory, name, value, value_max_len, nwritten_out)\n    }\n\n    fn header_values_get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        name: GuestPtr<[u8]>,\n        buf: GuestPtr<u8>,\n        buf_len: u32,\n        cursor: MultiValueCursor,\n        ending_cursor_out: GuestPtr<MultiValueCursorResult>,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        cfg_if! {\n            if #[cfg(feature = \"test-fatalerror-config\")] {\n                // Avoid warnings:\n                let _ = (memory, resp_handle, name, buf, buf_len, cursor, ending_cursor_out, nwritten_out);\n                return Err(Error::FatalError(\"A fatal error occurred in the test-only implementation of header_values_get\".to_string()));\n            } else {\n                let headers = &self.response_parts(resp_handle)?.headers;\n                multi_value_result!(\n                    memory,\n                    headers.values_get(memory, name, buf, buf_len, cursor, nwritten_out),\n                    ending_cursor_out\n                )\n            }\n        }\n    }\n\n    fn header_values_set(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        name: GuestPtr<[u8]>,\n        values: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let headers = &mut self.response_parts_mut(resp_handle)?.headers;\n        headers.values_set(memory, name, values)\n    }\n\n    fn header_insert(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let headers = &mut self.response_parts_mut(resp_handle)?.headers;\n        HttpHeaders::insert(headers, memory, name, value)\n    }\n\n    fn header_append<'a>(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        name: GuestPtr<[u8]>,\n        value: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let headers = &mut self.response_parts_mut(resp_handle)?.headers;\n        HttpHeaders::append(headers, memory, name, value)\n    }\n\n    fn header_remove<'a>(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        name: GuestPtr<[u8]>,\n    ) -> Result<(), Error> {\n        let headers = &mut self.response_parts_mut(resp_handle)?.headers;\n        HttpHeaders::remove(headers, memory, name)\n    }\n\n    fn version_get(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n    ) -> Result<HttpVersion, Error> {\n        let resp = self.response_parts(resp_handle)?;\n        HttpVersion::try_from(resp.version).map_err(|msg| Error::Unsupported { msg })\n    }\n\n    fn version_set(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        version: HttpVersion,\n    ) -> Result<(), Error> {\n        let resp = self.response_parts_mut(resp_handle)?;\n\n        let version = hyper::Version::from(version);\n        resp.version = version;\n        Ok(())\n    }\n\n    fn send_downstream(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        body_handle: BodyHandle,\n        streaming: u32,\n    ) -> Result<(), Error> {\n        let resp = {\n            // Take the response parts and body from the sandbox, and use them to build a response.\n            // Return an `FastlyStatus::Badf` error code if either of the given handles are invalid.\n            let resp_parts = self.take_response_parts(resp_handle)?;\n            let body = if streaming == 1 {\n                self.begin_streaming(body_handle)?\n            } else {\n                self.take_body(body_handle)?\n            };\n            Response::from_parts(resp_parts, body)\n        }; // Set the downstream response, and return.\n        self.send_downstream_response(resp)\n    }\n\n    fn status_get(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n    ) -> Result<HttpStatus, Error> {\n        Ok(self.response_parts(resp_handle)?.status.as_u16())\n    }\n\n    fn status_set(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        status: HttpStatus,\n    ) -> Result<(), Error> {\n        let resp = self.response_parts_mut(resp_handle)?;\n        let status = hyper::StatusCode::from_u16(status)?;\n        resp.status = status;\n        Ok(())\n    }\n\n    fn framing_headers_mode_set(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        mode: FramingHeadersMode,\n    ) -> Result<(), Error> {\n        let extensions = &mut self.response_parts_mut(resp_handle)?.extensions;\n\n        match extensions.get_mut::<ViceroyResponseMetadata>() {\n            None => {\n                extensions.insert(ViceroyResponseMetadata {\n                    framing_headers_mode: mode,\n                    // future note: at time of writing, this is the only field of\n                    // this structure, but there is an intention to add more fields.\n                    // When we do, and if/when an error appears, what you're looking\n                    // for is:\n                    // ..Default::default()\n                });\n            }\n            Some(vrm) => {\n                vrm.framing_headers_mode = mode;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn close(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n    ) -> Result<(), Error> {\n        // We don't do anything with the parts, but we do pass the error up if\n        // the handle given doesn't exist\n        self.take_response_parts(resp_handle)?;\n        Ok(())\n    }\n\n    fn http_keepalive_mode_set(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _h: ResponseHandle,\n        mode: HttpKeepaliveMode,\n    ) -> Result<(), Error> {\n        match mode {\n            HttpKeepaliveMode::NoKeepalive => Err(Error::NotAvailable(\"No Keepalive\")),\n            HttpKeepaliveMode::Automatic => Ok(()),\n        }\n    }\n\n    fn get_addr_dest_ip(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n        addr_octets_ptr: GuestPtr<u8>,\n    ) -> Result<u32, Error> {\n        let resp = self.response_parts(resp_handle)?;\n        let md = resp\n            .extensions\n            .get::<upstream::ConnMetadata>()\n            .ok_or(Error::ValueAbsent)?;\n\n        if !md.direct_pass {\n            // Compute currently only returns this value when we are doing\n            // direct pass, so we skip returning a value here for now, even\n            // if we have one, so that guest code doesn't come to expect it\n            // during local testing.\n            return Err(Error::ValueAbsent);\n        }\n\n        match md.remote_addr.ip() {\n            IpAddr::V4(addr) => {\n                let octets = addr.octets();\n                let octets_bytes = octets.len() as u32;\n                debug_assert_eq!(octets_bytes, 4);\n                memory.copy_from_slice(&octets, addr_octets_ptr.as_array(octets_bytes))?;\n                Ok(octets_bytes)\n            }\n            IpAddr::V6(addr) => {\n                let octets = addr.octets();\n                let octets_bytes = octets.len() as u32;\n                debug_assert_eq!(octets_bytes, 16);\n                memory.copy_from_slice(&octets, addr_octets_ptr.as_array(octets_bytes))?;\n                Ok(octets_bytes)\n            }\n        }\n    }\n\n    fn get_addr_dest_port(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        resp_handle: ResponseHandle,\n    ) -> Result<u16, Error> {\n        let resp = self.response_parts(resp_handle)?;\n        let md = resp\n            .extensions\n            .get::<upstream::ConnMetadata>()\n            .ok_or(Error::ValueAbsent)?;\n\n        if !md.direct_pass {\n            // Compute currently only returns this value when we are doing\n            // direct pass, so we skip returning a value here for now, even\n            // if we have one, so that guest code doesn't come to expect it\n            // during local testing.\n            return Err(Error::ValueAbsent);\n        }\n\n        let port = md.remote_addr.port();\n        Ok(port)\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/secret_store_impl.rs",
    "content": "use {\n    crate::{\n        error::Error,\n        sandbox::Sandbox,\n        secret_store::SecretLookup,\n        wiggle_abi::{\n            fastly_secret_store::FastlySecretStore,\n            types::{FastlyStatus, SecretHandle, SecretStoreHandle},\n        },\n    },\n    std::convert::TryFrom,\n    wiggle::{GuestMemory, GuestPtr},\n};\n\n#[derive(Debug, thiserror::Error)]\npub enum SecretStoreError {\n    /// A secret store with the given name was not found.\n    #[error(\"Unknown secret store: {0}\")]\n    UnknownSecretStore(String),\n\n    /// A secret with the given name was not found.\n    #[error(\"Unknown secret: {0}\")]\n    UnknownSecret(String),\n\n    /// An invalid secret store handle was provided.\n    #[error(\"Invalid secret store handle: {0}\")]\n    InvalidSecretStoreHandle(SecretStoreHandle),\n\n    /// An invalid secret handle was provided.\n    #[error(\"Invalid secret handle: {0}\")]\n    InvalidSecretHandle(SecretHandle),\n}\n\nimpl From<&SecretStoreError> for FastlyStatus {\n    fn from(err: &SecretStoreError) -> Self {\n        use SecretStoreError::*;\n        match err {\n            UnknownSecretStore(_) => FastlyStatus::None,\n            UnknownSecret(_) => FastlyStatus::None,\n            InvalidSecretStoreHandle(_) => FastlyStatus::Badf,\n            InvalidSecretHandle(_) => FastlyStatus::Badf,\n        }\n    }\n}\n\nimpl FastlySecretStore for Sandbox {\n    fn open(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        name: GuestPtr<str>,\n    ) -> Result<SecretStoreHandle, Error> {\n        let name = memory.as_str(name)?.ok_or(Error::SharedMemory)?;\n        self.secret_store_handle(name)\n            .ok_or(Error::SecretStoreError(\n                SecretStoreError::UnknownSecretStore(name.to_string()),\n            ))\n    }\n\n    fn get(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        secret_store_handle: SecretStoreHandle,\n        secret_name: GuestPtr<str>,\n    ) -> Result<SecretHandle, Error> {\n        let store_name =\n            self.secret_store_name(secret_store_handle)\n                .ok_or(Error::SecretStoreError(\n                    SecretStoreError::InvalidSecretStoreHandle(secret_store_handle),\n                ))?;\n        let secret_name = memory.as_str(secret_name)?.ok_or(Error::SharedMemory)?;\n        self.secret_handle(store_name.as_str(), secret_name)\n            .ok_or(Error::SecretStoreError(SecretStoreError::UnknownSecret(\n                secret_name.to_string(),\n            )))\n    }\n\n    fn plaintext(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        secret_handle: SecretHandle,\n        plaintext_buf: GuestPtr<u8>,\n        plaintext_max_len: u32,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        let lookup = self\n            .secret_lookup(secret_handle)\n            .ok_or(Error::SecretStoreError(\n                SecretStoreError::InvalidSecretHandle(secret_handle),\n            ))?;\n\n        let plaintext = match &lookup {\n            SecretLookup::Standard {\n                store_name,\n                secret_name,\n            } => self\n                .secret_stores()\n                .get_store(store_name)\n                .ok_or(Error::SecretStoreError(\n                    SecretStoreError::InvalidSecretHandle(secret_handle),\n                ))?\n                .get_secret(secret_name)\n                .ok_or(Error::SecretStoreError(\n                    SecretStoreError::InvalidSecretHandle(secret_handle),\n                ))?\n                .plaintext(),\n\n            SecretLookup::Injected { plaintext } => plaintext,\n        };\n\n        if plaintext.len() > plaintext_max_len as usize {\n            // Write out the number of bytes necessary to fit the\n            // plaintext, so client implementations can adapt their\n            // buffer sizes.\n            memory.write(nwritten_out, plaintext.len() as u32)?;\n            return Err(Error::BufferLengthError {\n                buf: \"plaintext_buf\",\n                len: \"plaintext_max_len\",\n            });\n        }\n        let plaintext_len = u32::try_from(plaintext.len())\n            .expect(\"smaller than plaintext_max_len means it must fit\");\n\n        memory.copy_from_slice(plaintext, plaintext_buf.as_array(plaintext_len))?;\n        memory.write(nwritten_out, plaintext_len)?;\n\n        Ok(())\n    }\n\n    fn from_bytes(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        plaintext_buf: GuestPtr<u8>,\n        plaintext_len: u32,\n    ) -> Result<SecretHandle, Error> {\n        let plaintext = memory.to_vec(plaintext_buf.as_array(plaintext_len))?;\n        Ok(self.add_secret(plaintext))\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/shielding.rs",
    "content": "use crate::config::Backend;\nuse crate::error::Error;\nuse crate::sandbox::Sandbox;\nuse crate::wiggle_abi::{fastly_shielding, types};\nuse http::Uri;\nuse std::str::FromStr;\n\nimpl fastly_shielding::FastlyShielding for Sandbox {\n    fn shield_info(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        name: wiggle::GuestPtr<str>,\n        out_buffer: wiggle::GuestPtr<u8>,\n        out_buffer_max_len: u32,\n    ) -> Result<u32, Error> {\n        // Validate the input name and then return the unsupported error.\n        let Some(name) = memory.as_str(name)?.map(str::to_string) else {\n            return Err(Error::ValueAbsent);\n        };\n\n        let running_on = self.shielding_sites().is_local(&name);\n        let unencrypted = self\n            .shielding_sites()\n            .get_unencrypted(&name)\n            .map(|x| x.to_string())\n            .unwrap_or_default();\n        let encrypted = self\n            .shielding_sites()\n            .get_encrypted(&name)\n            .map(|x| x.to_string())\n            .unwrap_or_default();\n\n        if !running_on && unencrypted.is_empty() {\n            return Err(Error::InvalidArgument);\n        }\n\n        let mut output_bytes = Vec::new();\n\n        output_bytes.push(if running_on { 1u8 } else { 0 });\n        output_bytes.extend_from_slice(unencrypted.as_bytes());\n        output_bytes.push(0);\n        output_bytes.extend_from_slice(encrypted.as_bytes());\n        output_bytes.push(0);\n\n        let target_len = output_bytes.len() as u32;\n\n        if target_len > out_buffer_max_len {\n            return Err(Error::BufferLengthError {\n                buf: \"shielding_info\",\n                len: \"info.len()\",\n            });\n        }\n\n        memory.copy_from_slice(&output_bytes, out_buffer.as_array(target_len))?;\n        Ok(target_len)\n    }\n\n    fn backend_for_shield(\n        &mut self,\n        memory: &mut wiggle::GuestMemory<'_>,\n        shield_name: wiggle::GuestPtr<str>,\n        shield_backend_options: types::ShieldBackendOptions,\n        shield_backend_config: wiggle::GuestPtr<types::ShieldBackendConfig>,\n        out_buffer: wiggle::GuestPtr<u8>,\n        out_buffer_max_len: u32,\n    ) -> Result<u32, Error> {\n        // Validate our inputs and then return the unsupported error.\n        let Some(shield_uri) = memory.as_str(shield_name)?.map(str::to_string) else {\n            return Err(Error::ValueAbsent);\n        };\n\n        if shield_backend_options.contains(types::ShieldBackendOptions::RESERVED) {\n            return Err(Error::InvalidArgument);\n        }\n\n        let config = memory.read(shield_backend_config)?;\n\n        if shield_backend_options.contains(types::ShieldBackendOptions::USE_CACHE_KEY) {\n            let field_string = config.cache_key.as_array(config.cache_key_len).cast();\n            if memory.as_str(field_string)?.is_none() {\n                return Err(Error::InvalidArgument);\n            }\n        }\n\n        let Ok(uri) = Uri::from_str(&shield_uri) else {\n            return Err(Error::InvalidArgument);\n        };\n\n        let new_name = format!(\"******{uri}*****\");\n        let new_backend = Backend {\n            uri,\n            override_host: None,\n            cert_host: None,\n            use_sni: false,\n            grpc: false,\n            client_cert: None,\n            ca_certs: Vec::new(),\n            health: crate::config::BackendHealth::Unknown,\n        };\n\n        if !self.add_backend(&new_name, new_backend) {\n            return Err(Error::BackendNameRegistryError(new_name));\n        }\n\n        let new_name_bytes = new_name.as_bytes().to_vec();\n\n        let target_len = new_name_bytes.len() as u32;\n\n        if target_len > out_buffer_max_len {\n            return Err(Error::BufferLengthError {\n                buf: \"shielding_backend\",\n                len: \"name.len()\",\n            });\n        }\n\n        memory.copy_from_slice(&new_name_bytes, out_buffer.as_array(target_len))?;\n\n        Ok(target_len)\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi/uap_impl.rs",
    "content": "//! fastly_uap` hostcall implementations.\n\nuse {\n    crate::{error::Error, sandbox::Sandbox, wiggle_abi::fastly_uap::FastlyUap},\n    wiggle::{GuestMemory, GuestPtr},\n};\n\nimpl FastlyUap for Sandbox {\n    fn parse(\n        &mut self,\n        _memory: &mut GuestMemory<'_>,\n        _user_agent: GuestPtr<str>,\n        _family: GuestPtr<u8>,\n        _family_len: u32,\n        _family_nwritten_out: GuestPtr<u32>,\n        _major: GuestPtr<u8>,\n        _major_len: u32,\n        _major_nwritten_out: GuestPtr<u32>,\n        _minor: GuestPtr<u8>,\n        _minor_len: u32,\n        _minor_nwritten_out: GuestPtr<u32>,\n        _patch: GuestPtr<u8>,\n        _patch_len: u32,\n        _patch_nwritten_out: GuestPtr<u32>,\n    ) -> Result<(), Error> {\n        Err(Error::NotAvailable(\"Useragent parsing\"))\n    }\n}\n"
  },
  {
    "path": "src/wiggle_abi.rs",
    "content": "// a few things to ignore from the `from_witx!` macro-generated code:\n#![allow(clippy::too_many_arguments)]\n#![allow(clippy::derive_partial_eq_without_eq)]\n\n//! Wiggle implementations for the Compute ABI.\n//\n// Future maintainers wishing to peer into the code generated by theses macros can do so by running\n// `cargo expand --package viceroy-lib wiggle_abi` in their shell, from the root of the `Viceroy`\n// project. Alternatively, you can run `make doc-dev` from the root of the `Viceroy` project to\n// build documentation which includes the code generated by Wiggle, and then open it in your\n// browser.\n\npub use self::dictionary_impl::DictionaryError;\npub use self::secret_store_impl::SecretStoreError;\n\npub use self::device_detection_impl::DeviceDetectionError;\n\nuse {\n    self::{\n        fastly_abi::FastlyAbi,\n        types::{FastlyStatus, UserErrorConversion},\n    },\n    crate::{error::Error, sandbox::Sandbox},\n    tracing::{Level, event},\n    wiggle::{GuestErrorType, GuestMemory, GuestPtr},\n};\n\npub const ABI_VERSION: u64 = 1;\n\n/// Wrapper macro to recover the pre-Wiggle behavior where multi-value hostcalls would write default\n/// outputs in case of failure.\n///\n/// This definition must appear above `mod req_impl` and `mod resp_impl` so that the macro\n/// is in scope in those modules.\n//\n// TODO ACF 2020-06-29: this lets us avoid ABI breakage for the moment, but the next time we need\n// to break the ABI, we should revisit whether we want to keep this behavior.\nmacro_rules! multi_value_result {\n    ( $memory:ident, $expr:expr, $ending_cursor_out:expr ) => {{\n        let res = $expr;\n        let ec = res.as_ref().unwrap_or(&(-1));\n        // the previous implementation would only write these if they were null\n        if $ending_cursor_out.offset() != 0 {\n            $memory.write($ending_cursor_out, *ec)?;\n        }\n        let _ = res?;\n        Ok(())\n    }};\n}\n\nmod acl;\nmod backend_impl;\nmod body_impl;\nmod cache;\nmod compute_runtime;\nmod config_store;\nmod device_detection_impl;\nmod dictionary_impl;\nmod entity;\nmod erl_impl;\nmod fastly_purge_impl;\nmod geo_impl;\nmod headers;\nmod http_cache;\nmod http_downstream;\nmod image_optimizer;\nmod kv_store_impl;\nmod log_impl;\nmod obj_store_impl;\nmod req_impl;\nmod resp_impl;\nmod secret_store_impl;\nmod shielding;\nmod uap_impl;\n\n// Expand the `.witx` interface definition into a collection of modules. The `types` module will\n// contain all of the `typename`'s defined in the `witx` file, and other modules will export traits\n// that *must* be implemented by our `ctx` type. See the `from_witx` documentation for more.\nwiggle::from_witx!({\n    witx: [\"wasm_abi/compute-at-edge-abi/compute-at-edge.witx\"],\n    errors: { fastly_status => Error },\n    async: {\n        fastly_acl::lookup,\n        fastly_async_io::{select},\n        fastly_object_store::{delete_async, pending_delete_wait, insert, insert_async, pending_insert_wait, lookup_async, pending_lookup_wait, list},\n        fastly_kv_store::{lookup, lookup_wait, lookup_wait_v2, insert, insert_wait, delete, delete_wait, list, list_wait},\n        fastly_http_body::{append, read, write},\n        fastly_http_cache::{lookup, transaction_lookup, insert, transaction_insert, transaction_insert_and_stream_back, transaction_update, transaction_update_and_return_fresh, transaction_record_not_cacheable, transaction_abandon, transaction_choose_stale, found, close, get_suggested_backend_request, get_suggested_cache_options, prepare_response_for_storage, get_found_response, get_state, get_length, get_max_age_ns, get_stale_while_revalidate_ns, get_stale_if_error_ns, get_age_ns, get_hits, get_sensitive_data, get_surrogate_keys, get_vary_rule},\n        fastly_cache::{ cache_busy_handle_wait, close, close_busy, get_age_ns, get_body, get_hits, get_length, get_max_age_ns, get_stale_while_revalidate_ns, get_state, get_user_metadata, insert, lookup, replace, replace_get_age_ns, replace_get_body, replace_get_hits, replace_get_length, replace_get_max_age_ns, replace_get_stale_while_revalidate_ns, replace_get_state, replace_get_user_metadata, replace_insert, transaction_cancel, transaction_insert, transaction_insert_and_stream_back, transaction_lookup, transaction_lookup_async, transaction_update },\n        fastly_http_downstream::{next_request, next_request_abandon, next_request_wait},\n        fastly_http_req::{\n            pending_req_select, pending_req_select_v2, pending_req_poll, pending_req_poll_v2,\n            pending_req_wait, pending_req_wait_v2, send, send_v2, send_v3, send_async, send_async_v2, send_async_streaming,\n        },\n        fastly_image_optimizer::transform_image_optimizer_request,\n    }\n});\n\nimpl From<types::ObjectStoreHandle> for types::KvStoreHandle {\n    fn from(h: types::ObjectStoreHandle) -> types::KvStoreHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::KvStoreHandle> for types::ObjectStoreHandle {\n    fn from(h: types::KvStoreHandle) -> types::ObjectStoreHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::KvStoreLookupHandle> for types::PendingKvLookupHandle {\n    fn from(h: types::KvStoreLookupHandle) -> types::PendingKvLookupHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::PendingKvLookupHandle> for types::KvStoreLookupHandle {\n    fn from(h: types::PendingKvLookupHandle) -> types::KvStoreLookupHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::KvStoreInsertHandle> for types::PendingKvInsertHandle {\n    fn from(h: types::KvStoreInsertHandle) -> types::PendingKvInsertHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::PendingKvInsertHandle> for types::KvStoreInsertHandle {\n    fn from(h: types::PendingKvInsertHandle) -> types::KvStoreInsertHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::KvStoreDeleteHandle> for types::PendingKvDeleteHandle {\n    fn from(h: types::KvStoreDeleteHandle) -> types::PendingKvDeleteHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::PendingKvDeleteHandle> for types::KvStoreDeleteHandle {\n    fn from(h: types::PendingKvDeleteHandle) -> types::KvStoreDeleteHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::KvStoreListHandle> for types::PendingKvListHandle {\n    fn from(h: types::KvStoreListHandle) -> types::PendingKvListHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::PendingKvListHandle> for types::KvStoreListHandle {\n    fn from(h: types::PendingKvListHandle) -> types::KvStoreListHandle {\n        let s = unsafe { h.inner() };\n        s.into()\n    }\n}\n\nimpl From<types::HttpVersion> for http::version::Version {\n    fn from(v: types::HttpVersion) -> http::version::Version {\n        match v {\n            types::HttpVersion::Http09 => http::version::Version::HTTP_09,\n            types::HttpVersion::Http10 => http::version::Version::HTTP_10,\n            types::HttpVersion::Http11 => http::version::Version::HTTP_11,\n            types::HttpVersion::H2 => http::version::Version::HTTP_2,\n            types::HttpVersion::H3 => http::version::Version::HTTP_3,\n        }\n    }\n}\n\n// The http crate's `Version` is a struct that has a bunch of\n// associated constants, not an enum; this is only a partial conversion.\nimpl TryFrom<http::version::Version> for types::HttpVersion {\n    type Error = &'static str;\n    fn try_from(v: http::version::Version) -> Result<Self, Self::Error> {\n        match v {\n            http::version::Version::HTTP_09 => Ok(types::HttpVersion::Http09),\n            http::version::Version::HTTP_10 => Ok(types::HttpVersion::Http10),\n            http::version::Version::HTTP_11 => Ok(types::HttpVersion::Http11),\n            http::version::Version::HTTP_2 => Ok(types::HttpVersion::H2),\n            http::version::Version::HTTP_3 => Ok(types::HttpVersion::H3),\n            _ => Err(\"unknown http::version::Version\"),\n        }\n    }\n}\n\nimpl FastlyAbi for Sandbox {\n    fn init(&mut self, _memory: &mut GuestMemory<'_>, abi_version: u64) -> Result<(), Error> {\n        if abi_version != ABI_VERSION {\n            Err(Error::AbiVersionMismatch)\n        } else {\n            Ok(())\n        }\n    }\n}\n\nimpl UserErrorConversion for Sandbox {\n    fn fastly_status_from_error(&mut self, e: Error) -> Result<FastlyStatus, anyhow::Error> {\n        match e {\n            Error::UnknownBackend(ref backend) => {\n                let config_path = self.config_path();\n                let backends_buffer = itertools::join(self.backend_names(), \",\");\n                let backends_len = self.backend_names().count();\n\n                match (backends_len, config_path) {\n                    (_, None) => event!(\n                        Level::WARN,\n                        \"Attempted to access backend '{}', but no manifest file was provided to define backends. \\\n                        Specify a file with -C <TOML_FILE>.\",\n                        backend,\n                    ),\n                    (0, Some(config_path)) => event!(\n                        Level::WARN,\n                        \"Attempted to access backend '{}', but no backends were defined in the {} manifest file.\",\n                        backend,\n                        config_path.display()\n                    ),\n                    (_, Some(config_path)) => event!(\n                        Level::WARN,\n                        \"Backend '{}' does not exist. Currently defined backends are: {}. \\\n                        To define additional backends, add them to your {} file.\",\n                        backend,\n                        backends_buffer,\n                        config_path.display(),\n                    ),\n                }\n            }\n            Error::DictionaryError(ref err) => match err {\n                DictionaryError::UnknownDictionaryItem(_) => {\n                    event!(Level::DEBUG, \"Hostcall yielded an error: {}\", err);\n                }\n                DictionaryError::UnknownDictionary(_) => {\n                    event!(Level::DEBUG, \"Hostcall yielded an error: {}\", err);\n                }\n            },\n            _ => event!(Level::DEBUG, \"Hostcall yielded an error: {}\", e),\n        }\n\n        match e {\n            // If a Fatal Error was encountered, propagate the error message out.\n            Error::FatalError(msg) => Err(anyhow::Error::new(Error::FatalError(msg))),\n            // Propagate the actionable error to the guest.\n            _ => Ok(e.to_fastly_status()),\n        }\n    }\n}\n\nimpl GuestErrorType for FastlyStatus {\n    fn success() -> Self {\n        FastlyStatus::Ok\n    }\n}\n\npub(crate) trait MultiValueWriter {\n    fn write_values(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        terminator: u8,\n        buf: GuestPtr<[u8]>,\n        cursor: types::MultiValueCursor,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<types::MultiValueCursorResult, Error>;\n}\n\nimpl<I, T> MultiValueWriter for I\nwhere\n    I: Iterator<Item = T>,\n    T: AsRef<[u8]>,\n{\n    #[allow(clippy::useless_conversion)] // numeric conversations that may vary by platform\n    fn write_values(\n        &mut self,\n        memory: &mut GuestMemory<'_>,\n        terminator: u8,\n        buf: GuestPtr<[u8]>,\n        cursor: types::MultiValueCursor,\n        nwritten_out: GuestPtr<u32>,\n    ) -> Result<types::MultiValueCursorResult, Error> {\n        let buf = memory.as_slice_mut(buf)?.ok_or(Error::SharedMemory)?;\n\n        // Note: the prior implementation multi_value_writer would first\n        // terminate the buffer, write -1 to the ending cursor, and zero the nwritten\n        // pointer. The latter two aren't possible under the wiggle model, and the\n        // guest code doesn't inspect any if the Result is not OK. Therefore,\n        // those steps are elided in this implementation.\n\n        let mut cursor = u32::from(cursor) as usize;\n\n        let mut buf_offset = 0;\n        let mut finished = true;\n\n        for value in self.skip(cursor) {\n            let value_bytes = value.as_ref();\n            let value_len = value_bytes.len();\n            let value_len_with_term = value_len + 1;\n            match buf.get_mut(buf_offset..buf_offset + value_len_with_term) {\n                None => {\n                    if buf_offset == 0 {\n                        // If there's not enough room to write even a single value, that's an error.\n                        // Write out the number of bytes necessary to fit this header value, or zero\n                        // on overflow to signal an error condition.\n                        memory.write(nwritten_out, value_len_with_term.try_into().unwrap_or(0))?;\n                        return Err(Error::BufferLengthError {\n                            buf: \"buf\",\n                            len: \"buf.len()\",\n                        });\n                    }\n                    // out of room, stop copying\n                    finished = false;\n                    break;\n                }\n                Some(dest) => {\n                    if dest.len() < value_len_with_term {\n                        // out of room, stop copying\n                        finished = false;\n                        break;\n                    }\n                    // copy the header bytes first\n                    dest[..value_len].copy_from_slice(value_bytes);\n                    // then add the terminating byte\n                    dest[value_len] = terminator;\n                    // now that the copy has succeeded, we update the cursor and the offset.\n                    cursor = if let Some(cursor) = cursor.checked_add(1) {\n                        cursor\n                    } else {\n                        return Err(Error::FatalError(\n                            \"multi_value_writer cursor overflowed\".to_owned(),\n                        ));\n                    };\n                    buf_offset += value_len_with_term;\n                }\n            }\n        }\n\n        let ending_cursor = if finished {\n            types::MultiValueCursorResult::from(-1i64)\n        } else {\n            types::MultiValueCursorResult::from(cursor as i64)\n        };\n\n        memory.write(nwritten_out, buf_offset.try_into().unwrap_or(0))?;\n\n        Ok(ending_cursor)\n    }\n}\n"
  },
  {
    "path": "test-fixtures/Cargo.toml",
    "content": "[package]\nname = \"test-fixtures\"\nversion = \"0.1.0\"\ndescription = \"Guest wasm programs intended for integration testing.\"\nauthors = [\"Fastly\"]\nedition = \"2024\"\nlicense = \"Apache-2.0 WITH LLVM-exception\"\npublish = false\n\n[dependencies]\nanyhow = \"1.0.86\"\nbase64 = \"0.21.2\"\nbitflags = \"1.3.2\"\nfastly = \"0.11.11\"\nfastly-shared = \"0.11.11\"\nfastly-sys = \"0.11.11\"\nhex-literal = \"0.4.1\"\nbytes = \"1.0.0\"\nhttp = \"1.1.0\"\nrustls-pemfile = \"1.0.3\"\nserde = \"1.0.114\"\nsha2 = \"0.10.8\"\nuuid = { version = \"1.15.1\", features = [\"v4\"] }\n"
  },
  {
    "path": "test-fixtures/combined_heap_limits.wat",
    "content": "(module\n\n  (import \"fastly_http_req\" \"body_downstream_get\"\n    (func $request_get\n      (param i32) (param i32)\n      (result i32)))\n  (import \"fastly_http_req\" \"header_value_get\"\n    (func $header_get\n      (param $handle i32) (param $name i32) (param $name_len i32) (param $out_value i32) (param $out_value_max_len i32) (param $out_value_written i32)\n      (result i32)))\n\n  (import \"fastly_http_resp\" \"new\"\n    (func $response_new\n      (param i32)\n      (result i32)))\n  (import \"fastly_http_resp\" \"header_insert\"\n    (func $response_add_header\n      (param i32) ;; the handle\n      (param i32) (param i32) ;; header string and length\n      (param i32) (param i32) ;; value string and length\n      (result i32)))\n  (import \"fastly_http_resp\" \"status_set\"\n    (func $response_set_status\n      (param i32) (param i32)\n      (result i32)))\n  (import \"fastly_http_resp\" \"send_downstream\"\n    (func $response_send\n      (param i32) (param i32) (param i32)\n      (result i32)))\n\n  (import \"fastly_http_body\" \"new\"\n    (func $response_new_body\n      (param i32)\n      (result i32)))\n  (import \"fastly_http_body\" \"write\"\n    (func $response_add_to_body\n      (param i32) ;; the handle\n      (param i32) ;; the pointer\n      (param i32) ;; the length\n      (param i32) ;; what end to write to ?\n      (param i32) ;; how many bytes were written\n      (result i32)))\n  (import \"fastly_http_body\" \"close\"\n    (func $response_done_with_body\n      (param i32) ;; the handle\n      (result i32)))\n\n  (import \"wasi_snapshot_preview1\" \"clock_time_get\"\n    (func $what_time_is_it\n      (param i32)\n      (param i64)\n      (param i32)\n      (result i32))) \n  (import \"wasi_snapshot_preview1\" \"proc_exit\"\n    (func $wasi_exit\n      (param i32)))\n\n  ;; we're going to fix a few memory locations as constants, just to avoid\n  ;; some other messiness, even though it's bad software engineering.\n  (global $request_handle_buffer i32 (i32.const 4))\n  (global $request_body_buffer i32 (i32.const 8))\n  (global $response_handle_buffer i32 (i32.const 12))\n  (global $response_body_buffer i32 (i32.const 16))\n  (global $amount_written i32 (i32.const 20))\n  (global $message_length i32 (i32.const 24))\n  (global $loop_time_response i32 (i32.const 32)) ;; actually is 16 bytes, and needs to\n                                                  ;; be 8-byte aligned.\n  (global $message i32 (i32.const 48))\n  (global $message_max_len i32 (i32.const 128))\n\n  ;; these are constant strings, defined at the end of this file. I\n  ;; wish there was an easier way to make this connection from here to\n  ;; the declared addresses there, but I couldn't figure one out.\n  (global $request_guest_kb i32 (i32.const 4096))\n  (global $request_body_kb i32 (i32.const 4224))\n  (global $request_header_kb i32 (i32.const 4352))\n  (global $seconds_to_take i32 (i32.const 4480))\n  (global $lorem_ipsum i32 (i32.const 4608))\n  (global $response_header_prefix i32 (i32.const 4736))\n  (global $response_header_number_start_offset i32 (i32.const 4750)) ;; where to add the number\n\n\n  (func $main (export \"_start\")\n    (local $start_time i64)\n    \n    ;; let's save what time we started, so that we can delay appropriately at\n    ;; the end\n    (i32.const 1) ;; we're looking for the monotonic clock\n    (i64.const 1000000000) ;; a precision of 1 second is just fine\n    (global.get $loop_time_response)\n    (call $what_time_is_it)\n    (call $maybe_error_die)\n    (global.get $loop_time_response)\n    (i64.load)\n    (local.set $start_time)\n\n    ;; pick up the request\n    (call $load_request)\n\n    ;; first we're going to push up the amount of direct space we can access\n    ;; within this particular wasm universe\n    (global.get $request_guest_kb)\n    (call $get_size) ;; does all the human string -> wasm number conversion, leaving\n                     ;; it on the top of the stack  \n    (call $extend_heap) ;; extend the heap by the total, which (again) is on\n                        ;; top of the stack\n\n    ;; now that we've consumed our unnecessary space, let's start creating\n    ;; the response we're going to send back.\n    (global.get $response_handle_buffer)\n    (call $response_new)\n    (call $maybe_error_die) ;; just a handy function that dies if we get\n                            ;; an error code from the runtime\n\n    ;; then we're going to add a bunch of headers to our outgoing message,\n    ;; to pad it out to the amount of header space we've been asked to\n    ;; consume.\n    (global.get $request_header_kb)\n    (call $get_size) ;; leaves the size on the top of the stack\n    (call $add_silly_headers)\n\n    ;; then we're going to extend the body by however many bytes the user\n    ;; asked us to include.\n    (global.get $request_body_kb)\n    (call $get_size)\n    (call $add_body)\n\n    ;; then we're going to delay for as long as the user told us to delay\n    ;; for.\n    (global.get $seconds_to_take)\n    (call $get_size)\n    (i64.extend_i32_u) ;; get this to the right side, but it's still in seconds\n    (i64.const 1000000000)\n    (i64.mul) ;; .. and now it's in nanoseconds\n    (local.get $start_time)\n    (i64.add) ;; now our target time is on top of the stack\n    (call $wait_until)\n\n    ;; and if we've survived all that, we're done. mark this as a 200\n    ;; response, send it back, and exit out.\n    (i32.const 200)\n    (call $send_response)\n    (i32.const 0)\n    (call $wasi_exit)\n    unreachable\n    )\n\n  ;; extend the heap by the given number of kilobytes. because WebAssembly\n  ;; only allows us to operate in terms of 64kb pages, this necessarily\n  ;; rounds up. if this operation fails we return a 500 error and quit\n  ;; immediately\n  (func $extend_heap (param $size_in_kb i32)\n    (local $original_heap_page_count i32)\n    (local $page_growth_target i32)\n\n    ;; we're going to get the current memory size now, because it'll\n    ;; be handy for computing a fill later.\n    (memory.size)\n    (local.set $original_heap_page_count)\n\n    ;; compute (size_in_kb + 63) `div` 64, which is a rounded up\n    ;; division by 64, which is the amount of heap to add in bytes\n    (local.get $size_in_kb)\n    (i32.const 63)\n    (i32.add)\n    (i32.const 64)\n    (i32.div_u)\n    (local.set $page_growth_target)\n\n    ;; grow the heap size. if this operation fails, then we should immediately\n    ;; quit with an error\n    (block $grow_grow\n       (local.get $page_growth_target)\n       (memory.grow)\n       (i32.const 0)\n       (i32.gt_s)\n       (br_if $grow_grow)\n       (i32.const 500)\n       (call $maybe_error_die))\n\n    ;; Do a little sanity check here. The new size should be the old size\n    ;; plus the given amount. If it's not, then something weird happened\n    ;; and this test is not working as intended.\n    (block $check_grow_worked\n      (local.get $original_heap_page_count)\n      (local.get $page_growth_target)\n      (i32.add)\n      (memory.size)\n      (i32.eq)\n      (br_if $check_grow_worked)\n      (i32.const 510)\n      (call $maybe_error_die))\n\n    ;; Yay! We allocated memory. Now write some things to it, just to make\n    ;; sure the compiler(s) aren't doing anything distressingly clever.\n    (local.get $original_heap_page_count)\n\n    ;; memory.fill takes three arguments, in the following order:\n    ;;   - the start of the block\n    ;;   - the value to fill\n    ;;   - the amount of space to fill (in bytes)\n    (i32.const 65536)\n    (i32.mul) ;; this is the base pointer for our new region. I told you\n              ;; it'd be handy!\n\n    (i32.const 0) ;; we want this filled with 0s\n\n    (local.get $page_growth_target)\n    (i32.const 65536)\n    (i32.mul) ;; the number of bytes to fill\n    (memory.fill))\n    \n  ;; add a bunch of silly headers to this particular message. each header\n  ;; will be a 64 byte block of lorem ipsum, just because I didn't want to\n  ;; worry about any sort of unicode nonsense.\n  (func $add_silly_headers (param $size_in_kb i32)\n    (local $i i32)\n\n    ;; compute the number of times we want to go around this loop. our text\n    ;; is 64 bytes long (assuming you don't count the null at the end), so\n    ;; we need 1024 / 64 = 16 copies of it per KB the user has requested.\n    (local.get $size_in_kb)\n    (i32.const 16)\n    (i32.mul) ;; whee! the total number we need is now on the stack.\n    (local.set $i)\n\n    ;; OK, now comes the loopy bit. recall that WASM loops go back to the\n    ;; start when you branch to their label, and blocks exit early when\n    ;; you jump to their label.\n    (loop $header_adding_loop\n      (block $while_test\n        (local.get $i)\n        (i32.const 0)\n        (i32.ne)\n        (br_if $while_test) ;; in other words, if i != 0 jump out of\n                            ;; this block and do the loop body; the\n                            ;; rest of this block is just the return\n        (return))\n\n      ;; whee! we get to add a header.\n      ;; first step in adding the header: figure out the name of the\n      ;; field. this involves using our little int2str helper function,\n      ;; which takes the value and a memory offset and returns the offset\n      ;; once it's done writing. it will *not* write the terminal null, so\n      ;; we'll need to do that.\n      (local.get $i)\n      (global.get $response_header_number_start_offset)\n      (call $int2str)\n      (i32.const 0)\n      (i32.store8)\n\n      ;; OK, our header string is set up, and we're just using a constant\n      ;; body, so we should be good to go.\n      (global.get $response_handle_buffer)\n      (i32.load)\n      (global.get $response_header_prefix)\n      (global.get $response_header_prefix) ;; \\ These compute the length of the header, and\n      (call $strlen)                       ;; / put it on the stack\n      (global.get $lorem_ipsum)\n      (global.get $lorem_ipsum)            ;; \\ these compute the length of the value, and\n      (call $strlen)                       ;; / put it on the stack\n      (call $response_add_header)\n      (call $maybe_error_die)\n\n      ;; OK, we added some data. subtract one from our counter and\n      ;; go again.\n      (local.get $i)\n      (i32.const 1)\n      (i32.sub)\n      (local.set $i)\n      (br $header_adding_loop))\n  \n    unreachable)\n\n\n  ;; add a body of the given size. we don't need to be too precious about\n  ;; this; HTTP bodies can be pretty much anything. so we're going to\n  ;; just write out 1k chunks from the start of our memory, and ignore\n  ;; the fact that this leaks internal state everywhere.\n  (func $add_body (param $size_in_kb i32)\n    (local $bytes_left_to_write i32)\n\n    ;; compute how many bytes to write. we do this because the spec\n    ;; allows partial writes, and I don't want to deal with writing\n    ;; an inner loop.\n    (local.get $size_in_kb)\n    (i32.const 1024)\n    (i32.mul)\n    (local.set $bytes_left_to_write)\n\n    ;; first thing's first: we need to create the body buffer inside\n    ;; the runtime\n    (global.get $response_body_buffer)\n    (call $response_new_body)\n    (call $maybe_error_die)\n\n    ;; now we're going to actually write to the buffer.\n    (block $body_write_block\n      (loop $body_write_loop\n        ;; if we've got 0 bytes left to write, let's just stop\n        (local.get $bytes_left_to_write)\n        (i32.const 0)\n        (i32.eq)\n        (br_if $body_write_block) ;; remember, this cancels execution of the\n                                  ;; rest of the block\n\n        ;; OK, we'll just tack on 1k more data\n        (global.get $response_body_buffer)\n        (i32.load) ;; push the handle\n        (i32.const 0) ;; push the pointer for the buffer; the start of memory,\n                      ;; because we don't care\n        (i32.const 1024) ;; the length of the buffer\n        (i32.const 0) ;; This means add it to the end; it's `body_write_end::back`\n                      ;; from `typenames.witx`. It appears that enumerations are\n                      ;; numbered in order from zero, for reference.\n        (global.get $amount_written)\n        (call $response_add_to_body)\n        (call $maybe_error_die)\n\n        ;; OK, let's see how many bytes we actually wrote, subtract that\n        ;; from our countdown, and loop\n        (local.get $bytes_left_to_write) ;; stack: [bytes_left_to_write]\n        (global.get $amount_written) ;; stack: [bytes_left_to_write, ptr to amount_written]\n        (i32.load) ;; stack: [bytes_left_to_write, amount_written]\n        (i32.sub) ;; stack: [bytes_left_to_write - amount_written]\n        (local.set $bytes_left_to_write) ;; stack: []\n\n        (br $body_write_loop)))\n\n    ;; ... and that's it. I thought I needed to call the close() function on\n    ;; the body, but it turns out that doing so invalidates the handle and\n    ;; means we can't send it in send_response. So ... just return here.\n    (return))\n\n  ;; Load the request information into the appropriate handle, so that\n  ;; we can interact with it in the future.\n  (func $load_request\n      (global.get $request_handle_buffer)\n      (global.get $request_body_buffer)\n      (call $request_get)\n      (call $maybe_error_die)\n    )\n\n  ;; get the size that the user wants from the provided header\n  ;; we're going to cheat a bit, here, and assume that the only\n  ;; reason we might get an error is because the user didn't pass\n  ;; this header. so if they didn't pass a header, or really if any\n  ;; other error happens looking up the field value, we're going to\n  ;; just return zero.\n  (func $get_size (param $string_ptr i32) (result i32)\n     (block $test_block\n       ;; first, let's get the string for this header\n       (global.get $request_handle_buffer)\n       (i32.load)\n       (local.get $string_ptr)\n       (local.get $string_ptr) ;; \\ These put the string length on the\n       (call $strlen)          ;; / stack next.\n       (global.get $message) ;; output buffer; leave 4 bytes for length\n       (global.get $message_max_len) ;; output buffer length; leave 4 bytes for length\n       (global.get $message_length) ;; here's the amount written, which we left space for\n       (call $header_get)\n       (i32.const 0) ;; if the value on the stack (the return code from\n       (i32.eq)      ;; header_get) is equal to 0\n       (br_if $test_block) ;; then break out of this block\n\n       ;; assume that we should default to 0.\n       (i32.const 0) ;; otherwise, return 0\n       (return))\n\n     ;; now we need to turn the darn ASCII string into a number\n     (global.get $message)\n     (global.get $message_length) ;; this is the message length ptr\n     (i32.load) ;; get the actual length\n     (call $to_int))\n\n\n  ;; Send OK back to the test harness; this thing is exiting normally\n  (func $send_response (param $result i32)\n      ;; set the status\n      (global.get $response_handle_buffer)\n      (i32.load)\n      (local.get $result)\n      (call $response_set_status)\n      (call $maybe_error_die)\n\n      ;; send it to the client\n      (global.get $response_handle_buffer)\n      (i32.load)\n      (global.get $response_body_buffer)\n      (i32.load)\n      (i32.const 0) ;; not streaming\n      (call $response_send)\n      (call $maybe_error_die)\n    )\n\n  ;; Convert the given ASCII string into an integer. This function does\n  ;; no safety checking, so SHOULD NOT BE USED IN PRODUCTION! ALARM\n  ;; ALARM ALARM!\n  (func $to_int (param $str i32) (param $len i32) (result i32)\n    (local $result i32)\n    (local $offset i32)\n\n    (i32.const 0)\n    (local.set $result) ;; result = 0\n    (i32.const 0)\n    (local.set $offset) ;; offset = 0\n\n    (loop $loop_body\n      ;; if offset >= len then break. this feels a little awkward, but\n      ;; it's how I got break to work. recall that in WAT, if the test\n      ;; is true and you `br_if` to a `block` label, then it pops you\n      ;; out of that label. so, in this case, if the offset is less\n      ;; than the length, then we pop out of $maybe_done. if it's >=,\n      ;; then we keep going ... which just fetches the results and\n      ;; returns.\n      (block $maybe_done\n        (local.get $offset)\n        (local.get $len)\n        (i32.lt_u)\n        (br_if $maybe_done)\n        (local.get $result)\n        (return))\n\n      ;; compute the next address in the string; $str + $offset\n      (local.get $str)\n      (local.get $offset)\n      (i32.add)\n\n      ;; read the next character, and turn it into a value by\n      ;; subtracting off the ASCII value of '0'; ASCII values\n      ;; are helpfully in order, so this gets us a numerical\n      ;; value from an ASCII one.\n      (i32.load8_u)\n      (i32.const 48) ;; ASCII '0' == 48\n      (i32.sub)\n\n      ;; Multiply the previous result by 10, then add the new\n      ;; value. The new value is on the stack from the sub.\n      (local.get $result)\n      (i32.const 10)\n      (i32.mul)\n      (i32.add)\n\n      ;; set the new result, and increment the offset\n      (local.set $result)\n      (local.get $offset)\n      (i32.const 1)\n      (i32.add)\n      (local.set $offset)\n      \n      ;; repeat!\n      (br $loop_body))\n\n    unreachable)\n\n  ;; convert the provided integer into a string, storing the values into\n  ;; memory at the given offset, and returning a new offset (where, presumably\n  ;; one could write more data).\n  ;;\n  ;; this is implemented as a recursive function, which is a bit scary\n  ;; but turns out to be not that bad.\n  (func $int2str (param $i i32) (param $offset i32) (result i32)\n    ;; OK, check for our base case: is $i less than 10. if it is, then\n    ;; we're just going to write our single character and return.\n    (block $base_case_exit\n      (local.get $i)\n      (i32.const 10)\n      (i32.ge_u)\n      (br_if $base_case_exit) ;; oh, well\n\n      ;; yay! this is a number less than 10, which is pretty easy to\n      ;; convert using knowledge of the ASCII table. ASCII '0' is 48,\n      ;; so we just need to add $i to 46, write that value to our\n      ;; offset, and return $offset + 1.\n      (local.get $offset) ;; this is where to write it\n      (i32.const 48)\n      (local.get $i)\n      (i32.add) ;; now we have the ASCII character\n      (i32.store8) ;; written!\n\n      ;; bump the offset and return\n      (local.get $offset)\n      (i32.const 1)\n      (i32.add)\n      (return))\n\n    ;; OK! we're bigger than 10. we're going to recursively call this\n    ;; function twice. First, with $i / 10, and then with $i % 10.\n    (local.get $i)\n    (i32.const 10)\n    (i32.div_u) ;; this puts the new i value for the first call on the stack\n    (local.get $offset) ;; conveniently, we just want to put it in the same place\n    (call $int2str)\n\n    ;; when we get back, the top of the stack is our new offset, which we\n    ;; will immediately save.\n    (local.set $offset)\n    ;; now compute $i % 10\n    (local.get $i)\n    (i32.const 10)\n    (i32.rem_u) ;; puts the new i on the stack\n    (local.get $offset) ;; ... which we just saved\n    (call $int2str)\n\n    ;; and this call will put the new offset on the stack ... which is\n    ;; what we wanted to return, so just return.\n    (return))\n\n  ;; compute the length of a NULL-terminated string. if this is not a\n  ;; NULL-terminated string, you're going to have a bad day.\n  (func $strlen (param $str i32) (result i32)\n    (local $offset i32)\n\n    ;; start at 0\n    (i32.const 0)\n    (local.set $offset) ;; offset = 0\n\n    (loop $loop_body\n\n      (block $maybe_done\n        ;; compute the pointer we're going to use here\n        (local.get $str)\n        (local.get $offset)\n        (i32.add)\n        (i32.load8_u)\n\n        ;; if it's zero, return the current offset, which happens to\n        ;; also be the length.\n        (i32.const 0)\n        (i32.ne)\n        (br_if $maybe_done)\n        (local.get $offset)\n        (return))\n\n      ;; increment the offset\n      (local.get $offset)\n      (i32.const 1)\n      (i32.add)\n      (local.set $offset)\n      \n      ;; repeat!\n      (br $loop_body))\n\n    unreachable)\n\n  (func $wait_until (param $target i64)\n    (loop $uffish_thought\n      (i32.const 1) ;; we're looking for the monotonic clock\n      (i64.const 1000000000) ;; a precision of 1 second is just fine\n      (global.get $loop_time_response)\n      (call $what_time_is_it)\n      (call $maybe_error_die)\n\n      (local.get $target)\n      (global.get $loop_time_response)\n      (i64.load)\n      (i64.gt_u)\n      (br_if $uffish_thought)))\n\n  ;; this is not super informative, but: check to see if the status code\n  ;; from whatever hostcall we just invoked came back as OK (0); if not,\n  ;; immediately exit with the return value as a diagnostic. (Not that it\n  ;; tells us where that value came from, but at least it's something?)\n  (func $maybe_error_die (param $status_code i32)\n    (block $test_block\n      (local.get $status_code)\n      (i32.const 0)\n      (i32.ne)\n      (br_if $test_block)\n      (return)\n    )\n    (local.get $status_code)\n    (call $wasi_exit)\n    unreachable\n    )\n\n  (memory (;0;) 1) ;; 1 * 64k = 64k :)\n  (export \"memory\" (memory 0))\n\n  ;; we're going to use the lowest 4k for globals and such, and then put\n  ;; a bunch of strings right after, spread 128 bytes apart.\n  (data $_header_guest_kb (i32.const 4096) \"guest-kb\\00\")\n  (data $_header_body_kb (i32.const 4224) \"body-kb\\00\")\n  (data $_header_header_kb (i32.const 4352) \"header-kb\\00\")\n  (data $_header_seconds_to_take (i32.const 4480) \"seconds-to-take\\00\")\n  (data $_lorem_ipsum (i32.const 4608) \"Lorem ipsum dolor sit amet, consectetur adipiscing elit viverra.\\00\")\n                                     ;; 1234567890123456789012345678901234567890123456789012345678901234\n                                     ;; 0         1         2         3         4         5         6\n  (data $_response_header_name (i32.const 4736) \"x-test-header-\")\n                                              ;; 012345678901234\n                                              ;;           1\n  )"
  },
  {
    "path": "test-fixtures/data/ca.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,6403E194E2FD15C4\n\nJXGAIQ6/3eVUy7R7PulfsnuNfTQye+HOw9EfLwsz6Tm6AxEiQkkXLyVP7BrmPRfa\nJ1WCngcSwyp1GpOD8qfQOaM0dxS+/bvVvB4eocTxgkDB42MG2HRZpJRrWGQ1VxUT\nvIdlV/zTyjC7aAQj5tTJkKN11+lwFxpcvlb3fNbQB23xWdi8yYGTHrEBw6cl5Szi\nipByvcbreKVH4eGrJLcC/H4wyrCe7CwzbP8deHNsH5WrxEWgkkjJMBAk6rzyp9su\nF6FNkJLup4Cfk7gmkpQsXs0Y+8LVnBfhc3dQrxdXZjVccFppvRDnoa9uzMQuS56U\njP+wloYeEVS7hfualKJFMNoC3oB7sI6PQGgYi047EkTEIBFFv1ryBcUAwzSlNdoe\n2MF1b4OpXrkR0J718QSE/ZpXe4K5HFtRtaE5b6Y47PbMoE3q+ldONEK2jLTvPtIv\nDOZWk8H67U0TOM53UQO5fVAntmPPuEiCX2cm0rISF7dwlgYG1obhdko3vir/6cJw\nCg9WbF5dGZYUUbZeNVdXHT+b7MaQOskXXBf+gUVRX/KIRLLP+yH1T06zxzYNHuSD\nJBkaklRuXBZnyRPTrqaFNmfgo9XoDY4Yd1QeKfD6Os5bvZeqBYgj3S+vmRUYZeE+\nPYKjzhIMjNmMP7ZP+h0iYs2rt6bRCgjTsd15ZjalPm+0f702sS51G1C8mdwy5ScQ\njX+1PTgpcfZ9aWZLmabC1Cvg1wOH0foAzbtag9qH/FUZgaLP0lfDhlAHCJwOrpRd\n3kegjfI1SXE2NGGq5GULU3bP50VYlWxpQNgWVImLnnpTCANfIV6QBx7JVVIxAJps\nLtzSt3fjU5wjNDE/GbwzzzA1DxissZzrXM2Q+ac69qPOE38e0PDsRahh2qf+1Db9\nozUvKOMbk8PAWUr5dtp9SFYgaF6gGoQY5JswP9tqHjTLYYYXZR21auZSIQXiG2yC\nwPBsSpnscBQSzo21+95f3jvrnq3HUFwcU5jE1W+RVhCQAQi0ZQdsYTyTsB5OksS3\nhXNBWFTVIOJjyUa0nwsh3hnLKwvl0/Sb8n5qssRMrx6ltN/JadA0I7dBHvjMr9n4\nZ1LkXQSKiGePcbzE8X4WhGApx6LwQgQm8OMqB3vw+HsGRmmzvpuy54mZgl8SV6ob\nR62uMVbuvNTJAr4XpGlsiZ+2FWKysvIXj5VEGwSfEHw8PIOuFgtJSS455iEokdid\nJJ9tfenJOCVBKLN5dYxzKbsOEvPdXIal9mM9HCPIqpIMhHRAzV1d47KUIrP6i8b8\nO8SXUZJz+JYvCUw6VYOorA7QRx8KBmZVRlJ3h5mocQ3BQIExu+XRQ8SVSYu/aedh\nzkDn9F0jg+LGLh10hh+L97glWWw48uFVFjMXUDhrcn2bA1xyvK0WNW8fnZ2pxjSj\nArDxC1qLiH0dRDAzXYOktmm7ToJppz7ncXnRAA7C9ZbGxj8w9D43Ypew+JBPjBKm\nXoXNsZLd8j7v66IJfKavA8+wV8u9GKsy6XaCX3y4/qzeMKiElii1bqcLz+Xvo3ZS\nkFPwN016SrFfF6jfwIaknZr2u9QELa5B5EIUsGC3PCwGUsqDNTkP3g==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test-fixtures/data/ca.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDqTCCApGgAwIBAgIUDXDr/2fouphqlB8iJASenWOr/XwwDQYJKoZIhvcNAQEL\nBQAwZDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk9yZWdvbjERMA8GA1UEBwwIUG9y\ndGxhbmQxEDAOBgNVBAoMB1ZpY2Vyb3kxHzAdBgkqhkiG9w0BCQEWEGF3aWNrQGZh\nc3RseS5jb20wHhcNMjMwNzI3MDAwODU5WhcNMzMwNzI0MDAwODU5WjBkMQswCQYD\nVQQGEwJVUzEPMA0GA1UECAwGT3JlZ29uMREwDwYDVQQHDAhQb3J0bGFuZDEQMA4G\nA1UECgwHVmljZXJveTEfMB0GCSqGSIb3DQEJARYQYXdpY2tAZmFzdGx5LmNvbTCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKxXdG4C6yEeLTtFPOXWTv1N\neEeJMLcAoupB9u3x0PYT+w+0ruAympviqGbEiyZL/qMKLYenLiQO+72VCISW5qfB\nZoCpwDxBon5TDUZ98JU93nVRml7uOg25G+KTs3aeJt6+rFDPNaNyxVcKgCuURB4y\nmwgosLUvxoEffFnHlURETLN4aSGQ6TLp8YEJp4EudTVo/l+kdhm6sLZMBkmUxnnl\nmuEc8ePAr1igYchz2tbcWRjzxoUOuEdoKaW2OCElNObt2WYPWzHs+6p1K8+KyTRY\n/pVOFtA43nuWmk++UHFthBAw9IqBuO0FMJr4SULnKfiTh5E9F+nZ0Q/1nfzzsAMC\nAwEAAaNTMFEwHQYDVR0OBBYEFGYM6HhP8yZ17eXw5nOfQ971u1l9MB8GA1UdIwQY\nMBaAFGYM6HhP8yZ17eXw5nOfQ971u1l9MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAFmFkUodKTXeT683GEj4SoiMbDL8d3x+Vc+kvLPC2Jloru4R\nQo0USu3eJZjNxKjmPbLii8gzf5ZmZHdytWQ+5irYjXBHrE9tPgmpavhM+0otpnUd\nvYosnfwv/aQEIiqeMkpqzbSKvb2I+TVpAC1xb6qbYE95tnsX/KEdAoJ/SAcZLGYQ\nLKGTjz3eKlgUWy69uwzHXkie8hxDVRlyA7cFY4AAqsLhL2KQPWtMT7fRKrVKfLYd\nQq7tJAMLnPnAdAUousI0RDcLpB8adGkhZH66lL4oV9U+aQ0dA0oiqSKZtMoHeWbr\n/L0ti7ZOfxOxRRCzt8KdLo/kGNTfAz+74P0MY80=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test-fixtures/data/ca.srl",
    "content": "3A9F7B82F32561D05823FDF2AE90DE1DB771E518\n"
  },
  {
    "path": "test-fixtures/data/client.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDvjCCAqagAwIBAgIUOp97gvMlYdBYI/3yrpDeHbdx5RgwDQYJKoZIhvcNAQEL\nBQAwZDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk9yZWdvbjERMA8GA1UEBwwIUG9y\ndGxhbmQxEDAOBgNVBAoMB1ZpY2Vyb3kxHzAdBgkqhkiG9w0BCQEWEGF3aWNrQGZh\nc3RseS5jb20wHhcNMjMwNzI3MDAxOTU0WhcNMzMwNzI0MDAxOTU0WjB1MQswCQYD\nVQQGEwJVUzEPMA0GA1UECAwGT3JlZ29uMREwDwYDVQQHDAhQb3J0bGFuZDEQMA4G\nA1UECgwHVmljZXJveTEPMA0GA1UECwwGQ2xpZW50MR8wHQYJKoZIhvcNAQkBFhBh\nd2lja0BmYXN0bHkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nz27x1GpD46K6b9/3PNyZYKgTL9GBbpLAVF8Uebd34ftUfnWZ3ER+x6A1YbacHnL1\n12diPPevyYkpXuiujwCeswYNrZHEtiRfAvrzBRhnhL8owQTxjOcG4EOzR7Je556F\nTq8kNth5iHckORjmXiV9ZahbLv/zBFpkXpDeze62zd8y9chPNEqcrLZBOb4UoKXm\nOt1lIdeo23nysR4rC6XemWNSFcZv9zagUzliMeca3XN2RIUAFZv4o+gYPqqXQi+0\na+OOq0jnKpawW+avn2UG7wzXGlLcVOvLe5BOCA1RfWtR8w03MFdvoBAesXJ4xGX1\nROUzelldedmpqtvORdhmGQIDAQABo1cwVTAfBgNVHSMEGDAWgBRmDOh4T/Mmde3l\n8OZzn0Pe9btZfTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAaBgNVHREEEzARggls\nb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBAJ84GzmmqsmmtqXcmZIH\ni644p8wIc/DXPqb7zzAVm9FXpFgW3mN4xu1JYWu+rb1sge8uIm7Vt5Isd4CZ89XI\nF2Q2DS/rKMQmjgSDReWm9G+qZROwuhNDzK85e73Rw2EdX6cXtAGR1h3IdOTIv1FC\nUElFER31U8i4J9pxUZF/FTzlPEA1agqMsO6hQlj/A9B6TtzL7SSxCFBBaFbNCLMC\nD/WCrIoklNV5TwutYG80EYZhJlfUJPDQBphkcetDBI0L/KL/n20bg8OR/epGD5++\nqKIulxf9iUR5QHm2fWKdTLOuADmV+lc925gIqGhFhjVvpNPOcdckecQUp3vCNu2/\nHrM=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test-fixtures/data/client.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIICujCCAaICAQAwdTELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk9yZWdvbjERMA8G\nA1UEBwwIUG9ydGxhbmQxEDAOBgNVBAoMB1ZpY2Vyb3kxDzANBgNVBAsMBkNsaWVu\ndDEfMB0GCSqGSIb3DQEJARYQYXdpY2tAZmFzdGx5LmNvbTCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAM9u8dRqQ+Oium/f9zzcmWCoEy/RgW6SwFRfFHm3\nd+H7VH51mdxEfsegNWG2nB5y9ddnYjz3r8mJKV7oro8AnrMGDa2RxLYkXwL68wUY\nZ4S/KMEE8YznBuBDs0eyXueehU6vJDbYeYh3JDkY5l4lfWWoWy7/8wRaZF6Q3s3u\nts3fMvXITzRKnKy2QTm+FKCl5jrdZSHXqNt58rEeKwul3pljUhXGb/c2oFM5YjHn\nGt1zdkSFABWb+KPoGD6ql0IvtGvjjqtI5yqWsFvmr59lBu8M1xpS3FTry3uQTggN\nUX1rUfMNNzBXb6AQHrFyeMRl9UTlM3pZXXnZqarbzkXYZhkCAwEAAaAAMA0GCSqG\nSIb3DQEBCwUAA4IBAQAAU/iqM3cx76vLFcV18xnnpi7jQjkxTvP5uq/7gisChgv1\nZ26DS6ZYihlFKJYt0fFXCh9CG61+ttkw6Cz53G62+iV/C3+0tamn4G8EQddZshg7\nQpRousktgfJKHIIQLfzqb6fe8G49jmQlAjdseenBn1dHndxRLNOtM2c/smaGaafU\nyTdPCdLDleqfCcUMvMjGp/WYo9j33Mw4ZN1C9IUjFc0+3Ythsw0hrZmOkr0X90v4\nH77tZsS+ySWbeInD7Ku6IJzDimYAlXNG251LFdgSdX0dkxCfTmhFf9Z6Gl/jNufO\nZEiHpX9oR7tFq89Ww6eMgW8gKJoyH9+1N91PtxCo\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "test-fixtures/data/client.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAz27x1GpD46K6b9/3PNyZYKgTL9GBbpLAVF8Uebd34ftUfnWZ\n3ER+x6A1YbacHnL112diPPevyYkpXuiujwCeswYNrZHEtiRfAvrzBRhnhL8owQTx\njOcG4EOzR7Je556FTq8kNth5iHckORjmXiV9ZahbLv/zBFpkXpDeze62zd8y9chP\nNEqcrLZBOb4UoKXmOt1lIdeo23nysR4rC6XemWNSFcZv9zagUzliMeca3XN2RIUA\nFZv4o+gYPqqXQi+0a+OOq0jnKpawW+avn2UG7wzXGlLcVOvLe5BOCA1RfWtR8w03\nMFdvoBAesXJ4xGX1ROUzelldedmpqtvORdhmGQIDAQABAoIBAQCsbu6KhDehMDHJ\nNCWjK0I4zh78/iyZDVbiDBPKRpBag4GuifX329yD95LIgnNvAGOKxz8rrT4sy19f\nrQ8Ggx5pdVvDcExUmRF+Obvw/WN4PywSoBhn59iYbs7Gh+lKo0Tvrrns+bC1l0y+\nRguiMYn3CqeZ/1w1vyp2TflYuNqvcR4zMzJ4dN474CCLPIUX9OfK21Lbv/UMdguF\nRs/BuStucqaCzEtTLyZYlxQc1i8S8Uy2yukXR6TYWJOsWZj0KIgH/YI7ZgzvTIxL\nax4Hn4jIHPFSJ+vl2ehDKffkQQ0lzm60ASkjaJY6GsFoTQzsmuafpLIAoJbDbZR1\ntxPSFC+BAoGBAPbp6+LsXoEY+4RfStg4c/oLWmK3aTxzQzMY90vxnMm6SJTwTPAm\npO+Pp2UGyEGHV7hg3d+ItWpM9QGVmsjm+punIfc0W/0+AVUonjPLfv44dz7+geYt\n/oeMv4RTqCclROvtQTqV6hHn4E3Xg061miEe6OxYmqfZuLD2nv2VlsQRAoGBANcR\nGAqeClQtraTnu+yU9U+FJZfvSxs1yHr7XItCMtwxeU6+nipa+3pXNnKu0dKKekUG\nPCdUipXgggA6OUm2YFKPUhiXJUNoHCj45Tkv2NshGplW33U3NcCkDqL7vvZoBBfP\nOPxEVRVEIlwp/WzEambs9MjWoecEaOe7/3UCVumJAoGANlfVquQLCK7O7JtshZon\nLGlDQ2bKqptTtvNPuk87CssNHnqk9FYNBwy+8uVDPejjzZjEPGaCRxsY8XhT0NPF\nZGysdRP5CwuSj4OZDh1DngAffqXVQSvuUTcRD7a506PIP4TATnygP8ChBYDhTXl6\nqr961EnMABVTKN+eroE15YECgYEAv+YLyqV71+KuNx9i6lV7kcnfYnNtU8koqruQ\ntt2Jnjoy4JVrcaWfEGmzNp9Qr4lKUj6e/AUOZ29c8DEDnwcxaVliynhLEptZzSFQ\n/zb3S4d9QWdnmiJ6Pvrj6H+yxBDJ3ijT0xxxwrj547y/2QZlXpN+U5pX+ldP974i\n0dgVjukCgYEArxv0dO2VEguWLx5YijHiN72nDDI+skbfkQkvWQjA7x8R9Xx1SWUl\nWeyeaaV5rqfJZF1wBCK5VJndjbOGhPh6u/0mpeYw4Ty3+CKN2WoikQO27qYfMZW5\nvvT7m9ZR+gkm2TjZ+pZuilz2gqu/yMJKl8Fi8Q7dsb8eWedWQXjbUZg=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test-fixtures/data/device-detection-mapping.json",
    "content": "{\n    \"Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 [FBAN/FBIOS;FBAV/8.0.0.28.18;FBBV/1665515;FBDV/iPhone4,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/7.0.4;FBSS/2; FBCR/Telekom.de;FBID/phone;FBLC/de_DE;FBOP/5]\": {\n        \"user_agent\": {},\n        \"os\": {},\n        \"device\": {\n            \"name\": \"iPhone\",\n            \"brand\": \"Apple\",\n            \"model\": \"iPhone4,1\",\n            \"hwtype\": \"Mobile Phone\",\n            \"is_ereader\": false,\n            \"is_gameconsole\": false,\n            \"is_mediaplayer\": false,\n            \"is_mobile\": true,\n            \"is_smarttv\": false,\n            \"is_tablet\": false,\n            \"is_tvplayer\": false,\n            \"is_desktop\": false,\n            \"is_touchscreen\": true\n        }\n    },\n    \"ghosts-app/1.0.2.1 (ASUSTeK COMPUTER INC.; X550CC; Windows 8 (X86); en)\": {\n        \"user_agent\": {},\n        \"os\": {},\n        \"device\": {\n            \"name\": \"Asus TeK\",\n            \"brand\": \"Asus\",\n            \"model\": \"TeK\",\n            \"is_desktop\": false\n        }\n    }\n}"
  },
  {
    "path": "test-fixtures/data/geolocation-mapping.json",
    "content": "{\n    \"127.0.0.1\": {\n        \"as_name\": \"Fastly Test\",\n        \"as_number\": 12345,\n        \"area_code\": 123,\n        \"city\": \"Test City\",\n        \"conn_speed\": \"broadband\",\n        \"conn_type\": \"wired\",\n        \"continent\": \"NA\",\n        \"country_code\": \"CA\",\n        \"country_code3\": \"CAN\",\n        \"country_name\": \"Canada\",\n        \"latitude\": 12.345,\n        \"longitude\": 54.321,\n        \"metro_code\": 0,\n        \"postal_code\": \"12345\",\n        \"proxy_description\": \"?\",\n        \"proxy_type\": \"?\",\n        \"region\": \"BC\",\n        \"utc_offset\": -700\n    },\n    \"0000:0000:0000:0000:0000:0000:0000:0001\": {\n        \"as_name\": \"Fastly Test IPv6\",\n        \"as_number\": 12345,\n        \"area_code\": 123,\n        \"city\": \"Test City IPv6\",\n        \"conn_speed\": \"broadband\",\n        \"conn_type\": \"wired\",\n        \"continent\": \"NA\",\n        \"country_code\": \"CA\",\n        \"country_code3\": \"CAN\",\n        \"country_name\": \"Canada\",\n        \"latitude\": 12.345,\n        \"longitude\": 54.321,\n        \"metro_code\": 0,\n        \"postal_code\": \"12345\",\n        \"proxy_description\": \"?\",\n        \"proxy_type\": \"?\",\n        \"region\": \"BC\",\n        \"utc_offset\": -700\n    }\n}"
  },
  {
    "path": "test-fixtures/data/json-config_store.json",
    "content": "{\n    \"cat\": \"meow\",\n    \"dog\": \"woof\"\n}\n"
  },
  {
    "path": "test-fixtures/data/json-dictionary.json",
    "content": "{\n    \"cat\": \"meow\",\n    \"dog\": \"woof\"\n}\n"
  },
  {
    "path": "test-fixtures/data/json-kv_store-invalid_1.json",
    "content": "{\n    \"first\": {\n    }\n}\n"
  },
  {
    "path": "test-fixtures/data/json-kv_store-invalid_2.json",
    "content": "{\n    \"first\": {\n        \"data\": \"this is invalid because it has both data and file\",\n        \"file\": \"../test-fixtures/data/kv-store.txt\"\n    }\n}\n"
  },
  {
    "path": "test-fixtures/data/json-kv_store.json",
    "content": "{\n    \"first\": \"This is some data\",\n    \"second\": {\n        \"file\": \"../test-fixtures/data/kv-store.txt\"\n    },\n    \"third\": {\n        \"data\": \"third\",\n        \"metadata\": \"some metadata\"\n    }\n}\n"
  },
  {
    "path": "test-fixtures/data/json-secret_store.json",
    "content": "{\n    \"first\": \"first secret\",\n    \"second\": \"another secret\"\n}\n"
  },
  {
    "path": "test-fixtures/data/kv-store.txt",
    "content": "More data"
  },
  {
    "path": "test-fixtures/data/my-acl-1.json",
    "content": "{\n  \"entries\": [\n    { \"op\": \"update\", \"prefix\": \"1.2.3.0/24\", \"action\": \"BLOCK\" },\n    { \"op\": \"create\", \"prefix\": \"192.168.0.0/16\", \"action\": \"BLOCK\" },\n    { \"op\": \"update\", \"prefix\": \"23.23.23.23/32\", \"action\": \"ALLOW\" },\n    { \"op\": \"create\", \"prefix\": \"1.2.3.4/32\", \"action\": \"ALLOW\" }\n  ]\n}\n"
  },
  {
    "path": "test-fixtures/data/my-acl-2.json",
    "content": "{\n  \"entries\": [\n    { \"op\": \"update\", \"prefix\": \"2000::/24\", \"action\": \"BLOCK\" },\n    { \"op\": \"create\", \"prefix\": \"FACE::/16\", \"action\": \"ALLOW\" }\n  ]\n}\n"
  },
  {
    "path": "test-fixtures/data/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDvjCCAqagAwIBAgIUOp97gvMlYdBYI/3yrpDeHbdx5RcwDQYJKoZIhvcNAQEL\nBQAwZDELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk9yZWdvbjERMA8GA1UEBwwIUG9y\ndGxhbmQxEDAOBgNVBAoMB1ZpY2Vyb3kxHzAdBgkqhkiG9w0BCQEWEGF3aWNrQGZh\nc3RseS5jb20wHhcNMjMwNzI3MDAxODA5WhcNMzMwNzI0MDAxODA5WjB1MQswCQYD\nVQQGEwJVUzEPMA0GA1UECAwGT3JlZ29uMREwDwYDVQQHDAhQb3J0bGFuZDEQMA4G\nA1UECgwHVmljZXJveTEPMA0GA1UECwwGU2VydmVyMR8wHQYJKoZIhvcNAQkBFhBh\nd2lja0BmYXN0bHkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nvaJbeui08EYihCfktu3Ary6CnxNmsg8kCXPddRdGa9+QJGGkSNBf4AseYI/jXPpO\n/eMixAnlv5ebie9vTazKPNxXwDOzoFu3Gt8jFKwI0jsmkcIIk1wC0CDQrsAm50uC\nla6zEGWLF/Fd5lfoXZ5Jo20LgHV7t9tXsZCzT3RgC0dKkrnGumJK5g1OYGN6l4wh\ni2zOCMvIT/V5Am7epCZx1GS6O2mVLZhrfgpfFiRx6o1QV07XXnW+LFz8Rk5cwOUl\nt4K9h0DSc9p5yPvgz3DDw2qA4rcr7BK18ePUJR1khey8aDQWzHfSfVj8OwLlhLN8\nixdXv+YLvGKaOsVH2cvt2wIDAQABo1cwVTAfBgNVHSMEGDAWgBRmDOh4T/Mmde3l\n8OZzn0Pe9btZfTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIE8DAaBgNVHREEEzARggls\nb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggEBAFE8Kycq90Dnij9WJx5n\nBuR+KU/o4nV3NjVHmo503KxIW6PFuOMSc65Uxq0Q6IZmJvzovdnCRRda+n9c/1dV\nAPFTwG5QCOJOEzK7SgJpvxlkZ+39fMBPpakcBOUobFqifaltKum5x3pT9zn1n2jx\nEGay3RWT45wXtKBEd0zZ/vMu0fFGoMBzT696ywVMzbSI7o745UN3fwrE8FVGkJXk\nV5F+6ugjXpqhqPi3rZRfC+ZJolFe+blj5wlXScwg/W5BKQDbotCZFCswk00J++/M\n516yQItLQUU4lYJzVAqlrHIwJeBhAinHlpfNRqQJtxVE2VIzlMRp4n2VxZO64IfH\nxaw=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test-fixtures/data/server.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIICujCCAaICAQAwdTELMAkGA1UEBhMCVVMxDzANBgNVBAgMBk9yZWdvbjERMA8G\nA1UEBwwIUG9ydGxhbmQxEDAOBgNVBAoMB1ZpY2Vyb3kxDzANBgNVBAsMBlNlcnZl\ncjEfMB0GCSqGSIb3DQEJARYQYXdpY2tAZmFzdGx5LmNvbTCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAL2iW3rotPBGIoQn5LbtwK8ugp8TZrIPJAlz3XUX\nRmvfkCRhpEjQX+ALHmCP41z6Tv3jIsQJ5b+Xm4nvb02syjzcV8Azs6BbtxrfIxSs\nCNI7JpHCCJNcAtAg0K7AJudLgpWusxBlixfxXeZX6F2eSaNtC4B1e7fbV7GQs090\nYAtHSpK5xrpiSuYNTmBjepeMIYtszgjLyE/1eQJu3qQmcdRkujtplS2Ya34KXxYk\nceqNUFdO1151vixc/EZOXMDlJbeCvYdA0nPaecj74M9ww8NqgOK3K+wStfHj1CUd\nZIXsvGg0Fsx30n1Y/DsC5YSzfIsXV7/mC7ximjrFR9nL7dsCAwEAAaAAMA0GCSqG\nSIb3DQEBCwUAA4IBAQBza1mDXtkyCeUbQzaNW0QamUWmGTijIAP6klE4w2Wy0cN5\nXrkkRQXMRGDfwOnkTqsv8p1AHKjPdnC2fY4a4fn5qR4/6dBVB40889UH39w1qcDW\nL+ybOPDA/UjhiXoL2EiW5iLyHeK/ttHFgvq5niHUmYk/ZVElS4xD0FAIIagxDa/u\nzTXYwlf+ZT4KdjPdx/s9evRQQWrS5Z06Rv9Lzkl5A9WCUXgyGw16MvU0rA/SFEgu\n98XqUY7wa5DC7RuW38IKk06CGBDWNc3l1ZxQ+VB/sym702nbV2HFZT46viflll1a\nFjgQEu8ztbDfgooRGGHimZZXA2dBEHirgdM0bdzy\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "test-fixtures/data/server.ext",
    "content": "authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE \nkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.1 = localhost\nIP.1 = 127.0.0.1 \n"
  },
  {
    "path": "test-fixtures/data/server.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAvaJbeui08EYihCfktu3Ary6CnxNmsg8kCXPddRdGa9+QJGGk\nSNBf4AseYI/jXPpO/eMixAnlv5ebie9vTazKPNxXwDOzoFu3Gt8jFKwI0jsmkcII\nk1wC0CDQrsAm50uCla6zEGWLF/Fd5lfoXZ5Jo20LgHV7t9tXsZCzT3RgC0dKkrnG\numJK5g1OYGN6l4whi2zOCMvIT/V5Am7epCZx1GS6O2mVLZhrfgpfFiRx6o1QV07X\nXnW+LFz8Rk5cwOUlt4K9h0DSc9p5yPvgz3DDw2qA4rcr7BK18ePUJR1khey8aDQW\nzHfSfVj8OwLlhLN8ixdXv+YLvGKaOsVH2cvt2wIDAQABAoIBAEvF8z3SfHJB5Arg\nkfBSYhrdv83mh7OAf0rTpFrkOPxjsYoIBggeUyEH8FRvSk9dqXCjcMHanpYG81yT\ncusbrxfQh7PCNPVPkIPJQ5BACapPfmLhoGGZc3pMknYxS5pCPuSmkOBtYr3ncTjY\nSX4XAJ+vs9fZmdzmZU0LX8rQ2ovGeFrbX0jRr3g6B85VsAeGJ9TDSSerPnO0mtFI\nZSdl1wttuZErRRygt0zf00wlVh1iPXXe8J3oW4spFPwCPKahhNc7Ezz6HWo6ml+h\nDlmbcjz1QIqKLd8DCmGh/gx7X1lC/AKEq2c6KIMDoaBKjICwh9Kho0/OsJQ3+BKn\n/yvBsvECgYEA+ccNBnmLd+VrLtTh7+dKRAlQsDkirnvZ3bpA3QJWAA97QYpz5riy\nIDQO3UV6jkcMbcaj3wOwKAi9k3FwCPaz93+//W6JpQWnwjH9c/ziogAJ/g6Pman/\nhHTKJO044nCZh3WLQpSOwTz0yFRQM6q1cEollhT2hKnFxbtDzVZx+CMCgYEAwlu+\ne6UAs3xrftiX70UkPbGNBLoP0wNJFqT4TautmVbUF3s2tSdyf/wbWuhTIsYYwRdv\nB5i7Or1e/DsVH82cq8z9ZB15EVTR4awshLVJz15RvqEtITfZrZ2hdFf0CKk/9J4w\nhzmRG14qz/GlL8CVqork5F4bd10jZscVdQqA8ukCgYBlCzEpvWG+TwDdISGFe3t/\nqoUJxRNSoqewGvjCb3965shl6yyX2X+1p1mcCc9aX0OX5RPF1CgfCeonC2zXM3X6\nWaPBUkY8i90hojd2BIdqIbnpHNravvqvCs/7wDuS3xo8wkBj3tUhNxePMwx+2kAr\n/NLXtANGB6gKJYd4OdBBIQKBgD4gf3ocm2XETsRETgTQ8C28VJx/MVG9Sh6v6yNA\nzoQmijNbUniDvIkGuGPNwc1qzzzh1b7y5l53bCZqaG07F2qfYxweg7WzjEd79tsQ\n7CAaQT0TXk6xAKcLrTF4b+xY1bXG3zJKh4TdDAhecPQbtnvGXDZXkqYMIqXW25gH\nHIMJAoGAUzABCDKoI1qsznMVqM7jC77akGHlwlWI9BmYhWOXIrO5th5CHFJK+YBE\nsG44dwnd2Sv/o2REn34cM8HETq7cyuW+Et2N+VyogyGlS3W+HCHkMYINU/ZPeGMF\nwiwXklON2CtatN31JDEeMusyySki5I/lw9at2TvDsdrLGeP4i3I=\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test-fixtures/return_ok.wat",
    "content": "(module\n  (import \"fastly_http_resp\" \"new\"\n    (func $response_new\n      (param i32)\n      (result i32)))\n  (import \"fastly_http_body\" \"new\"\n    (func $body_new\n      (param i32)\n      (result i32)))\n  (import \"fastly_http_resp\" \"status_set\"\n    (func $response_set_status\n      (param i32) (param i32)\n      (result i32)))\n  (import \"fastly_http_resp\" \"send_downstream\"\n    (func $response_send\n      (param i32) (param i32) (param i32)\n      (result i32)))\n \n  (import \"wasi_snapshot_preview1\" \"proc_exit\"\n    (func $wasi_exit\n      (param i32)))\n\n  ;; we're going to fix a few memory locations as constants, just to avoid\n  ;; some other messiness, even though it's bad software engineering.\n  (global $response_handle_buffer i32 (i32.const 4))\n  (global $body_handle_buffer i32 (i32.const 8))\n\n  (func $main (export \"_start\")\n    (i32.const 200)\n    (call $send_response)\n    (i32.const 0)\n    (call $wasi_exit)\n    unreachable\n    )\n\n  ;; Send a response back to the test harness, using the status code\n  ;; provided in the first argument. This message will have no body,\n  ;; and no headers, just the response code. It will fail catastrophically\n  ;; (i.e., end this execution) if anything goes wrong in the process.\n  (func $send_response (param $result i32)\n      ;; create the response\n      (global.get $response_handle_buffer)\n      (call $response_new)\n      (call $maybe_error_die)\n\n      ;; create an empty body\n      (global.get $body_handle_buffer)\n      (call $body_new)\n      (call $maybe_error_die)\n\n      ;; set the status\n      (global.get $response_handle_buffer)\n      (i32.load)\n      (local.get $result)\n      (call $response_set_status)\n      (call $maybe_error_die)\n\n      ;; send it to the client\n      (global.get $response_handle_buffer)\n      (i32.load)\n\n      ;; empty body\n      (global.get $body_handle_buffer)\n      (i32.load)\n\n      (i32.const 0) ;; not streaming\n      (call $response_send)\n      (call $maybe_error_die)\n    )\n\n  ;; this is not super informative, but: check to see if the status code\n  ;; from whatever hostcall we just invoked came back as OK (0); if not,\n  ;; immediately exit with the return value as a diagnostic. (Not that it\n  ;; tells us where that value came from, but at least it's something?)\n  (func $maybe_error_die (param $status_code i32)\n    (block $test_block\n      (local.get $status_code)\n      (i32.const 0)\n      (i32.ne)\n      (br_if $test_block)\n      (return)\n    )\n    (local.get $status_code)\n    (call $wasi_exit)\n    unreachable\n    )\n\n  (memory (;0;) 1) ;; 1 * 64k = 64k :)\n  (export \"memory\" (memory 0))\n\n  )"
  },
  {
    "path": "test-fixtures/src/bin/acl.rs",
    "content": "//! A guest program to test that acls works properly.\nuse fastly::acl::Acl;\nuse fastly::Error;\nuse std::net::{Ipv4Addr, Ipv6Addr};\n\nfn main() -> Result<(), Error> {\n    match Acl::open(\"DOES-NOT-EXIST\") {\n        Err(fastly::acl::OpenError::AclNotFound) => { /* OK */ }\n        Err(other) => panic!(\"expected error opening non-existant acl, got: {:?}\", other),\n        _ => panic!(\"expected error opening non-existant acl, got Ok\"),\n    }\n\n    let acl1 = Acl::open(\"my-acl-1\")?;\n\n    match acl1.try_lookup(Ipv4Addr::new(192, 168, 1, 1).into())? {\n        Some(lookup_match) => {\n            assert_eq!(lookup_match.prefix(), \"192.168.0.0/16\");\n            assert!(lookup_match.is_block());\n        }\n        None => panic!(\"expected match\"),\n    };\n    match acl1.try_lookup(Ipv4Addr::new(23, 23, 23, 23).into())? {\n        Some(lookup_match) => {\n            assert_eq!(lookup_match.prefix(), \"23.23.23.23/32\");\n            assert!(lookup_match.is_allow());\n        }\n        None => panic!(\"expected match\"),\n    };\n    if let Some(lookup_match) = acl1.try_lookup(Ipv4Addr::new(100, 100, 100, 100).into())? {\n        panic!(\"expected no match, got: {:?}\", lookup_match);\n    }\n\n    let acl2 = Acl::open(\"my-acl-2\")?;\n\n    match acl2.try_lookup(Ipv6Addr::new(0x2000, 0, 0, 0, 0, 1, 2, 3).into())? {\n        Some(lookup_match) => {\n            assert_eq!(lookup_match.prefix(), \"2000::/24\");\n            assert!(lookup_match.is_block());\n        }\n        None => panic!(\"expected match\"),\n    };\n    match acl2.try_lookup(Ipv6Addr::new(0xFACE, 0, 2, 3, 4, 5, 6, 7).into())? {\n        Some(lookup_match) => {\n            assert_eq!(lookup_match.prefix(), \"face::/16\");\n            assert!(lookup_match.is_allow());\n        }\n        None => panic!(\"expected match\"),\n    };\n    if let Some(lookup_match) =\n        acl2.try_lookup(Ipv6Addr::new(0xFADE, 1, 2, 3, 4, 5, 6, 7).into())?\n    {\n        panic!(\"expected no match, got: {:?}\", lookup_match);\n    };\n\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/args.rs",
    "content": "fn main() {\n    let args = Vec::from_iter(std::env::args());\n\n    assert_eq!(args.len(), 1);\n    assert_eq!(args[0], \"compute-app\");\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/async_io.rs",
    "content": "//! Tests the `async_io` hostcalls by setting up one of each kind of async item, each against\n//! a distinct backend. Checks each for readiness, and then does a select with timeout against\n//! all of them.\n\nuse std::io::Write;\nuse std::str::FromStr;\n\nuse fastly::handle::{BodyHandle, RequestHandle, ResponseHandle};\nuse fastly::http::{HeaderName, HeaderValue, Method, StatusCode, Url};\nuse fastly::Error;\nuse fastly::Request;\nuse fastly_shared::{CacheOverride, FastlyStatus};\n\nfn is_ready(handle: u32) -> bool {\n    let mut ready_out: u32 = 0;\n    unsafe {\n        let status = fastly_sys::fastly_async_io::is_ready(handle, &mut ready_out);\n        assert_eq!(status, FastlyStatus::OK);\n    }\n    ready_out == 1\n}\n\nfn append_header(resp: &mut ResponseHandle, header: impl ToString, value: impl ToString) {\n    resp.append_header(\n        &HeaderName::from_str(&header.to_string()).unwrap(),\n        &HeaderValue::from_str(&value.to_string()).unwrap(),\n    )\n}\n\nfn test_select() -> Result<(), Error> {\n    let mut ds_resp = ResponseHandle::new();\n    let pass = CacheOverride::pass();\n\n    // The \"simple\" case is a pending request, where the async item is awaiting the response headers\n    let mut simple_req = RequestHandle::new();\n    simple_req.set_url(&Url::parse(\"http://simple.org/\")?);\n    simple_req.set_cache_override(&pass);\n    let simple_pending_req = simple_req.send_async(BodyHandle::new(), \"Simple\")?;\n    let simple_pending_req_handle = simple_pending_req.as_u32();\n\n    // The \"read body\" case involves a sync `send`, followed by treating the response body as an async item\n    // to read from\n    let mut read_body_req = RequestHandle::new();\n    read_body_req.set_url(&Url::parse(\"http://readbody.org/\")?);\n    read_body_req.set_cache_override(&pass);\n    let (_read_body_resp, read_body) = read_body_req.send(BodyHandle::new(), \"ReadBody\")?;\n    let read_body_handle = unsafe { read_body.as_u32() };\n\n    // This request is used as a synchronization mechanism for the purposes of\n    // this test\n    let write_body_sync_req = Request::get(\"http://writebody.org/\")\n        .send_async(\"Semaphore\")\n        .expect(\"request begins sending\");\n\n    // The \"write body\" case involves a `send_async_streaming` call, where the streaming body is the\n    // async item of interest. To test readiness, we need to ensure the body is large enough that Hyper\n    // won't try to buffer it, and hence we can see backpressure on streaming. We do this by including\n    // a large (4MB) prefix of the body _prior to_ streaming.\n    const INITIAL_BYTE_COUNT: usize = 4 * 1024 * 1024;\n    let mut write_body_req = RequestHandle::new();\n    write_body_req.set_url(&Url::parse(\"http://writebody.org/\")?);\n    write_body_req.set_cache_override(&pass);\n    write_body_req.set_method(&Method::POST);\n    let mut write_body_initial = BodyHandle::new();\n    let initial_bytes = write_body_initial\n        .write(&vec![0; INITIAL_BYTE_COUNT])\n        .expect(\"failed to write to body handle\");\n    assert_eq!(initial_bytes, INITIAL_BYTE_COUNT);\n    let (mut write_body, _write_body_pending_req) =\n        write_body_req.send_async_streaming(write_body_initial, \"WriteBody\")?;\n    let write_body_handle = unsafe { write_body.as_u32() };\n\n    // Now we attempt to stream chunks into the body until we encounter backpressure. That backpressure\n    // should result from the fixed channel-size, and the fact that the test server waits to read the\n    // body we are streaming to it.\n    let one_chunk = vec![0; 8 * 1024];\n    while is_ready(write_body_handle) {\n        let nwritten = write_body\n            .write(&one_chunk)\n            .expect(\"failed to write to streaming body handle\");\n        assert!(nwritten > 0);\n    }\n\n    // We wait on this request here to give the servers a chance to do their\n    // thing. This is needed to resolve a race between the servers initiating\n    // responses / reading buffers and the guest snapshotting readiness or\n    // performing `select`. This request should return when the other backends\n    // have reached their steady state\n    write_body_sync_req.wait()?;\n\n    append_header(\n        &mut ds_resp,\n        \"Simple-Ready\",\n        is_ready(simple_pending_req_handle),\n    );\n    append_header(&mut ds_resp, \"Read-Ready\", is_ready(read_body_handle));\n    append_header(&mut ds_resp, \"Write-Ready\", is_ready(write_body_handle));\n\n    let handles = vec![\n        simple_pending_req_handle,\n        read_body_handle,\n        write_body_handle,\n    ];\n    let mut ready_idx = 0;\n    unsafe {\n        fastly_sys::fastly_async_io::select(handles.as_ptr(), handles.len(), 20, &mut ready_idx);\n    };\n    if ready_idx == u32::MAX {\n        append_header(&mut ds_resp, \"Ready-Index\", \"timeout\");\n    } else {\n        append_header(&mut ds_resp, \"Ready-Index\", ready_idx);\n    }\n\n    // check that handles are still available after the select, by explicitly closing one of them:\n    assert!(read_body.close().is_ok());\n\n    ds_resp.send_to_client(BodyHandle::new());\n    Ok(())\n}\n\nfn test_empty_select(timeout: u32) {\n    let mut ds_resp = ResponseHandle::new();\n\n    let empty_handles = Vec::new();\n    let mut ready_idx = 0;\n\n    let res = unsafe {\n        fastly_sys::fastly_async_io::select(empty_handles.as_ptr(), 0, timeout, &mut ready_idx)\n    };\n\n    if res == FastlyStatus::OK {\n        if ready_idx == u32::MAX {\n            append_header(&mut ds_resp, \"Ready-Index\", \"timeout\");\n        } else {\n            append_header(&mut ds_resp, \"Ready-Index\", ready_idx);\n        }\n    } else {\n        ds_resp.set_status(StatusCode::INTERNAL_SERVER_ERROR);\n    }\n\n    ds_resp.send_to_client(BodyHandle::new());\n}\n\nfn main() -> Result<(), Error> {\n    let client_req = RequestHandle::from_client();\n    if let Ok(Some(val)) =\n        client_req.get_header_value(&HeaderName::from_str(\"Empty-Select-Timeout\").unwrap(), 1024)\n    {\n        test_empty_select(val.to_str().unwrap().parse().unwrap());\n        Ok(())\n    } else {\n        test_select()\n    }\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/bad-framing-headers.rs",
    "content": "use fastly::http::{self, HeaderValue};\nuse fastly::{Error, Request};\n\nfn main() -> Result<(), Error> {\n    let mut req = Request::from_client();\n\n    // Send a backend request with a synthetic body and some bad headers\n    Request::post(\"http://example.org/TheURL\")\n        // Even though we set a chunked transfer encoding, the backend should see a\n        // content-length. This is hardcoded XQD behavior to strip this header.\n        .with_header(\n            http::header::TRANSFER_ENCODING,\n            HeaderValue::from_static(\"chunked\"),\n        )\n        .with_body(\"synthetic\")\n        .with_pass(true)\n        .send(\"TheOrigin\")?\n        .into_body_bytes();\n\n    // Now test forwarding the non-synthetic request from the client.\n\n    // Set a bogus content-length and forward to the backend.\n    req.set_header(\n        http::header::CONTENT_LENGTH,\n        HeaderValue::from_static(\"99999\"),\n    );\n    let mut resp = req.with_pass(true).send(\"TheOrigin\")?;\n\n    // Now set a bogus transfer encoding even though this should not be a chunked response\n    resp.set_header(\n        http::header::TRANSFER_ENCODING,\n        HeaderValue::from_static(\"chunked\"),\n    );\n    resp.send_to_client();\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/cache.rs",
    "content": "//! A guest program to test the core cache API works properly.\n\nuse bytes::Bytes;\nuse fastly::cache::core::*;\nuse fastly::http::{HeaderName, HeaderValue};\nuse fastly::Body;\nuse std::io::{Read, Write};\nuse std::time::Duration;\nuse uuid::Uuid;\n\n/// Run a test function with wrapped logging.\n/// This makes it easy to tell what test failed when run on Compute Platform.\nmacro_rules! run_test {\n    ($name:ident) => {{\n        eprintln!(\"running test: {}\", stringify!($name));\n        $name();\n        eprintln!(\"completed test: {}\", stringify!($name));\n    }};\n}\n\nfn main() {\n    let service = std::env::var(\"FASTLY_SERVICE_VERSION\").unwrap();\n    eprintln!(\"Running tests; version {service}\");\n\n    run_test!(test_simple_cache_expires);\n    run_test!(test_core_cache_expires);\n\n    run_test!(test_non_concurrent);\n    run_test!(test_concurrent);\n\n    run_test!(test_single_body);\n    run_test!(test_insert_stale);\n    run_test!(test_edge_expired);\n    run_test!(test_edge_expires_after_ttl);\n\n    run_test!(test_vary);\n    run_test!(test_vary_multiple);\n    run_test!(test_novary_ignore_headers);\n    run_test!(test_vary_subtle);\n    run_test!(test_vary_combine);\n\n    run_test!(test_length_from_body);\n    run_test!(test_inconsistent_body_length);\n    run_test!(test_nonconcurrent_range);\n    run_test!(test_concurrent_range);\n    run_test!(test_concurrent_range_fixed);\n    run_test!(test_concurrent_range_known_length_fixed);\n    run_test!(test_transaction_range_fixed);\n\n    run_test!(test_known_length_nonblocking);\n    run_test!(test_known_length_invalid_range);\n    run_test!(test_unknown_length_invalid_range);\n\n    run_test!(test_user_metadata);\n    run_test!(test_service_id);\n\n    run_test!(test_stale_while_revalidate);\n    run_test!(test_keyed_purge);\n    run_test!(test_soft_purge);\n    run_test!(test_purge_variant);\n\n    run_test!(test_racing_transactions);\n    run_test!(test_implicit_cancel_of_fetch);\n    run_test!(test_implicit_cancel_of_pending);\n    run_test!(test_explicit_cancel);\n    run_test!(test_collapse_across_vary);\n\n    run_test!(test_stream_back);\n    run_test!(test_stream_back_fixed);\n\n    eprintln!(\"Completed all tests for version {service}\")\n}\n\nfn new_key() -> CacheKey {\n    Uuid::new_v4().into_bytes().to_vec().into()\n}\n\n/// Among two transactions started in order,\n/// assert that the first is ready and the second is pending,\n/// and convert the former to a Transaction.\nfn ready_and_pending(\n    busy1: PendingTransaction,\n    busy2: PendingTransaction,\n) -> (Transaction, PendingTransaction) {\n    let b1 = busy1.pending().expect(\"error checking status\");\n    let b2 = busy2.pending().expect(\"error checking status\");\n    assert!(!b1);\n    assert!(b2);\n    (busy1.wait().unwrap(), busy2)\n}\n\n/// Wait for the length of a cached object to be known.\n///\n/// Internally in Viceroy, all writes to cached bodies are streaming writes, and are processed\n/// concurrently. That means an `insert..finish()` sequence, followed by a `lookup..to_stream()`,\n/// may still observe the body as \"streaming\" for a period of time.\n///\n/// If we didn't provide a known .length() along with the insert data, we can sleep+poll until the\n/// length is known, implying that the whole body is available.\n///\n/// Note that we have to do the whole lookup in order to get the result; the metadata can't change\n/// from under a Found.\n///\n/// This is an ugly hack. Sorry.\nfn poll_known_length(key: &CacheKey) -> Found {\n    const SLEEP: Duration = Duration::from_millis(101);\n    const TIMEOUT: Duration = Duration::from_secs(10);\n    let iters = TIMEOUT.as_millis() / SLEEP.as_millis();\n    for _ in 0..iters {\n        if let Some(v) = lookup(key.clone())\n            .execute()\n            .expect(\"lookup should not generate error\")\n        {\n            if v.known_length().is_some() {\n                return v;\n            }\n        }\n        std::thread::sleep(SLEEP);\n    }\n    panic!(\n        \"did not arrive at known-length after {} seconds\",\n        TIMEOUT.as_secs()\n    );\n}\n\nfn test_non_concurrent() {\n    let key = new_key();\n\n    {\n        let fetch = lookup(key.clone())\n            .execute()\n            .expect(\"failed initial lookup\");\n        assert!(fetch.is_none());\n    }\n\n    let body = \"hello world\".as_bytes();\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(101))\n            .known_length(body.len() as u64)\n            .execute()\n            .unwrap();\n        writer.write_all(body).unwrap();\n        writer.finish().unwrap();\n    }\n\n    {\n        let fetch = lookup(key.clone()).execute().unwrap();\n        let Some(got) = fetch else {\n            panic!(\"did not fetch from cache\")\n        };\n        let got = got.to_stream().unwrap().into_bytes();\n        assert_eq!(&got, &body);\n    }\n}\n\nfn test_concurrent() {\n    let key = new_key();\n\n    {\n        let fetch = lookup(key.clone())\n            .execute()\n            .expect(\"failed initial lookup\");\n        assert!(fetch.is_none());\n    }\n\n    let mut writer = insert(key.clone(), Duration::from_secs(525600 * 60))\n        .execute()\n        .unwrap();\n\n    let fetch: Found = lookup(key.clone()).execute().unwrap().unwrap();\n    assert!(fetch.is_usable());\n    assert!(!fetch.is_stale());\n    let mut body = fetch.to_stream().unwrap();\n    let mut body = body.read_chunks(6);\n\n    write!(writer, \"hello \").unwrap();\n    writer.flush().unwrap();\n\n    // This appears to be the only read mechanism that won't block for more.\n    let hello = body.next().unwrap().unwrap();\n    assert_eq!(hello, b\"hello \");\n\n    write!(writer, \"world\").unwrap();\n    writer.finish().unwrap();\n\n    let cached = body.next().unwrap().unwrap();\n    assert_eq!(cached, b\"world\");\n\n    assert!(body.next().is_none());\n}\n\nfn test_single_body() {\n    let key = new_key();\n\n    let body = \"hello world\".as_bytes();\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(102))\n            .known_length(body.len() as u64)\n            .execute()\n            .unwrap();\n        writer.write_all(body).unwrap();\n        writer.finish().unwrap();\n    }\n\n    let f1 = lookup(key.clone())\n        .execute()\n        .unwrap()\n        .expect(\"could not perform first fetch\");\n\n    let b1 = f1.to_stream().unwrap();\n\n    // Reading a second body from the same Found results in an error, as documented:\n    assert!(matches!(f1.to_stream(), Err(CacheError::InvalidOperation)));\n\n    // If the existing body is read out and closed...\n    let v1 = b1.into_bytes();\n    // A new body can be read:\n    let b2 = f1.to_stream().unwrap();\n\n    // TODO: This is a difference between compute platform and Viceroy.\n    // In Viceroy, it's sufficient to close the body, then the read can proceed:\n    if std::env::var(\"FASTLY_HOSTNAME\").unwrap() == \"localhost\" {\n        b2.into_handle().close().unwrap();\n        let b3 = f1\n            .to_stream()\n            .expect(\"should be able to re-read body after close of existing body\");\n        let v3 = b3.into_bytes();\n        assert_eq!(&v1, &v3);\n    } else {\n        b2.into_handle().close().unwrap();\n        // In Compute Platform, the body must complete:\n        f1.to_stream()\n            .expect_err(\"should be able to re-read body after close of existing body\");\n    }\n}\n\nfn test_insert_stale() {\n    let key = new_key();\n\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(1))\n            .initial_age(Duration::from_secs(2))\n            .execute()\n            .unwrap();\n        write!(writer, \"hello\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    let Some(found) = lookup(key.clone()).execute().unwrap() else {\n        // Compute platform only returns stale results if the object is in the stale-while-revalidate period;\n        // it would return here.\n        return;\n    };\n    // In Viceroy, you may get a stale result- but will still tell you it's stale.\n    assert!(!found.is_usable());\n    assert!(found.is_stale());\n    assert!(found.age() >= Duration::from_secs(2));\n}\n\nfn test_edge_expired() {\n    let key = new_key();\n\n    // Expired on the edge, but not for users.\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(103))\n            .initial_age(Duration::from_secs(2))\n            .deliver_node_max_age(Duration::from_secs(1))\n            .execute()\n            .unwrap();\n        write!(writer, \"hello\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    // According to current Compute Platform behavior... still fresh!\n    let found = lookup(key.clone())\n        .execute()\n        .unwrap()\n        .expect(\"still considered fresh\");\n    assert!(found.is_usable());\n    assert!(!found.is_stale());\n    assert!(found.age() >= Duration::from_secs(2));\n    assert!(\n        found.max_age() > Duration::from_secs(1),\n        \"got ttl: {}\",\n        found.max_age().as_secs_f32()\n    );\n}\n\nfn test_edge_expires_after_ttl() {\n    let key = new_key();\n\n    // Error for the delivery max age to be greater than the TTL.\n    let result = insert(key.clone(), Duration::from_secs(1))\n        .deliver_node_max_age(Duration::from_secs(104))\n        .execute();\n    if !result.is_err() {\n        panic!(\"error to provide deliver_node_max_age > ttl\");\n    }\n}\n\nfn test_vary() {\n    let key = new_key();\n\n    let header_name = HeaderName::from_static(\"x-viceroy-test\");\n\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(105))\n            .header(&header_name, \"foo\")\n            .vary_by([&header_name])\n            .execute()\n            .unwrap();\n        write!(writer, \"hello\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    // Lookup with just the key should return \"not found\";\n    // the request's headers don't match the rule.\n    let r1 = lookup(key.clone()).execute().unwrap();\n    assert!(r1.is_none());\n\n    // Lookup with the key & matching header value should work:\n    let r2 = lookup(key.clone())\n        .header(&header_name, \"foo\")\n        .execute()\n        .unwrap();\n    assert!(r2.is_some());\n\n    // Lookup with the key & non-matching header value should be \"not found\":\n    let r3 = lookup(key.clone())\n        .header(&header_name, \"bar\")\n        .execute()\n        .unwrap();\n    assert!(r3.is_none());\n}\n\nfn test_vary_multiple() {\n    let key = new_key();\n\n    // Set up three objects with different vary_by headers:\n    // The first varies by h1 and expects \"test\", with body \"hello\"\n    // The second varies by h2 and expects \"assert\", with body \"world\"\n    // The third varies by h3 and expects nothing, with body \"goodbye\"\n    let h1 = HeaderName::from_static(\"x-viceroy-test\");\n    let h2 = HeaderName::from_static(\"x-viceroy-assert\");\n    let h3 = HeaderName::from_static(\"x-viceroy-verify\");\n\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(106))\n            .header(&h1, \"test\")\n            .vary_by([&h1])\n            .execute()\n            .unwrap();\n        write!(writer, \"hello\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(107))\n            .header(&h2, \"assert\")\n            .vary_by([&h2])\n            .execute()\n            .unwrap();\n        write!(writer, \"world\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(108))\n            .vary_by([&h3])\n            .execute()\n            .unwrap();\n        write!(writer, \"goodbye\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    {\n        // Match values for all three.\n        // Should return the most-recently-inserted, i.e. \"goodbye\".\n        // Note: reversed order of headers in the request.\n        let r = lookup(key.clone())\n            .header(&h2, \"assert\")\n            .header(&h1, \"test\")\n            // No h3; note that H3 doesn't have a value when inserted\n            .execute()\n            .unwrap();\n        let body = r.unwrap().to_stream().unwrap().into_string();\n        assert_eq!(&body, \"goodbye\");\n    }\n\n    {\n        // Match values for just the first.\n        let r = lookup(key.clone())\n            .header(&h1, \"test\")\n            .header(&h3, \"verify\")\n            .execute()\n            .unwrap();\n        let body = r.unwrap().to_stream().unwrap().into_string();\n        assert_eq!(&body, \"hello\");\n    }\n\n    {\n        // Match values for the last, by providing no headers.\n        let r = lookup(key.clone()).execute().unwrap();\n        let body = r.unwrap().to_stream().unwrap().into_string();\n        assert_eq!(&body, \"goodbye\");\n    }\n}\n\nfn test_novary_ignore_headers() {\n    let key = new_key();\n\n    // The response and request have headers included, but don't have vary_by.\n    // That means the headers shouldn't matter.\n    let h1 = HeaderName::from_static(\"x-viceroy-test\");\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(109))\n            .header(&h1, \"test\")\n            .execute()\n            .unwrap();\n        write!(writer, \"hello\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    {\n        // Header present: should retrieve the result.\n        let r = lookup(key.clone()).header(&h1, \"test\").execute().unwrap();\n        let body = r.unwrap().to_stream().unwrap().into_string();\n        assert_eq!(&body, \"hello\");\n    }\n\n    {\n        // Header missing: no vary_by, so shouldn't matter.\n        let r = lookup(key.clone()).execute().unwrap();\n        let body = r.unwrap().to_stream().unwrap().into_string();\n        assert_eq!(&body, \"hello\");\n    }\n}\n\nfn test_vary_subtle() {\n    let key = new_key();\n\n    // A very subtle (hah!) case: can one header run into another?\n\n    let h1 = HeaderName::from_static(\"x-viceroy-test\");\n    let h2 = HeaderName::from_static(\"x-viceroy-assert\");\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(110))\n            .vary_by([&h1, &h2])\n            .header(&h1, \"test\")\n            .header(&h2, \"assert\")\n            .execute()\n            .unwrap();\n        write!(writer, \"hello\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    // We want to try to insert a tricky header *value*...\n    let v: Result<HeaderValue, _> = \"test\\r\\nx-viceroy-assert: assert\".try_into();\n    // ...but that won't work:\n    v.unwrap_err();\n    // If it did, we would do this:\n    //let request = lookup(key.clone()).header(&h1, v);\n    //let result = request.execute().unwrap();\n    //assert!(result.is_none())\n}\n\nfn test_vary_combine() {\n    let key = new_key();\n\n    let trust = HeaderValue::from_static(\"trust\");\n    let verify = HeaderValue::from_static(\"verify\");\n\n    let h1 = HeaderName::from_static(\"x-viceroy-test\");\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(111))\n            .vary_by([&h1])\n            .header_values(&h1, [&trust, &verify])\n            .execute()\n            .unwrap();\n        write!(writer, \"hello\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    let r = lookup(key.clone())\n        .header_values(&h1, [&trust, &verify])\n        .execute()\n        .unwrap();\n    assert!(r.is_some());\n\n    // A comma-delimited value is considered distinct from multiple values.\n    // This may lead to suboptimal caching in some cases, but is safe from information leakage.\n    let r = lookup(key.clone())\n        .header(&h1, \"trust, verify\")\n        .execute()\n        .unwrap();\n    assert!(r.is_none());\n\n    // Order matters for HTTP header values:\n    let r = lookup(key.clone())\n        .header_values(&h1, [&verify, &trust])\n        .execute()\n        .unwrap();\n    assert!(r.is_none());\n}\n\nfn test_user_metadata() {\n    let key = new_key();\n\n    let writer = insert(key.clone(), Duration::from_secs(112))\n        .user_metadata(Bytes::copy_from_slice(b\"hi there\"))\n        .execute()\n        .unwrap();\n\n    // Body not yet written, but we should be able to read the metadata right away.\n    {\n        let got = lookup(key.clone())\n            .execute()\n            .unwrap()\n            .expect(\"did not fetch streaming\");\n        let md = got.user_metadata();\n        assert_eq!(&md, b\"hi there\".as_slice());\n    }\n\n    writer.finish().unwrap();\n\n    {\n        let got = lookup(key.clone())\n            .execute()\n            .unwrap()\n            .expect(\"did not fetch streaming\");\n        let md = got.user_metadata();\n        assert_eq!(&md, b\"hi there\".as_slice());\n    }\n}\n\nfn test_service_id() {\n    let key = new_key();\n\n    let Err(e) = insert(key.clone(), Duration::from_secs(10))\n        .on_behalf_of(\"NRF5TZWykaNWzn1WCb7hj2\")\n        .execute()\n    else {\n        panic!(\"unexpected success at using on_behalf_of\");\n    };\n\n    assert!(matches!(e, CacheError::Unsupported), \"{}\", e);\n}\n\nfn test_length_from_body() {\n    // We can get a known length from \"just\" streaming the body.\n    let key = new_key();\n\n    let body = \"hello beautiful world\".as_bytes();\n    let mut writer = insert(key.clone(), Duration::from_secs(115))\n        .execute()\n        .unwrap();\n    {\n        let fetch = lookup(key.clone()).execute().unwrap();\n        // We haven't streamed the body, so the length is unknown.\n        let got = fetch.unwrap();\n        assert!(got.known_length().is_none());\n    }\n    writer.write_all(body).unwrap();\n    writer.finish().unwrap();\n\n    let got = poll_known_length(&key);\n    assert_eq!(got.known_length().unwrap(), body.len() as u64);\n}\n\nfn test_inconsistent_body_length() {\n    // If a body length is provided, writing an inconsistent length should generate an error.\n    let key = new_key();\n\n    {\n        let fetch = lookup(key.clone())\n            .execute()\n            .expect(\"failed initial lookup\");\n        assert!(fetch.is_none());\n    }\n\n    // Short write:\n    {\n        let body = \"hello beautiful world\".as_bytes();\n        let extra_len = body.len() + 10;\n        let mut writer = insert(key.clone(), Duration::from_secs(116))\n            .known_length(extra_len as u64)\n            .execute()\n            .unwrap();\n\n        let found = lookup(key.clone()).execute().unwrap().unwrap();\n        // In metadata, so should be immediately available:\n        assert_eq!(found.known_length().unwrap(), extra_len as u64);\n\n        // Finish writing, but it's a short write:\n        writer.write_all(body).unwrap();\n        writer.finish().unwrap();\n\n        let mut got = found.to_stream().unwrap();\n        let mut data = Vec::new();\n        got.read_to_end(&mut data).unwrap_err();\n    }\n\n    // Long write:\n    {\n        let body = \"hello beautiful world\".as_bytes();\n        let extra_len = body.len() - 3;\n        let mut writer = insert(key.clone(), Duration::from_secs(10))\n            .known_length(extra_len as u64)\n            .execute()\n            .unwrap();\n\n        let found = lookup(key.clone()).execute().unwrap().unwrap();\n        assert_eq!(found.known_length().unwrap(), extra_len as u64);\n        writer.write_all(body).unwrap();\n        writer.finish().unwrap();\n\n        let mut got = found.to_stream().unwrap();\n        let mut data = Vec::new();\n        got.read_to_end(&mut data).unwrap_err();\n    }\n}\n\nfn test_nonconcurrent_range() {\n    // When we do a full body, then a range request, we get only the ranged bits.\n    let key = new_key();\n\n    {\n        let fetch = lookup(key.clone())\n            .execute()\n            .expect(\"failed initial lookup\");\n        assert!(fetch.is_none());\n    }\n\n    let body = \"hello beautiful world\".as_bytes();\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(117))\n            .execute()\n            .unwrap();\n        writer.write_all(body).unwrap();\n        writer.finish().unwrap();\n    }\n    {\n        let got = poll_known_length(&key);\n        let got = got\n            .to_stream_from_range(Some(6), Some(14))\n            .unwrap()\n            .into_bytes();\n        assert_eq!(&got, b\"beautiful\");\n    }\n\n    {\n        let got = lookup(key.clone()).execute().unwrap().unwrap();\n        let got = got\n            .to_stream_from_range(Some(6), None)\n            .unwrap()\n            .into_bytes();\n        assert_eq!(&got, b\"beautiful world\");\n    }\n\n    // to_stream_from_range(None, Some(x)) gets the _last_ (x) bytes\n    {\n        let got = lookup(key.clone()).execute().unwrap().unwrap();\n        let got = got\n            .to_stream_from_range(None, Some(4))\n            .unwrap()\n            .into_bytes();\n        assert_eq!(std::str::from_utf8(&got).unwrap(), \"orld\");\n    }\n}\n\nfn test_concurrent_range() {\n    // When we stream a body, we get different results based on whether we provided a known length.\n    let key_unknown_length = new_key();\n    let key_known_length = new_key();\n\n    let body = \"hello beautiful world\".as_bytes();\n    let mut writer_unknown_length = insert(key_unknown_length.clone(), Duration::from_secs(118))\n        .execute()\n        .unwrap();\n    let mut writer_known_length = insert(key_known_length.clone(), Duration::from_secs(119))\n        .known_length(body.len() as u64)\n        .execute()\n        .unwrap();\n\n    let read_unknown_length = lookup(key_unknown_length).execute().unwrap().unwrap();\n    let read_known_length = lookup(key_known_length).execute().unwrap().unwrap();\n\n    // These capture the state when no body has been streamed at all, so the behavior should be\n    // deterministic.\n    let body_unknown_length = read_unknown_length\n        .to_stream_from_range(Some(6), Some(14))\n        .unwrap();\n    let body_known_length = read_known_length\n        .to_stream_from_range(Some(6), Some(14))\n        .unwrap();\n\n    writer_unknown_length.write(body).unwrap();\n    writer_unknown_length.finish().unwrap();\n    writer_known_length.write(body).unwrap();\n    writer_known_length.finish().unwrap();\n\n    let body_unknown_length = body_unknown_length.into_bytes();\n    let body_known_length = body_known_length.into_bytes();\n\n    assert_eq!(&body_unknown_length, body);\n    assert_eq!(&body_known_length, b\"beautiful\");\n}\n\nfn test_concurrent_range_fixed() {\n    // Test the \"always use requested range\" behavior.\n    let key_unknown_length = new_key();\n\n    let body = \"hello beautiful world\";\n    let mut writer_unknown_length = insert(key_unknown_length.clone(), Duration::from_secs(120))\n        .execute()\n        .unwrap();\n\n    // The to_stream_from_range() call below will block until the first byte of the range is available.\n    // Make it available.\n    writer_unknown_length.write(&body.as_bytes()[0..7]).unwrap();\n    writer_unknown_length.flush().unwrap();\n\n    // Then ask for a streaming body, with just part of the range.\n    let read_unknown_length = lookup(key_unknown_length)\n        .always_use_requested_range()\n        .execute()\n        .unwrap()\n        .unwrap();\n    let body_unknown_length = read_unknown_length\n        .to_stream_from_range(Some(6), Some(14))\n        .unwrap();\n\n    // Finish writing:\n    writer_unknown_length.write(&body.as_bytes()[7..]).unwrap();\n    writer_unknown_length.finish().unwrap();\n\n    let body_unknown_length = body_unknown_length.into_string();\n\n    assert_eq!(&body_unknown_length, &body[6..=14]);\n}\n\nfn test_concurrent_range_known_length_fixed() {\n    // When we stream a body, we get different results based on whether we provided a known length.\n    let key_known_length = new_key();\n\n    let body = \"hello beautiful world\".as_bytes();\n    let mut writer_known_length = insert(key_known_length.clone(), Duration::from_secs(121))\n        .known_length(body.len() as u64)\n        .execute()\n        .unwrap();\n\n    let read_known_length = lookup(key_known_length)\n        .always_use_requested_range()\n        .execute()\n        .unwrap()\n        .unwrap();\n\n    let body_known_length = read_known_length\n        .to_stream_from_range(Some(6), Some(14))\n        .unwrap();\n\n    writer_known_length.write(body).unwrap();\n    writer_known_length.finish().unwrap();\n\n    let body_known_length = body_known_length.into_bytes();\n\n    assert_eq!(&body_known_length, b\"beautiful\");\n}\n\nfn test_transaction_range_fixed() {\n    let key = new_key();\n\n    let body = \"hello beautiful world\";\n    let tx1 = Transaction::lookup(key.clone())\n        .always_use_requested_range()\n        .execute()\n        .unwrap();\n    let mut writer = tx1.insert(Duration::from_secs(122)).execute().unwrap();\n    writer.write(&body.as_bytes()[..7]).unwrap();\n    writer.flush().unwrap();\n\n    // Next transaction should be ready immediately, because the write has started.\n    let tx2 = Transaction::lookup(key.clone())\n        .always_use_requested_range()\n        .execute_async()\n        .unwrap()\n        .wait()\n        .unwrap();\n    let found = tx2.found().expect(\"write has started, should be ready\");\n    let body2 = found.to_stream_from_range(Some(6), Some(14)).unwrap();\n\n    // Finish the write:\n    writer.write(&body.as_bytes()[7..]).unwrap();\n    writer.finish().unwrap();\n\n    let got = body2.into_string();\n    assert_eq!(&got, &body[6..=14]);\n}\n\nfn test_known_length_nonblocking() {\n    let key = new_key();\n\n    let source = \"hello beautiful world\".as_bytes();\n    let mut writer = insert(key.clone(), Duration::from_secs(10))\n        .known_length(source.len() as u64)\n        .execute()\n        .unwrap();\n\n    // Try to read, even though we don't have these bytes yet:\n    let read = lookup(key.clone())\n        .always_use_requested_range()\n        .execute()\n        .unwrap()\n        .unwrap();\n    let body = read.to_stream_from_range(Some(6), Some(14)).unwrap();\n\n    // Finish the write, so we can convert into bytes:\n    writer.write(source).unwrap();\n    writer.finish().unwrap();\n\n    let got = body.into_bytes();\n    assert_eq!(&got, b\"beautiful\");\n}\n\nfn test_known_length_invalid_range() {\n    let key = new_key();\n\n    let source = \"hello beautiful world\".as_bytes();\n    let mut writer = insert(key.clone(), Duration::from_secs(10))\n        .known_length(source.len() as u64)\n        .execute()\n        .unwrap();\n    writer.write(source).unwrap();\n    writer.flush().unwrap();\n\n    // Perform a lookup and get a body for the given range.\n    // We have to do a separate lookup for each because we can only read the body from each Found\n    // once.\n    let get_body = |from: Option<u64>, to: Option<u64>| -> Result<Body, _> {\n        let read = lookup(key.clone())\n            .always_use_requested_range()\n            .execute()\n            .unwrap()\n            .unwrap();\n        read.to_stream_from_range(from, to)\n    };\n\n    let after_end = source.len() as u64;\n\n    // Known-invalid: the indices are backwards.\n    let _got = get_body(Some(6), Some(4)).unwrap_err();\n\n    let mut bodies = Vec::new();\n    // Each of these is invalid in some way; so, they should always return the full body,\n    // because the length of the cached item is known.\n    bodies.push(get_body(Some(after_end), None).unwrap());\n    bodies.push(get_body(Some(4), Some(after_end)).unwrap());\n    bodies.push(get_body(None, Some(after_end)).unwrap());\n    bodies.push(get_body(None, Some(0)).unwrap());\n\n    // Finish the write, so we can convert into bodies:\n    writer.finish().unwrap();\n\n    for body in bodies {\n        let got = body.into_bytes();\n        assert_eq!(&got, source);\n    }\n}\n\nfn test_unknown_length_invalid_range() {\n    let key = new_key();\n\n    let body = \"hello beautiful world\".as_bytes();\n    let mut writer = insert(key.clone(), Duration::from_secs(10))\n        .execute()\n        .unwrap();\n    writer.write(body).unwrap();\n    writer.flush().unwrap();\n\n    {\n        let read = lookup(key.clone())\n            .always_use_requested_range()\n            .execute()\n            .unwrap()\n            .unwrap();\n\n        let after_end = (body.len() + 1) as u64;\n\n        let got = read.to_stream_from_range(Some(6), Some(4)).unwrap_err();\n        assert!(matches!(got, CacheError::Other(_)));\n\n        // TODO: This will block until \"finish\", so we can't do anything with it here.\n        //let got = read\n        //    .to_stream_from_range(Some(after_end), None)\n        //    .unwrap()\n        //    .into_bytes();\n        //assert_eq!(got.len(), body.len());\n\n        // We also only know about this after \"finish\", so the body should be ready immediately:\n        let mut got = read.to_stream_from_range(Some(4), Some(after_end)).unwrap();\n        let mut available = [0u8];\n        // We should be able to read one byte...\n        got.read_exact(&mut available).unwrap();\n        // Finish the write to force an error:\n        writer.finish().unwrap();\n\n        // Can't read past the end:\n        let mut v = Vec::new();\n        got.read_to_end(&mut v).unwrap_err();\n    }\n}\n\nfn test_stale_while_revalidate() {\n    let key = new_key();\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(1))\n            .initial_age(Duration::from_secs(2))\n            .user_metadata(Bytes::from_static(b\"version 1\"))\n            .stale_while_revalidate(Duration::from_secs(100))\n            .execute()\n            .unwrap();\n        write!(writer, \"hello\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    // A normal lookup will get the stale result, but can't pick up an obligation.\n    {\n        let found = lookup(key.clone()).execute().unwrap().unwrap();\n        assert!(found.is_stale());\n        assert!(found.is_usable());\n    }\n\n    // Transactional lookups will all complete, with the stale result.\n    let txn1 = Transaction::lookup(key.clone()).execute().unwrap();\n    let txn2 = Transaction::lookup(key.clone()).execute().unwrap();\n    for txn in [&txn1, &txn2] {\n        assert!(txn.found().unwrap().is_stale());\n        assert!(txn.found().unwrap().is_usable());\n        assert_eq!(\n            txn.found().unwrap().user_metadata().iter().as_slice(),\n            b\"version 1\"\n        );\n    }\n\n    // Update without modifying the body:\n    txn1.update(Duration::from_secs(100))\n        .user_metadata(Bytes::from_static(b\"version 2\"))\n        .execute()\n        .unwrap();\n\n    // A new request should read the new metadata:\n    let found = lookup(key.clone()).execute().unwrap().unwrap();\n    assert_eq!(found.user_metadata().iter().as_slice(), b\"version 2\");\n    // And the original body:\n    let body = found.to_stream().unwrap().into_string();\n    assert_eq!(&body, \"hello\");\n}\n\nfn test_keyed_purge() {\n    fn write_key(surrogate_keys: impl IntoIterator<Item = &'static str>, value: &str) -> Bytes {\n        let key = new_key();\n        let mut writer = insert(key.clone(), Duration::from_secs(100))\n            .surrogate_keys(surrogate_keys)\n            .execute()\n            .unwrap();\n        writer.write_all(value.as_bytes()).unwrap();\n        writer.finish().unwrap();\n        key\n    }\n\n    let key1 = write_key([\"keyA\", \"keyB\"], \"value1\");\n    let key2 = write_key([\"keyA\"], \"value2\");\n    let key3 = write_key([\"keyB\"], \"value3\");\n    assert!(lookup(key1.clone()).execute().unwrap().is_some());\n    assert!(lookup(key2.clone()).execute().unwrap().is_some());\n    assert!(lookup(key3.clone()).execute().unwrap().is_some());\n\n    fastly::http::purge::purge_surrogate_key(\"keyB\").unwrap();\n    assert!(lookup(key1).execute().unwrap().is_none());\n    assert!(lookup(key2).execute().unwrap().is_some());\n    assert!(lookup(key3).execute().unwrap().is_none());\n}\n\nfn test_soft_purge() {\n    fn write_key(surrogate_keys: impl IntoIterator<Item = &'static str>, value: &str) -> Bytes {\n        let key = new_key();\n        let mut writer = insert(key.clone(), Duration::from_secs(100))\n            .surrogate_keys(surrogate_keys)\n            .execute()\n            .unwrap();\n        writer.write_all(value.as_bytes()).unwrap();\n        writer.finish().unwrap();\n        key\n    }\n\n    let key1 = write_key([\"keyA\", \"keyB\"], \"value1\");\n    let key2 = write_key([\"keyA\"], \"value2\");\n    let key3 = write_key([\"keyB\"], \"value3\");\n\n    fastly::http::purge::soft_purge_surrogate_key(\"keyB\").unwrap();\n    // Compute Platform will return stale data that has been soft-purged, if it's still within the\n    // TTL.\n    assert!(lookup(key1)\n        .execute()\n        .unwrap()\n        .expect(\"is found\")\n        .is_stale());\n    assert!(lookup(key3)\n        .execute()\n        .unwrap()\n        .expect(\"is found\")\n        .is_stale());\n    // key2 is untouched:\n    assert!(!lookup(key2)\n        .execute()\n        .unwrap()\n        .expect(\"is found\")\n        .is_stale());\n}\n\nfn test_purge_variant() {\n    let header = HeaderName::from_static(\"test\");\n    let key = new_key();\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(100))\n            .surrogate_keys([\"keyA\"])\n            .header(header.clone(), \"value1\")\n            .vary_by([&header])\n            .execute()\n            .unwrap();\n        writer.write_all(b\"value1\").unwrap();\n        writer.finish().unwrap();\n    }\n    {\n        let mut writer = insert(key.clone(), Duration::from_secs(100))\n            .surrogate_keys([\"keyB\"])\n            .header(header.clone(), \"value2\")\n            .vary_by([&header])\n            .execute()\n            .unwrap();\n        writer.write_all(b\"value2\").unwrap();\n        writer.finish().unwrap();\n    }\n\n    // Self-test: we can read these back before we purge...\n    for variant in [\"value1\", \"value2\"] {\n        assert_eq!(\n            lookup(key.clone())\n                .header(header.clone(), variant)\n                .execute()\n                .unwrap()\n                .unwrap()\n                .to_stream()\n                .unwrap()\n                .into_string(),\n            variant\n        );\n    }\n\n    fastly::http::purge::purge_surrogate_key(\"keyA\").unwrap();\n\n    // keyA was purged:\n    assert!(lookup(key.clone())\n        .header(header.clone(), \"value1\")\n        .execute()\n        .unwrap()\n        .is_none());\n\n    // keyB is fine:\n    assert_eq!(\n        lookup(key.clone())\n            .header(header.clone(), \"value2\")\n            .execute()\n            .unwrap()\n            .unwrap()\n            .to_stream()\n            .unwrap()\n            .into_string(),\n        \"value2\"\n    );\n}\n\nfn test_racing_transactions() {\n    let key = new_key();\n\n    let busy1 = Transaction::lookup(key.clone()).execute_async().unwrap();\n    let busy2 = Transaction::lookup(key.clone()).execute_async().unwrap();\n    let (tx, pending) = ready_and_pending(busy1, busy2);\n    assert!(tx.found().is_none());\n    // The first to resolve should have the obligation to insert:\n    assert!(tx.must_insert());\n    let mut body = tx.insert(Duration::from_secs(125)).execute().unwrap();\n\n    // Once we've started streaming, the other transaction should complete:\n    let tx = pending.wait().unwrap();\n    assert!(!tx.must_insert());\n    let found = tx.found().unwrap();\n    assert_eq!(found.max_age(), Duration::from_secs(125));\n    let mut rd_body = found.to_stream().unwrap();\n\n    // Write the body and read it back:\n    body.write(b\"hello\").unwrap();\n    body.finish().unwrap();\n\n    let mut read = String::new();\n    rd_body.read_to_string(&mut read).unwrap();\n    assert_eq!(&read, \"hello\");\n}\n\nfn test_implicit_cancel_of_fetch() {\n    let key = new_key();\n\n    let busy1 = Transaction::lookup(key.clone()).execute_async().unwrap();\n    let busy2 = Transaction::lookup(key.clone()).execute_async().unwrap();\n    let (t1, pending) = ready_and_pending(busy1, busy2);\n\n    // Cancel via dropping:\n    assert!(t1.must_insert_or_update());\n    std::mem::drop(t1);\n    let t2 = pending.wait().unwrap();\n    assert!(t2.found().is_none());\n    assert!(t2.must_insert_or_update());\n}\n\nfn test_implicit_cancel_of_pending() {\n    let key = new_key();\n\n    let busy1 = Transaction::lookup(key.clone()).execute_async().unwrap();\n    let busy2 = Transaction::lookup(key.clone()).execute_async().unwrap();\n    let (t1, pending) = ready_and_pending(busy1, busy2);\n\n    // Should be safe to drop `pending` while t1 is outstanding.\n    // Note: Compute Platform previously required\n    //  std::mem::drop(t1);\n    // before drop(pending), so this is a regression test for Compute.\n    std::mem::drop(pending);\n    assert!(t1.must_insert_or_update());\n}\n\nfn test_explicit_cancel() {\n    let key = new_key();\n\n    let busy1 = Transaction::lookup(key.clone()).execute_async().unwrap();\n    let busy2 = Transaction::lookup(key.clone()).execute_async().unwrap();\n    let (tx, pending) = ready_and_pending(busy1, busy2);\n\n    // Cancel explicitly:\n    assert!(tx.must_insert_or_update());\n    tx.cancel_insert_or_update().unwrap();\n\n    let t2 = pending.wait().unwrap();\n    assert!(!t2.found().is_some());\n    assert!(t2.must_insert_or_update());\n}\n\nfn test_collapse_across_vary() {\n    let key = new_key();\n\n    let header1 = HeaderName::from_static(\"header1\");\n    let header2 = HeaderName::from_static(\"header2\");\n\n    // Prefill with distinct vary rules, with stale responses:\n    let b = insert(key.clone(), Duration::ZERO)\n        .header(&header1, \"value1\")\n        .vary_by([&header1].into_iter())\n        .execute()\n        .unwrap();\n    b.finish().unwrap();\n\n    let b = insert(key.clone(), Duration::ZERO)\n        .header(&header2, \"value2\")\n        .vary_by([&header2].into_iter())\n        .execute()\n        .unwrap();\n    b.finish().unwrap();\n\n    // vary: header2 is the most recent value.\n    // If we start a transaction that matches both rules:\n    let txn2 = Transaction::lookup(key.clone())\n        .header(&header2, \"value2\")\n        .header(&header1, \"value1\")\n        .execute_async()\n        .unwrap();\n    // And a transaction that matches only the header1 rule:\n    let txn1 = Transaction::lookup(key.clone())\n        .header(&header1, \"value1\")\n        .execute_async()\n        .unwrap();\n\n    // Are these requests collapsed?\n    //\n    // If we are using the most-recent-received Vary rule as a heuristic for \"what do we expect the\n    // next Vary rule to be\", then we should *not* collapse them: the most recent vary rule is\n    // `header2`, and these have distinct `header2` values.\n    //\n    // However, if we're saying \"collapse based on any vary rule received in the past\", they would\n    // not be collapsed.\n    //\n    // We err on the side of \"less latency, more requests\": both of these should be outstanding,\n    // because according to the most recent vary rule they are distinct.\n    assert!(!txn2.pending().unwrap());\n    assert!(!txn1.pending().unwrap());\n}\n\nfn test_stream_back() {\n    let key = new_key();\n\n    let body = \"hello beautiful world\";\n\n    let tx = Transaction::lookup(key.clone()).execute().unwrap();\n    assert!(tx.found().is_none());\n    assert!(tx.must_insert_or_update());\n\n    let (mut writer, found) = tx\n        .insert(Duration::from_secs(126))\n        .execute_and_stream_back()\n        .unwrap();\n\n    writer.write_all(body.as_bytes()).unwrap();\n    writer.finish().unwrap();\n\n    let got = found.to_stream().unwrap().into_string();\n    assert_eq!(&got, &body);\n}\n\nfn test_stream_back_fixed() {\n    // Test the \"always use requested range\" behavior.\n    let key = new_key();\n\n    let body = \"hello beautiful world\";\n\n    let tx = Transaction::lookup(key.clone())\n        .always_use_requested_range()\n        .execute()\n        .unwrap();\n    assert!(tx.found().is_none());\n    assert!(tx.must_insert_or_update());\n\n    let (mut writer, found) = tx\n        .insert(Duration::from_secs(124))\n        .execute_and_stream_back()\n        .unwrap();\n\n    // The to_stream_from_range() call below will block until the first byte of the range is available.\n    // Make it available.\n    writer.write(&body.as_bytes()[0..7]).unwrap();\n    writer.flush().unwrap();\n\n    // Then ask for a streaming body, with just part of the range.\n    let got = found.to_stream_from_range(Some(6), Some(14)).unwrap();\n\n    // Finish writing:\n    writer.write(&body.as_bytes()[7..]).unwrap();\n    writer.finish().unwrap();\n\n    let got = got.into_string();\n\n    assert_eq!(&got, &body[6..=14]);\n}\n\nfn test_simple_cache_expires() {\n    let key = new_key();\n\n    let body = \"hello beautiful world\";\n\n    let _ = fastly::cache::simple::get_or_set(key.clone(), body, Duration::from_secs(1)).expect(\"insert into simple cache\");\n    std::thread::sleep(Duration::from_secs(2));\n    let returned = fastly::cache::simple::get(key).expect(\"retrieve from simple cache\");\n    // Stale, and simple cache doesn't support SWR\n    assert!(returned.is_none());\n}\n\nfn test_core_cache_expires() {\n    let key = new_key();\n\n    let body = \"hello beautiful world\";\n\n    let mut v = fastly::cache::core::insert(key.clone(), Duration::from_secs(1)).execute().expect(\"insert into core cache\");\n    v.write_all(body.as_bytes()).expect(\"write body\");\n    v.finish().expect(\"finish insert\");\n\n    std::thread::sleep(Duration::from_secs(2));\n    let returned = fastly::cache::core::lookup(key).execute().expect(\"retrieve from core cache\");\n    // Stale, no SWR\n    assert!(returned.is_none());\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/config-store-lookup.rs",
    "content": "//! A guest program to test that config-store lookups work properly.\n\nuse fastly_shared::FastlyStatus;\n\nfn main() {\n    let animals = unsafe {\n        let mut dict_handle = fastly_shared::INVALID_CONFIG_STORE_HANDLE;\n        let res = fastly_sys::fastly_config_store::open(\n            \"animals\".as_ptr(),\n            \"animals\".len(),\n            &mut dict_handle as *mut _,\n        );\n        assert_eq!(res, FastlyStatus::OK, \"Failed to open config-store\");\n        dict_handle\n    };\n\n    #[derive(Debug, PartialEq, Eq)]\n    enum LookupResult {\n        Success(String),\n        Missing,\n        BufTooSmall(usize),\n        Err(FastlyStatus),\n    }\n\n    let get = |key: &str, buf_len: usize| unsafe {\n        let mut value = Vec::with_capacity(buf_len);\n        let mut nwritten = 0;\n        let res = fastly_sys::fastly_config_store::get(\n            animals,\n            key.as_ptr(),\n            key.len(),\n            value.as_mut_ptr(),\n            buf_len,\n            &mut nwritten as *mut _,\n        );\n\n        if res != FastlyStatus::OK {\n            if res == FastlyStatus::NONE {\n                return LookupResult::Missing;\n            }\n\n            if res == FastlyStatus::BUFLEN {\n                assert!(nwritten > 0 && buf_len < nwritten);\n                return LookupResult::BufTooSmall(nwritten);\n            }\n\n            return LookupResult::Err(res);\n        }\n\n        value.set_len(nwritten);\n        value.shrink_to(nwritten);\n        LookupResult::Success(String::from_utf8(value).unwrap())\n    };\n\n    assert_eq!(get(\"dog\", 4), LookupResult::Success(String::from(\"woof\")));\n    assert_eq!(get(\"cat\", 4), LookupResult::Success(String::from(\"meow\")));\n    assert_eq!(get(\"cat\", 2), LookupResult::BufTooSmall(4));\n    assert_eq!(get(\"lamp\", 4), LookupResult::Missing);\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/content-length.rs",
    "content": "//! Exercise calculation of Content-Length.\nuse fastly::handle::{BodyHandle, RequestHandle, ResponseHandle};\nuse fastly::http::{header, Url};\nuse fastly::log::set_panic_endpoint;\nuse fastly::Error;\nuse std::io::{Read, Write};\n\nfn main() -> Result<(), Error> {\n    set_panic_endpoint(\"PANIC!\")?;\n    let mut r = RequestHandle::new();\n    r.set_url(&Url::parse(\"http://origin.org/\")?);\n    let b = BodyHandle::new();\n    let (resp, mut body) = r.send(b, \"TheOrigin\")?;\n\n    assert_eq!(resp.get_status(), 200);\n\n    assert_eq!(\n        resp.get_header_value(&header::CONTENT_LENGTH, 10)?\n            .expect(\"response should have Content-Length\"),\n        \"20\"\n    );\n\n    let mut buf = [0; 4];\n\n    body.read_exact(&mut buf).expect(\"read should have succeed\");\n\n    // Copy some bytes from the upstream response\n    let mut newb = BodyHandle::from(&buf[..]); // 4\n\n    // Add some synthetic bytes\n    newb.write_all(b\"12345\")?; // ; 4 + 5\n\n    // Append a synthetic body\n    newb.append(BodyHandle::from(\"xyz\")); // 4 + 5 + 3\n\n    // Append the rest of the upstream body\n    newb.append(body); // 4 + 5 + 3 + (20-4) = 28\n\n    ResponseHandle::new().send_to_client(newb);\n\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/device-detection-lookup.rs",
    "content": "// //! A guest program to test that Device Detection lookups work properly.\n\nuse fastly::device_detection::lookup;\n\nfn main() {\n        let ua = \"Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0 [FBAN/FBIOS;FBAV/8.0.0.28.18;FBBV/1665515;FBDV/iPhone4,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/7.0.4;FBSS/2; FBCR/Telekom.de;FBID/phone;FBLC/de_DE;FBOP/5]\";\n        let device = lookup(&ua).unwrap();\n        assert_eq!(device.device_name(), Some(\"iPhone\"));\n        assert_eq!(device.brand(), Some(\"Apple\"));\n        assert_eq!(device.model(), Some(\"iPhone4,1\"));\n        assert_eq!(device.hwtype(), Some(\"Mobile Phone\"));\n        assert_eq!(device.is_ereader(), Some(false));\n        assert_eq!(device.is_gameconsole(), Some(false));\n        assert_eq!(device.is_mediaplayer(), Some(false));\n        assert_eq!(device.is_mobile(), Some(true));\n        assert_eq!(device.is_smarttv(), Some(false));\n        assert_eq!(device.is_tablet(), Some(false));\n        assert_eq!(device.is_tvplayer(), Some(false));\n        assert_eq!(device.is_desktop(), Some(false));\n        assert_eq!(device.is_touchscreen(), Some(true));\n\n        let ua = \"ghosts-app/1.0.2.1 (ASUSTeK COMPUTER INC.; X550CC; Windows 8 (X86); en)\";\n        let device = lookup(&ua).unwrap();\n        assert_eq!(device.device_name(), Some(\"Asus TeK\"));\n        assert_eq!(device.brand(), Some(\"Asus\"));\n        assert_eq!(device.model(), Some(\"TeK\"));\n        assert_eq!(device.hwtype(), None);\n        assert_eq!(device.is_ereader(), None);\n        assert_eq!(device.is_gameconsole(), None);\n        assert_eq!(device.is_mediaplayer(), None);\n        assert_eq!(device.is_mobile(), None);\n        assert_eq!(device.is_smarttv(), None);\n        assert_eq!(device.is_tablet(), None);\n        assert_eq!(device.is_tvplayer(), None);\n        assert_eq!(device.is_desktop(), Some(false));\n        assert_eq!(device.is_touchscreen(), None);\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/dictionary-lookup.rs",
    "content": "//! A guest program to test that dictionary lookups work properly.\n\nuse fastly_shared::FastlyStatus;\n\nfn main() {\n    let animals = unsafe {\n        let mut dict_handle = fastly_shared::INVALID_DICTIONARY_HANDLE;\n        let res = fastly_sys::fastly_dictionary::open(\n            \"animals\".as_ptr(),\n            \"animals\".len(),\n            &mut dict_handle as *mut _,\n        );\n        assert_eq!(res, FastlyStatus::OK, \"Failed to open dictionary\");\n        dict_handle\n    };\n\n    #[derive(Debug, PartialEq, Eq)]\n    enum LookupResult {\n        Success(String),\n        Missing,\n        BufTooSmall(usize),\n        Err(FastlyStatus),\n    }\n\n    let get = |key: &str, buf_len: usize| unsafe {\n        let mut value = Vec::with_capacity(buf_len);\n        let mut nwritten = 0;\n        let res = fastly_sys::fastly_dictionary::get(\n            animals,\n            key.as_ptr(),\n            key.len(),\n            value.as_mut_ptr(),\n            buf_len,\n            &mut nwritten as *mut _,\n        );\n\n        if res != FastlyStatus::OK {\n            if res == FastlyStatus::NONE {\n                return LookupResult::Missing;\n            }\n\n            if res == FastlyStatus::BUFLEN {\n                assert!(nwritten > 0 && buf_len < nwritten);\n                return LookupResult::BufTooSmall(nwritten);\n            }\n\n            return LookupResult::Err(res);\n        }\n\n        value.set_len(nwritten);\n        value.shrink_to(nwritten);\n        LookupResult::Success(String::from_utf8(value).unwrap())\n    };\n\n    assert_eq!(get(\"dog\", 4), LookupResult::Success(String::from(\"woof\")));\n    assert_eq!(get(\"cat\", 4), LookupResult::Success(String::from(\"meow\")));\n    assert_eq!(get(\"cat\", 2), LookupResult::BufTooSmall(4));\n    assert_eq!(get(\"lamp\", 4), LookupResult::Missing);\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/downstream-req.rs",
    "content": "use fastly::Request;\nuse fastly_shared::FastlyStatus;\nuse std::net::IpAddr;\n\n#[link(wasm_import_module = \"fastly_http_downstream\")]\nunsafe extern \"C\" {\n    #[link_name = \"downstream_compliance_region\"]\n    pub fn downstream_compliance_region(\n        req_handle: fastly_sys::RequestHandle,\n        region_out: *mut u8,\n        region_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus;\n}\n\nfn main() {\n    let mut client_req = Request::from_client();\n\n    // Check that the actual request headers came through\n    assert_eq!(client_req.get_header_str(\"Accept\"), Some(\"text/html\"));\n    assert_eq!(client_req.get_header_str(\"X-Custom-Test\"), Some(\"abcdef\"));\n\n    // Mutate the client request\n    client_req.set_header(\"X-Custom-2\", \"added\");\n\n    // Ensure that the methods for getting the original header info do _not_\n    // include the mutation\n    let names: Vec<String> = client_req.get_original_header_names().unwrap().collect();\n    assert_eq!(\n        names,\n        vec![String::from(\"accept\"), String::from(\"x-custom-test\")]\n    );\n    assert_eq!(client_req.get_original_header_count().unwrap(), 2);\n\n    assert_eq!(client_req.take_body_str(), \"Hello, world!\");\n\n    // Request ID matches sandbox ID for first request:\n    let req_id = client_req.get_client_request_id().unwrap().to_string();\n    let sandbox_id = std::env::var(\"FASTLY_TRACE_ID\").unwrap();\n    assert_eq!(req_id, sandbox_id);\n\n    // Check that we can get addresses used in downstream connection:\n    let localhost: IpAddr = \"127.0.0.1\".parse().unwrap();\n    assert_eq!(client_req.get_client_ip_addr().unwrap(), localhost);\n\n    let localhost: IpAddr = \"127.0.0.1\".parse().unwrap();\n    assert_eq!(client_req.get_server_ip_addr().unwrap(), localhost);\n\n    // Viceroy doesn't consider its requests DDOS attacks:\n    assert_eq!(client_req.get_client_ddos_detected(), Some(false));\n\n    // Viceroy returns false for `fastly_key_is_valid`:\n    assert_eq!(client_req.fastly_key_is_valid(), false);\n\n    // TLS is currently unsupported, so these should work but return `None`:\n    assert_eq!(client_req.get_tls_cipher_openssl_name(), None);\n    assert_eq!(client_req.get_tls_cipher_openssl_name_bytes(), None);\n    assert_eq!(client_req.get_tls_client_hello(), None);\n    assert_eq!(client_req.get_tls_protocol(), None);\n    assert_eq!(client_req.get_tls_protocol_bytes(), None);\n    assert_eq!(client_req.get_tls_client_hello(), None);\n    assert_eq!(client_req.get_tls_ja3_md5(), None);\n    assert_eq!(client_req.get_tls_ja4(), None);\n    assert_eq!(client_req.get_tls_raw_client_certificate(), None);\n    assert_eq!(client_req.get_tls_raw_client_certificate_bytes(), None);\n    assert!(client_req.get_tls_client_cert_verify_result().is_none());\n    // NOTE: This currently fails, waiting on a patch to land in the fastly crate\n    // assert_eq!(client_req.get_tls_raw_client_certificate(), None);\n    assert_eq!(client_req.get_tls_raw_client_certificate_bytes(), None);\n\n    // Other downstream metadata that Viceroy doesn't currently support:\n    assert_eq!(client_req.get_client_h2_fingerprint(), None);\n    assert_eq!(client_req.get_client_oh_fingerprint(), None);\n\n    // Get the actual handle:\n    let (rh, _) = client_req.into_handles();\n\n    // Check that we get a \"none\" region:\n    let mut region = Vec::with_capacity(10);\n    let mut nwritten = 0;\n    let status = unsafe {\n        downstream_compliance_region(rh.as_u32(), region.as_mut_ptr(), region.capacity(), &mut nwritten)\n    };\n    unsafe {\n        region.set_len(nwritten);\n    }\n    assert_eq!(status, FastlyStatus::OK);\n    assert_eq!(nwritten, 4);\n    assert_eq!(region.as_slice(), b\"none\");\n\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/early-hints.rs",
    "content": "//! A guest program to test that config-store lookups work properly.\n\nuse std::{thread, time::Duration};\n\nuse fastly::http::StatusCode;\nuse fastly::{Error, Request, Response};\n\n#[fastly::main]\nfn main(_req: Request) -> Result<Response, Error> {\n    let hint = Response::from_status(103)\n        .with_header(\"Link\", \"</style1>; rel=preload; as=style\")\n        .with_header(\"Link\", \"</script1.js>; rel=preload; as=scrypt\");\n    hint.send_to_client();\n    thread::sleep(Duration::from_secs(1));\n    let hint2 = Response::from_status(103)\n        .with_header(\"Link\", \"</style2>; rel=preload; as=style\")\n        .with_header(\"Link\", \"</script2.js>; rel=preload; as=scrypt\");\n    hint2.send_to_client();\n    thread::sleep(Duration::from_secs(1));\n\n    Ok(Response::from_status(StatusCode::OK).with_body(\"Here's the real HTTP body!\"))\n}"
  },
  {
    "path": "test-fixtures/src/bin/edge-rate-limiting.rs",
    "content": "//! A guest program to test that edge-rate-limiting API is implemented.\n\nuse std::time::Duration;\n\nuse fastly::erl::{CounterDuration, Penaltybox, RateCounter, RateWindow, ERL};\n\nfn main() {\n   let entry = \"entry\";\n\n   let rc = RateCounter::open(\"rc\");\n   let pb = Penaltybox::open(\"pb\");\n   let erl = ERL::open(rc, pb);\n\n   let not_blocked = erl\n       .check_rate(entry, 1, RateWindow::TenSecs, 100, Duration::from_secs(300))\n       .unwrap();\n   assert_eq!(not_blocked, false);\n\n   let rc2 = RateCounter::open(\"rc\");\n   let rate_1 = rc2.lookup_rate(entry, RateWindow::OneSec).unwrap();\n   assert_eq!(rate_1, 0);\n\n   let count10 = rc2.lookup_count(entry, CounterDuration::TenSec).unwrap();\n   assert_eq!(count10, 0);\n\n   assert!(rc2.increment(entry, 600).is_ok());\n\n   let pb2 = Penaltybox::open(\"pb\");\n   let not_in_pb = pb2.has(entry).unwrap();\n   assert_eq!(not_in_pb, false);\n\n   assert!(pb2.add(entry, Duration::from_secs(300)).is_ok());\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/env-vars.rs",
    "content": "//! A guest program to test that Viceroy sets environment variables\n\nuse std::env;\n\nfn main() {\n    let _generation: u64 = env::var(\"FASTLY_CACHE_GENERATION\")\n        .expect(\"cache generation available\")\n        .parse()\n        .expect(\"parses as u64\");\n\n    let cid = env::var(\"FASTLY_CUSTOMER_ID\").expect(\"customer ID available\");\n    assert!(!cid.is_empty());\n\n    let pop = env::var(\"FASTLY_POP\").expect(\"POP available\");\n    assert_eq!(pop.len(), 3);\n\n    let region = env::var(\"FASTLY_REGION\").expect(\"region available\");\n    assert!(!region.is_empty());\n\n    let sid = env::var(\"FASTLY_SERVICE_ID\").expect(\"service ID available\");\n    assert!(!sid.is_empty());\n\n    let _version: u64 = env::var(\"FASTLY_SERVICE_VERSION\")\n        .expect(\"service version available\")\n        .parse()\n        .expect(\"parses as u64\");\n\n    let id = env::var(\"FASTLY_TRACE_ID\").expect(\"trace ID available\");\n    u64::from_str_radix(&id, 16).expect(\"parses as u64\");\n\n    let host_name = env::var(\"FASTLY_HOSTNAME\").expect(\"host name available\");\n    assert_eq!(host_name, \"localhost\");\n\n    let is_staging = env::var(\"FASTLY_IS_STAGING\").expect(\"staging variable set\");\n\n    assert!(is_staging == \"0\" || is_staging == \"1\");\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/expects-hello.rs",
    "content": "use fastly::Request;\nuse http::{HeaderName, StatusCode};\n\nfn main() {\n    let mut resp = Request::get(\"https://fastly.com/\")\n        // .with_version(Version::HTTP_2)\n        .with_header(HeaderName::from_static(\"accept-encoding\"), \"gzip\")\n        .with_auto_decompress_gzip(true)\n        .send(\"ReturnsHello\")\n        .expect(\"can send request\");\n    assert_eq!(resp.get_status(), StatusCode::OK);\n    let got = resp.take_body_str();\n    eprintln!(\"got: {}\", got);\n\n    assert_eq!(&got, \"hello world\", \"got: {}\", got);\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/fastly-key-is-valid.rs",
    "content": "use fastly::Request;\n\nfn main() {\n    let client_req = Request::from_client();\n\n    // Check the result of fastly_key_is_valid based on the test scenario.\n    // The test sends requests with different header values to verify behavior.\n    let is_valid = client_req.fastly_key_is_valid();\n\n    // Return the result as the response body so the integration test can check it.\n    let body = format!(\"is_valid={}\", is_valid);\n\n    fastly::Response::from_body(body)\n        .with_status(200)\n        .send_to_client();\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/geolocation-lookup-default.rs",
    "content": "//! A guest program to test that Geolocation lookups work properly.\n\nuse fastly::geo::{\n    geo_lookup,\n    ConnSpeed,\n    ConnType,\n    Continent,\n    ProxyDescription,\n    ProxyType,\n    // UtcOffset,\n};\nuse std::net::{IpAddr, Ipv4Addr, Ipv6Addr};\n\nfn main() {\n    let client_ip_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));\n    let geo_v4 = geo_lookup(client_ip_v4).unwrap();\n\n    assert_eq!(geo_v4.as_name(), \"Fastly, Inc\");\n    assert_eq!(geo_v4.as_number(), 54113);\n    assert_eq!(geo_v4.area_code(), 415);\n    assert_eq!(geo_v4.city(), \"San Francisco\");\n    assert_eq!(geo_v4.conn_speed(), ConnSpeed::Broadband);\n    assert_eq!(geo_v4.conn_type(), ConnType::Wired);\n    assert_eq!(geo_v4.continent(), Continent::NorthAmerica);\n    assert_eq!(geo_v4.country_code(), \"US\");\n    assert_eq!(geo_v4.country_code3(), \"USA\");\n    assert_eq!(geo_v4.country_name(), \"United States of America\");\n    assert_eq!(geo_v4.latitude(), 37.77869);\n    assert_eq!(geo_v4.longitude(), -122.39557);\n    assert_eq!(geo_v4.metro_code(), 0);\n    assert_eq!(geo_v4.postal_code(), \"94107\");\n    assert_eq!(geo_v4.proxy_description(), ProxyDescription::Unknown);\n    assert_eq!(geo_v4.proxy_type(), ProxyType::Unknown);\n    assert_eq!(geo_v4.region(), Some(\"CA\"));\n    // commented out because the below line fails both in Viceroy and Compute.\n    // assert_eq!(geo_v4.utc_offset(), Some(UtcOffset::from_hms(-7, 0, 0).unwrap());\n\n    let client_ip_v6 = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));\n    let geo_v6 = geo_lookup(client_ip_v6).unwrap();\n\n    assert_eq!(geo_v6.as_name(), \"Fastly, Inc\");\n    assert_eq!(geo_v6.as_number(), 54113);\n    assert_eq!(geo_v6.area_code(), 415);\n    assert_eq!(geo_v6.city(), \"San Francisco\");\n    assert_eq!(geo_v6.conn_speed(), ConnSpeed::Broadband);\n    assert_eq!(geo_v6.conn_type(), ConnType::Wired);\n    assert_eq!(geo_v6.continent(), Continent::NorthAmerica);\n    assert_eq!(geo_v6.country_code(), \"US\");\n    assert_eq!(geo_v6.country_code3(), \"USA\");\n    assert_eq!(geo_v6.country_name(), \"United States of America\");\n    assert_eq!(geo_v6.latitude(), 37.77869);\n    assert_eq!(geo_v6.longitude(), -122.39557);\n    assert_eq!(geo_v6.metro_code(), 0);\n    assert_eq!(geo_v6.postal_code(), \"94107\");\n    assert_eq!(geo_v6.proxy_description(), ProxyDescription::Unknown);\n    assert_eq!(geo_v6.proxy_type(), ProxyType::Unknown);\n    assert_eq!(geo_v6.region(), Some(\"CA\"));\n    // commented out because the below line fails both in Viceroy and Compute.\n    // assert_eq!(geo_v6.utc_offset(), Some(UtcOffset::from_hms(-7, 0, 0).unwrap());\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/geolocation-lookup.rs",
    "content": "//! A guest program to test that Geolocation lookups work properly.\n\nuse fastly::geo::{\n    geo_lookup,\n    ConnSpeed,\n    ConnType,\n    Continent,\n    ProxyDescription,\n    ProxyType,\n    // UtcOffset,\n};\nuse std::net::{IpAddr, Ipv4Addr, Ipv6Addr};\n\nfn main() {\n    let client_ip_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));\n    let geo_v4 = geo_lookup(client_ip_v4).unwrap();\n    assert_eq!(geo_v4.as_name(), \"Fastly Test\");\n    assert_eq!(geo_v4.as_number(), 12345);\n    assert_eq!(geo_v4.area_code(), 123);\n    assert_eq!(geo_v4.city(), \"Test City\");\n    assert_eq!(geo_v4.conn_speed(), ConnSpeed::Broadband);\n    assert_eq!(geo_v4.conn_type(), ConnType::Wired);\n    assert_eq!(geo_v4.continent(), Continent::NorthAmerica);\n    assert_eq!(geo_v4.country_code(), \"CA\");\n    assert_eq!(geo_v4.country_code3(), \"CAN\");\n    assert_eq!(geo_v4.country_name(), \"Canada\");\n    assert_eq!(geo_v4.latitude(), 12.345);\n    assert_eq!(geo_v4.longitude(), 54.321);\n    assert_eq!(geo_v4.metro_code(), 0);\n    assert_eq!(geo_v4.postal_code(), \"12345\");\n    assert_eq!(geo_v4.proxy_description(), ProxyDescription::Unknown);\n    assert_eq!(geo_v4.proxy_type(), ProxyType::Unknown);\n    assert_eq!(geo_v4.region(), Some(\"BC\"));\n    // commented out because the below line fails both in Viceroy and Compute.\n    // assert_eq!(geo_v4.utc_offset(), Some(UtcOffset::from_hms(-7, 0, 0).unwrap()));\n\n    let client_ip_v6 = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));\n    let geo_v6 = geo_lookup(client_ip_v6).unwrap();\n    assert_eq!(geo_v6.as_name(), \"Fastly Test IPv6\");\n    assert_eq!(geo_v6.city(), \"Test City IPv6\");\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/grpc.rs",
    "content": "use fastly::experimental::GrpcBackend;\nuse fastly::{Backend, Error, Request};\nuse std::str::FromStr;\n\n/// Pass everything from the downstream request through to the backend, then pass everything back\n/// from the upstream request to the downstream response.\nfn main() -> Result<(), Error> {\n    let client_req = Request::from_client();\n    let Some(port_str) = client_req.get_header_str(\"Port\") else {\n        panic!(\"Couldn't find out what port to use!\");\n    };\n    let port = u16::from_str(port_str).unwrap();\n\n    let backend = Backend::builder(\"grpc-backend\", format!(\"localhost:{}\", port))\n        .for_grpc(true)\n        .finish()\n        .expect(\"can build backend\");\n\n    Request::get(\"http://localhost/\")\n        .with_header(\"header\", \"is-a-thing\")\n        .with_body(\"hello\")\n        .send(backend)\n        .unwrap()\n        .send_to_client();\n\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/gzipped-response.rs",
    "content": "use fastly::http::request::SendError;\nuse fastly::http::StatusCode;\nuse fastly::{Backend, Request};\nuse std::io::{Read, Write};\n\nstatic HELLO_WORLD: &'static str = \"hello, world!\\n\";\nstatic HELLO_WORLD_GZ: &'static [u8] = include_bytes!(\"../../data/hello_world.gz\");\n\nfn main() -> Result<(), SendError> {\n    let echo_server = Backend::from_name(\"echo\").expect(\"Could not find echo backend?\");\n\n    // Test framework sanity check: a request without the auto_decompress flag\n    // should bounce back to us unchanged.\n    let standard_echo = Request::put(\"http://127.0.0.1\")\n        .with_header(\"Content-Encoding\", \"gzip\")\n        .with_body_octet_stream(HELLO_WORLD_GZ)\n        .send(echo_server.clone())?;\n\n    assert_eq!(\n        standard_echo.get_header_str(\"Content-Encoding\"),\n        Some(\"gzip\")\n    );\n    assert_eq!(\n        standard_echo.get_content_length(),\n        Some(HELLO_WORLD_GZ.len())\n    );\n\n    // Similarly, if we set the auto_decompress flag to false, it should also\n    // bounce back to us unchanged.\n    let explicit_no = Request::put(\"http://127.0.0.1\")\n        .with_header(\"Content-Encoding\", \"gzip\")\n        .with_body_octet_stream(HELLO_WORLD_GZ)\n        .with_auto_decompress_gzip(false)\n        .send(echo_server.clone())?;\n\n    assert_eq!(explicit_no.get_header_str(\"Content-Encoding\"), Some(\"gzip\"));\n    assert_eq!(explicit_no.get_content_length(), Some(HELLO_WORLD_GZ.len()));\n\n    // But if we set the auto_decompress flag to true, and send a compressed\n    // file, we should get the uncompressed version back\n    let mut unpacked_echo = Request::put(\"http://127.0.0.1\")\n        .with_header(\"Content-Encoding\", \"gzip\")\n        .with_body_octet_stream(HELLO_WORLD_GZ)\n        .with_auto_decompress_gzip(true)\n        .send(echo_server.clone())?;\n\n    assert!(unpacked_echo.get_header(\"Content-Encoding\").is_none());\n    assert!(unpacked_echo.get_content_length().is_none());\n    let hopefully_unpacked = unpacked_echo.take_body_str();\n    assert_eq!(HELLO_WORLD, &hopefully_unpacked);\n\n    // This should work when the header is \"x-gzip\", as well; see\n    // https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#gzip.coding\n    let mut xunpacked_echo = Request::put(\"http://127.0.0.1\")\n        .with_header(\"Content-Encoding\", \"x-gzip\")\n        .with_body_octet_stream(HELLO_WORLD_GZ)\n        .with_auto_decompress_gzip(true)\n        .send(echo_server.clone())?;\n\n    assert!(xunpacked_echo.get_header(\"Content-Encoding\").is_none());\n    assert!(xunpacked_echo.get_content_length().is_none());\n    let xhopefully_unpacked = xunpacked_echo.take_body_str();\n    assert_eq!(HELLO_WORLD, &xhopefully_unpacked);\n\n    // The same, but now we're going to use await.\n    let unpacked_echo_pending = Request::put(\"http://127.0.0.1\")\n        .with_header(\"Content-Encoding\", \"gzip\")\n        .with_body_octet_stream(HELLO_WORLD_GZ)\n        .with_auto_decompress_gzip(true)\n        .send_async(echo_server.clone())?;\n\n    let mut unpacked_echo_async = unpacked_echo_pending.wait()?;\n    assert!(unpacked_echo_async.get_header(\"Content-Encoding\").is_none());\n    assert!(unpacked_echo_async.get_content_length().is_none());\n    let hopefully_unpacked = unpacked_echo_async.take_body_str();\n    assert_eq!(HELLO_WORLD, &hopefully_unpacked);\n\n    // The same, but now we're going to stream the data over.\n    let unpacked_stream_pending = {\n        // braces to force the drop on the body\n        let (mut streaming_body, pending_req) = Request::put(\"http://127.0.0.1\")\n            .with_header(\"Content-Encoding\", \"gzip\")\n            .with_auto_decompress_gzip(true)\n            .send_async_streaming(echo_server.clone())?;\n\n        for tiny_bit in HELLO_WORLD_GZ.chunks(8) {\n            streaming_body.write_all(tiny_bit).unwrap();\n        }\n\n        streaming_body.finish().unwrap();\n\n        pending_req\n    };\n    let mut unpacked_stream_async = unpacked_stream_pending.wait()?;\n    assert!(unpacked_stream_async\n        .get_header(\"Content-Encoding\")\n        .is_none());\n    assert!(unpacked_stream_async.get_content_length().is_none());\n    let hopefully_unpacked = unpacked_stream_async.take_body_str();\n    assert_eq!(HELLO_WORLD, &hopefully_unpacked);\n\n    // That being said, if we set the flag to true and send it a text file,\n    // we should just get it back unchanged.\n    let mut yes_but_uncompressed = Request::put(\"http://127.0.0.1\")\n        .with_body_octet_stream(HELLO_WORLD.as_bytes())\n        .with_auto_decompress_gzip(true)\n        .send(echo_server.clone())?;\n\n    let still_unpacked = yes_but_uncompressed.take_body_str();\n    assert_eq!(HELLO_WORLD, &still_unpacked);\n\n    // A slightly odder case: We set everything up for unpacking, but we\n    // don't actually send a gzip'd file. We should get a response, and\n    // it should technically be OK, but we should get an error when we\n    // try to do anything with the body.\n    let mut bad_gzip = Request::put(\"http://127.0.0.1\")\n        .with_header(\"Content-Encoding\", \"gzip\")\n        .with_body_octet_stream(HELLO_WORLD.as_bytes())\n        .with_auto_decompress_gzip(true)\n        .send(echo_server.clone())?;\n\n    assert!(bad_gzip.get_header(\"Content-Encoding\").is_none());\n    assert!(bad_gzip.get_content_length().is_none());\n    assert_eq!(bad_gzip.get_status(), StatusCode::OK);\n    let mut body = vec![];\n    assert!(bad_gzip.get_body_mut().read_to_end(&mut body).is_err());\n\n    // Just for fun, let's return the response to the caller, and make\n    // sure things come out there, as well.\n    Request::put(\"http://127.0.0.1\")\n        .with_header(\"Content-Encoding\", \"gzip\")\n        .with_body_octet_stream(HELLO_WORLD_GZ)\n        .with_auto_decompress_gzip(true)\n        .send(echo_server.clone())?\n        .send_to_client();\n\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/inspect.rs",
    "content": "use fastly::http::StatusCode;\nuse fastly::security::{inspect, InspectConfig};\nuse fastly::{Error, Request, Response};\n\n#[fastly::main]\nfn main(req: Request) -> Result<Response, Error> {\n    let inspectconf = InspectConfig::from_request(&req)\n        .corp(\"junichi-lab\")\n        .workspace(\"lab\");\n\n    let resp = match inspect(inspectconf) {\n        Ok(x) => {\n            let body = format!(\n                \"inspect result: waf_response={}, tags={:?}, decision_ms={}ms, verdict={:?}\",\n                x.status(),\n                x.tags(),\n                x.decision_ms().as_millis(),\n                x.verdict()\n            );\n\n            Response::from_status(StatusCode::OK).with_body_text_plain(&body)\n        }\n        Err(e) => {\n            let body = format!(\"Error: {e:?}\");\n\n            Response::from_status(StatusCode::BAD_REQUEST).with_body_text_plain(&body)\n        }\n    };\n\n    Ok(resp)\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/invalid-status-code.rs",
    "content": "use fastly::{Error, Request, Response};\n\n#[fastly::main]\nfn main(_req: Request) -> Result<Response, Error> {\n    Ok(Response::from_status(101).with_body(\"This should cause an error\"))\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/kv_store.rs",
    "content": "//! A guest program to test that KV store works properly.\n\nuse fastly::kv_store::{\n    KVStore,\n    KVStoreError::{ItemNotFound, StoreNotFound},\n};\n\nfn main() {\n    // Check we can't get a store that does not exist\n    match KVStore::open(\"non_existant\") {\n        Err(StoreNotFound(_)) => {}\n        _ => panic!(),\n    }\n\n    let store_one = KVStore::open(\"store_one\").unwrap().unwrap();\n    // Test that we can get data using the `data` config key\n    assert_eq!(\n        store_one.lookup(\"first\").unwrap().take_body().into_string(),\n        \"This is some data\"\n    );\n    // Test that we can get data from a file using the `path` config key\n    assert_eq!(\n        store_one\n            .lookup(\"second\")\n            .unwrap()\n            .take_body()\n            .into_string(),\n        \"More data\"\n    );\n    // Test that we can get metadata using the `metadata` config key\n    assert_eq!(\n        store_one.lookup(\"third\").unwrap().metadata().unwrap(),\n        \"some metadata\"\n    );\n    // Test that we cannot get metadata if it's not set\n    assert_eq!(\n        store_one.lookup(\"first\").unwrap().metadata(),\n        None\n    );\n\n    let empty_store = KVStore::open(\"empty_store\").unwrap().unwrap();\n    // Check that the value \"bar\" is not in the store\n    match empty_store.lookup(\"bar\") {\n        Err(ItemNotFound) => {}\n        _ => panic!(),\n    }\n    empty_store.insert(\"bar\", \"foo\").unwrap();\n    // Check that the value \"bar\" is now in the store\n    assert_eq!(\n        empty_store.lookup(\"bar\").unwrap().take_body().into_string(),\n        \"foo\"\n    );\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/logging.rs",
    "content": "use fastly::log::Endpoint;\nuse std::io::Write;\n\nfn main() {\n    let mut mib = Endpoint::from_name(\"mib\");\n    let mut inigo = Endpoint::from_name(\"inigo\");\n\n    inigo.write_all(b\"Who are you?\").unwrap();\n    mib.write_all(b\"No one of consequence.\").unwrap();\n    inigo.write_all(b\"I must know.\").unwrap();\n    mib.write_all(b\"Get used to disappointment.\").unwrap();\n\n    mib.write_all(b\"There is something\\nI ought to tell you.\")\n        .unwrap();\n    inigo.write_all(b\"Tell me.\\n\\n\").unwrap();\n    mib.write_all(b\"I'm not left-handed either.\").unwrap();\n    inigo.write_all(b\"\\n\").unwrap(); // this event should be dropped\n    inigo.write_all(b\"O_O\\n\").unwrap();\n\n    println!(\"logging from stdout\");\n    eprintln!(\"logging from stderr\");\n    println!(\"\"); // this should be dropped\n    eprintln!(\"\"); // this should be dropped\n\n    // showcase line buffering on stdout\n    print!(\"log line terminates on flush\");\n    std::io::stdout().flush().unwrap();\n    print!(\"newline completes\");\n    println!(\" a log line\");\n\n    // showcase no buffering on stderr\n    eprint!(\"log line terminates on flush\");\n    std::io::stderr().flush().unwrap();\n    eprint!(\"log line terminates\");\n    eprint!(\"on each write\");\n\n    assert!(Endpoint::try_from_name(\"stdout\").is_err());\n    assert!(Endpoint::try_from_name(\"stderr\").is_err());\n    assert!(Endpoint::try_from_name(\"STDOUT\").is_err());\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/manual-framing-headers.rs",
    "content": "//! Test that ManuallyFromHeaders mode preserves Transfer-Encoding header.\n//!\n//! With manual framing mode, the guest explicitly sets framing headers (Content-Length\n//! or Transfer-Encoding) and they should be preserved rather than stripped.\n//!\n//! We use a streaming body so hyper doesn't know the length upfront.\n\nuse fastly::http::{header, FramingHeadersMode, HeaderValue};\nuse fastly::{Error, Request, Response};\nuse std::io::Write;\n\nfn main() -> Result<(), Error> {\n    let (mut stream, pending) = Request::post(\"http://example.org/\")\n        .with_header(header::TRANSFER_ENCODING, HeaderValue::from_static(\"chunked\"))\n        .with_framing_headers_mode(FramingHeadersMode::ManuallyFromHeaders)\n        .send_async_streaming(\"TheOrigin\")?;\n\n    write!(stream, \"test\")?;\n    stream.finish()?;\n    pending.wait()?;\n\n    Response::new().send_to_client();\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/mutual-tls.rs",
    "content": "use base64::engine::{general_purpose, Engine};\nuse fastly::http::StatusCode;\nuse fastly::secret_store::Secret;\nuse fastly::{Backend, Error, Request, Response};\nuse std::str::FromStr;\n\n/// Pass everything from the downstream request through to the backend, then pass everything back\n/// from the upstream request to the downstream response.\nfn main() -> Result<(), Error> {\n    let client_req = Request::from_client();\n    let certificate = include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/data/client.crt\"));\n    let key_bytes = include_bytes!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/data/client.key\"));\n    let key_secret = Secret::from_bytes(key_bytes.to_vec()).expect(\"can inject key\");\n\n    let Some(port_str) = client_req.get_header_str(\"Port\") else {\n        panic!(\"Couldn't find out what port to use!\");\n    };\n    let port = u16::from_str(port_str).unwrap();\n\n    let backend = Backend::builder(\"mtls-backend\", format!(\"localhost:{}\", port))\n        .enable_ssl()\n        .provide_client_certificate(certificate, key_secret);\n\n    let backend = if client_req.contains_header(\"set-ca\") {\n        backend.ca_certificate(include_str!(concat!(\n            env!(\"CARGO_MANIFEST_DIR\"),\n            \"/data/ca.pem\"\n        )))\n    } else {\n        backend\n    };\n\n    let backend = backend.finish().expect(\"can build backend\");\n\n    let resp = Request::get(\"http://localhost/\")\n        .with_header(\"header\", \"is-a-thing\")\n        .with_body(\"hello\")\n        .send(backend);\n\n    match resp {\n        Ok(resp) => {\n            assert_eq!(resp.get_status(), StatusCode::OK);\n            let body = resp.into_body().into_string();\n            let mut cert_cursor = std::io::Cursor::new(certificate);\n            let mut info = rustls_pemfile::certs(&mut cert_cursor).expect(\"got certs\");\n            assert_eq!(info.len(), 1);\n            let reflected_cert = info.remove(0);\n            let base64_cert = general_purpose::STANDARD.encode(reflected_cert);\n            assert_eq!(body, base64_cert);\n\n            Response::from_status(200)\n                .with_body(\"Hello, Viceroy!\")\n                .send_to_client();\n        }\n        Err(e) => {\n            Response::from_status(StatusCode::SERVICE_UNAVAILABLE)\n                .with_body(format!(\"much sadness: {}\", e))\n                .send_to_client();\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/noop.rs",
    "content": "//! An empty guest program.\n\nfn main() {}\n"
  },
  {
    "path": "test-fixtures/src/bin/panic.rs",
    "content": "// A simple guest that panics.\nfn main() {\n    panic!();\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/request.rs",
    "content": "//! A guest program that tests the hostcalls in the request module.\n\nuse {\n    crate::limits::MAX_HEADER_NAME_LEN,\n    bytes::BytesMut,\n    fastly::{error::BufferSizeError, handle::RequestHandle as FastlyRequestHandle},\n    fastly_shared::{FastlyStatus, HttpVersion},\n    fastly_sys::fastly_http_req::{\n        header_append, header_insert, header_remove, header_value_get, method_get, method_set, new,\n        uri_get, uri_set, version_get, version_set,\n    },\n    fastly_sys::{ContentEncodings, RequestHandle},\n    http::header::{HeaderName, HeaderValue},\n    http::{Method, Uri},\n};\n\n#[path = \"../limits.rs\"]\npub(crate) mod limits;\n\nconst HEADER_LEN_TOO_LONG: usize = MAX_HEADER_NAME_LEN + 1;\n\nfn test_version_set_and_get() {\n    let mut req1: RequestHandle = 0;\n    let mut req2: RequestHandle = 0;\n\n    let mut version1 = 0;\n    let mut version2 = 0;\n\n    unsafe {\n        // Test that one successfully gets the default version.\n        new(&mut req1);\n        let stat = version_get(req1, &mut version1);\n        assert_eq!(stat, FastlyStatus::OK);\n\n        // Test that one successfully gets the previously-set version.\n        new(&mut req2);\n        let stat = version_set(req2, HttpVersion::Http09 as u32);\n        assert_eq!(stat, FastlyStatus::OK);\n        let stat = version_get(req2, &mut version2);\n        assert_eq!(stat, FastlyStatus::OK);\n    }\n\n    assert_eq!(version1, HttpVersion::Http11 as u32);\n    assert_eq!(version2, HttpVersion::Http09 as u32);\n}\n\nfn test_uri_set_and_get() {\n    let mut req: RequestHandle = 0;\n\n    let uri: &[u8] = b\"https://zip.com/foo/bar\";\n\n    let tiny_max = 2;\n    let mut tiny_buffer = BytesMut::with_capacity(tiny_max);\n\n    let good_max = 255;\n    let mut good_buffer = BytesMut::with_capacity(good_max);\n\n    let bad_max = 1;\n    let mut bad_buffer = BytesMut::with_capacity(bad_max);\n\n    let mut nwritten = 0;\n\n    unsafe {\n        // Test that one successfully gets the default uri.\n        new(&mut req);\n        assert_eq!(\n            uri_get(req, tiny_buffer.as_mut_ptr(), tiny_max, &mut nwritten),\n            FastlyStatus::OK\n        );\n        tiny_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 1);\n        assert_eq!(\n            \"/\",\n            Uri::from_maybe_shared(tiny_buffer.freeze()).expect(\"Request uri is valid\"),\n        );\n\n        // Test that one can set and get a uri.\n        nwritten = 0;\n        assert_eq!(uri_set(req, uri.as_ptr(), uri.len()), FastlyStatus::OK);\n\n        uri_get(req, good_buffer.as_mut_ptr(), good_max, &mut nwritten);\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, uri.len());\n        assert_eq!(\n            \"https://zip.com/foo/bar\",\n            Uri::from_maybe_shared(good_buffer.freeze()).expect(\"Request uri is valid\"),\n        );\n\n        // Test that one cannot get a uri when a too-small buffer is supplied\n        assert_eq!(\n            uri_get(req, bad_buffer.as_mut_ptr(), bad_max, &mut nwritten),\n            FastlyStatus::BUFLEN\n        );\n        // Affirm that nwritten indicates the amount of space needed for this call\n        // to have been successful.\n        assert_eq!(nwritten, uri.len());\n\n        // Test that one cannot exaggerate the size of one's uri.\n        assert_eq!(\n            uri_set(req, uri.as_ptr(), uri.len() + 1),\n            FastlyStatus::ERROR\n        );\n    }\n}\n\nfn test_method_set_and_get() {\n    let mut req: RequestHandle = 0;\n\n    let method: &[u8] = b\"POST\";\n\n    let tiny_max = 5;\n    let mut tiny_buffer = BytesMut::with_capacity(tiny_max);\n\n    let good_max = 255;\n    let mut good_buffer = BytesMut::with_capacity(good_max);\n\n    let bad_max = 1;\n    let mut bad_buffer = BytesMut::with_capacity(bad_max);\n\n    let mut nwritten = 0;\n\n    unsafe {\n        // Test that one successfully gets the default method.\n        new(&mut req);\n        assert_eq!(\n            method_get(req, tiny_buffer.as_mut_ptr(), tiny_max, &mut nwritten),\n            FastlyStatus::OK\n        );\n        tiny_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 3);\n        assert_eq!(\n            Method::GET,\n            Method::from_bytes(&tiny_buffer).expect(\"Request method is valid\"),\n        );\n\n        // Test that one can set and get a method.\n        nwritten = 0;\n        assert_eq!(\n            method_set(req, method.as_ptr(), method.len()),\n            FastlyStatus::OK\n        );\n        assert_eq!(\n            method_get(req, good_buffer.as_mut_ptr(), good_max, &mut nwritten),\n            FastlyStatus::OK\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 4);\n        assert_eq!(\n            Method::POST,\n            Method::from_bytes(&good_buffer).expect(\"Request method is valid\"),\n        );\n\n        // Test that one cannot get a method when a too-small buffer is supplied\n        assert_eq!(\n            method_get(req, bad_buffer.as_mut_ptr(), bad_max, &mut nwritten),\n            FastlyStatus::BUFLEN\n        );\n        // Affirm that nwritten indicates the amount of space needed for this call\n        // to have been successful.\n        assert_eq!(nwritten, method.len());\n    }\n}\n\nfn test_header_value_get_and_insert() {\n    let mut req: RequestHandle = 0;\n\n    let hdr_name: &[u8] = b\"header-name\";\n    let hdr_val: &[u8] = b\"foo\";\n\n    let good_max = 255;\n    let mut good_buffer = BytesMut::with_capacity(good_max);\n\n    let bad_max = 1;\n    let mut bad_buffer = BytesMut::with_capacity(bad_max);\n\n    let mut nwritten = 0;\n\n    unsafe {\n        // Test that one successfully gets a header that is not set.\n        new(&mut req);\n        header_value_get(\n            req,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            good_buffer.as_mut_ptr(),\n            good_max,\n            &mut nwritten,\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 0);\n        assert_eq!(\n            \"\",\n            HeaderValue::from_bytes(&good_buffer).expect(\"bytes from host are valid\")\n        );\n\n        // Test that one successfully gets a header that has been inserted.\n        header_insert(\n            req,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            hdr_val.as_ptr(),\n            hdr_val.len(),\n        );\n        header_value_get(\n            req,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            good_buffer.as_mut_ptr(),\n            good_max,\n            &mut nwritten,\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 3);\n        assert_eq!(\n            \"foo\",\n            HeaderValue::from_bytes(&good_buffer).expect(\"bytes from host are valid\")\n        );\n\n        // Test that an attempt to place a header value in a too-short buffer fails.\n        nwritten = 0;\n        assert_eq!(\n            header_value_get(\n                req,\n                hdr_name.as_ptr(),\n                hdr_name.len(),\n                bad_buffer.as_mut_ptr(),\n                bad_max,\n                &mut nwritten\n            ),\n            FastlyStatus::BUFLEN\n        );\n        // Affirm that nwritten indicates the amount of space needed for this call\n        // to have been successful.\n        assert_eq!(nwritten, 3);\n\n        // Test that an attempt to get a too-long header name fails.\n        nwritten = 0;\n        let long_header =\n            Vec::from_iter(hdr_name.iter().cycle().take(HEADER_LEN_TOO_LONG).copied());\n        assert_eq!(\n            header_value_get(\n                req,\n                long_header.as_ptr(),\n                long_header.len(),\n                good_buffer.as_mut_ptr(),\n                good_max,\n                &mut nwritten\n            ),\n            FastlyStatus::INVAL\n        );\n        assert_eq!(nwritten, 0);\n\n        // Test that an attempt to insert a too-long header name fails.\n        assert_eq!(\n            header_insert(\n                req,\n                long_header.as_ptr(),\n                long_header.len(),\n                hdr_val.as_ptr(),\n                hdr_val.len(),\n            ),\n            FastlyStatus::INVAL\n        );\n    }\n}\n\nfn test_header_append_and_remove() {\n    let mut req: RequestHandle = 0;\n\n    let hdr_name: &[u8] = b\"header-name\";\n    let hdr_val: &[u8] = b\"foo\";\n\n    let good_max = 255;\n    let mut good_buffer = BytesMut::with_capacity(good_max);\n\n    let mut nwritten = 0;\n\n    unsafe {\n        // Test that one can append a header that is not already set.\n        new(&mut req);\n        header_append(\n            req,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            hdr_val.as_ptr(),\n            hdr_val.len(),\n        );\n        header_value_get(\n            req,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            good_buffer.as_mut_ptr(),\n            good_max,\n            &mut nwritten,\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 3);\n        assert_eq!(\n            \"foo\",\n            HeaderValue::from_bytes(&good_buffer).expect(\"bytes from host are valid\")\n        );\n\n        // Test that an attempt to append a too-long header name fails.\n        let long_header =\n            Vec::from_iter(hdr_name.iter().cycle().take(HEADER_LEN_TOO_LONG).copied());\n        assert_eq!(\n            header_append(\n                req,\n                long_header.as_ptr(),\n                long_header.len(),\n                hdr_val.as_ptr(),\n                hdr_val.len(),\n            ),\n            FastlyStatus::INVAL\n        );\n\n        // Test that an attempt to remove a too-long header name fails.\n        assert_eq!(\n            header_remove(req, long_header.as_ptr(), long_header.len()),\n            FastlyStatus::INVAL\n        );\n\n        // Test that removing a previously-appended header succeeds.\n        nwritten = 0;\n        assert_eq!(\n            header_remove(req, hdr_name.as_ptr(), hdr_name.len()),\n            FastlyStatus::OK\n        );\n        header_value_get(\n            req,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            good_buffer.as_mut_ptr(),\n            good_max,\n            &mut nwritten,\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 0);\n        assert_eq!(\n            \"\",\n            HeaderValue::from_bytes(&good_buffer).expect(\"bytes from host are valid\")\n        );\n\n        // Test that attempting to remove a header that isn't there fails.\n        assert_eq!(\n            header_remove(req, hdr_name.as_ptr(), hdr_name.len()),\n            FastlyStatus::INVAL\n        );\n    }\n}\n\nfn test_header_multi_value_set_and_get() {\n    let mut request = FastlyRequestHandle::new();\n    let hdr_name = HeaderName::from_static(\"header-name\");\n    let hdr_values = vec![\n        HeaderValue::from_static(\"foo\"),\n        HeaderValue::from_static(\"bar\"),\n    ];\n\n    let othr_hdr_name = HeaderName::from_static(\"other-header-name\");\n    let othr_hdr_values = vec![\n        HeaderValue::from_static(\"zip\"),\n        HeaderValue::from_static(\"zap\"),\n    ];\n\n    let good_max = 255;\n    let bad_max = 1;\n\n    // Test that one can set a header to multiple values and get those same values back.\n    request.set_header_values(&hdr_name, &hdr_values);\n    let header_values: Vec<HeaderValue> = request\n        .get_header_values(&hdr_name, good_max)\n        .collect::<Result<Vec<HeaderValue>, _>>()\n        .unwrap();\n\n    assert!(header_values.iter().any(|i| i == \"foo\"));\n    assert!(header_values.iter().any(|i| i == \"bar\"));\n    assert_eq!(hdr_values.len(), header_values.len());\n\n    // Test that one gets the expected error when the buffer size is too small.\n    match request\n        .get_header_values(&hdr_name, bad_max)\n        .collect::<Result<Vec<HeaderValue>, _>>()\n    {\n        Err(BufferSizeError { .. }) => (),\n        _ => panic!(\"Expected BufferSizeError\"),\n    }\n\n    // Test that an attempt to get values for a not-there header results in a 0-length vector.\n    let not_there_hdr_name = HeaderName::from_static(\"not-there-header\");\n    let result = request\n        .get_header_values(&not_there_hdr_name, good_max)\n        .collect::<Result<Vec<HeaderValue>, _>>()\n        .unwrap();\n    assert_eq!(result.len(), 0);\n\n    // Test that one can set multiple header names on a response and get all those names back\n    request.set_header_values(&othr_hdr_name, &othr_hdr_values);\n    let header_names: Vec<HeaderName> = request\n        .get_header_names(good_max)\n        .collect::<Result<Vec<HeaderName>, _>>()\n        .unwrap();\n\n    assert!(header_names.iter().any(|i| i == \"header-name\"));\n    assert!(header_names.iter().any(|i| i == \"other-header-name\"));\n    assert_eq!(header_names.len(), 2);\n\n    // Test that one gets the expected error when the buffer size is too small.\n    match request\n        .get_header_names(bad_max)\n        .collect::<Result<Vec<HeaderName>, _>>()\n    {\n        Err(BufferSizeError { .. }) => (),\n        _ => panic!(\"Expected BufferSizeError\"),\n    }\n\n    // Test that an attempt to get names for a headerless request results in a 0-length vector.\n    let headerless_request = FastlyRequestHandle::new();\n    let no_header_names: Vec<HeaderName> = headerless_request\n        .get_header_names(good_max)\n        .collect::<Result<Vec<HeaderName>, _>>()\n        .unwrap();\n\n    assert_eq!(0, no_header_names.len());\n}\n\nfn test_default_decompress_response() {\n    let mut request = FastlyRequestHandle::new();\n\n    request.set_auto_decompress_response(ContentEncodings::default());\n}\n\nfn main() {\n    test_version_set_and_get();\n    test_uri_set_and_get();\n    test_method_set_and_get();\n    test_header_value_get_and_insert();\n    test_header_append_and_remove();\n    test_header_multi_value_set_and_get();\n    test_default_decompress_response()\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/response.rs",
    "content": "//! A guest program that tests the hostcalls in the response module.\n\nuse {\n    crate::limits::MAX_HEADER_NAME_LEN,\n    bytes::BytesMut,\n    fastly::{error::BufferSizeError, handle::ResponseHandle as FastlyResponseHandle},\n    fastly_shared::{FastlyStatus, HttpVersion},\n    fastly_sys::{\n        fastly_http_resp::{\n            header_append, header_insert, header_remove, header_value_get, new, status_get,\n            status_set, version_get, version_set,\n        },\n        ResponseHandle,\n    },\n    http::header::{HeaderName, HeaderValue},\n};\n\n#[path = \"../limits.rs\"]\npub(crate) mod limits;\n\nconst HEADER_LEN_TOO_LONG: usize = MAX_HEADER_NAME_LEN + 1;\n\nfn test_status_set_and_get() {\n    let mut resp1: ResponseHandle = 0;\n    let mut resp2: ResponseHandle = 0;\n    let mut resp3: ResponseHandle = 0;\n    let mut resp4: ResponseHandle = 0;\n\n    unsafe {\n        new(&mut resp1);\n        new(&mut resp2);\n        new(&mut resp3);\n        new(&mut resp4);\n\n        status_set(resp1, 200);\n        status_set(resp2, 300);\n        status_set(resp3, 500);\n\n        // Test that an invalid status code will result in an error code.\n        assert_eq!(status_set(resp4, 1), FastlyStatus::INVAL);\n    }\n\n    let mut status1: u16 = 0;\n    let mut status2: u16 = 0;\n    let mut status3: u16 = 0;\n\n    unsafe {\n        status_get(resp1, &mut status1);\n        status_get(resp2, &mut status2);\n        status_get(resp3, &mut status3);\n    }\n\n    assert_eq!(status1, 200);\n    assert_eq!(status2, 300);\n    assert_eq!(status3, 500);\n}\n\nfn test_version_set_and_get() {\n    let mut resp1: ResponseHandle = 0;\n    let mut resp2: ResponseHandle = 0;\n\n    let mut version1 = 0;\n    let mut version2 = 0;\n\n    unsafe {\n        // Test that one successfully gets the default version.\n        new(&mut resp1);\n        version_get(resp1, &mut version1);\n\n        // Test that one successfully gets the previously-set version.\n        new(&mut resp2);\n        version_set(resp2, HttpVersion::Http09 as u32);\n        version_get(resp2, &mut version2);\n    }\n\n    assert_eq!(version1, HttpVersion::Http11 as u32);\n    assert_eq!(version2, HttpVersion::Http09 as u32);\n}\n\nfn test_header_value_get_and_insert() {\n    let mut resp: ResponseHandle = 0;\n\n    let hdr_name: &[u8] = b\"header-name\";\n    let hdr_val: &[u8] = b\"foo\";\n\n    let good_max = 255;\n    let mut good_buffer = BytesMut::with_capacity(good_max);\n\n    let bad_max = 1;\n    let mut bad_buffer = BytesMut::with_capacity(bad_max);\n\n    let mut nwritten = 0;\n\n    unsafe {\n        // Test that one successfully gets a header that is not set.\n        new(&mut resp);\n        header_value_get(\n            resp,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            good_buffer.as_mut_ptr(),\n            good_max,\n            &mut nwritten,\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 0);\n        assert_eq!(\n            \"\",\n            HeaderValue::from_bytes(&good_buffer).expect(\"bytes from host are valid\")\n        );\n\n        // Test that one successfully gets a header that has been inserted.\n        header_insert(\n            resp,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            hdr_val.as_ptr(),\n            hdr_val.len(),\n        );\n        header_value_get(\n            resp,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            good_buffer.as_mut_ptr(),\n            good_max,\n            &mut nwritten,\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 3);\n        assert_eq!(\n            \"foo\",\n            HeaderValue::from_bytes(&good_buffer).expect(\"bytes from host are valid\")\n        );\n\n        // Test that an attempt to place a header value in a too-short buffer fails.\n        nwritten = 0;\n        assert_eq!(\n            header_value_get(\n                resp,\n                hdr_name.as_ptr(),\n                hdr_name.len(),\n                bad_buffer.as_mut_ptr(),\n                bad_max,\n                &mut nwritten\n            ),\n            FastlyStatus::BUFLEN\n        );\n        // Affirm that nwritten indicates the amount of space needed for this call\n        // to have been successful.\n        assert_eq!(nwritten, 3);\n\n        // Test that an attempt to get a too-long header name fails.\n        nwritten = 0;\n        let long_header =\n            Vec::from_iter(hdr_name.iter().cycle().take(HEADER_LEN_TOO_LONG).copied());\n        assert_eq!(\n            header_value_get(\n                resp,\n                long_header.as_ptr(),\n                long_header.len(),\n                good_buffer.as_mut_ptr(),\n                good_max,\n                &mut nwritten\n            ),\n            FastlyStatus::INVAL\n        );\n        assert_eq!(nwritten, 0);\n\n        // Test that an attempt to insert a too-long header name fails.\n        assert_eq!(\n            header_insert(\n                resp,\n                long_header.as_ptr(),\n                long_header.len(),\n                hdr_val.as_ptr(),\n                hdr_val.len(),\n            ),\n            FastlyStatus::INVAL\n        );\n    }\n}\n\nfn test_header_append_and_remove() {\n    let mut resp: ResponseHandle = 0;\n\n    let hdr_name: &[u8] = b\"header-name\";\n    let hdr_val: &[u8] = b\"foo\";\n\n    let good_max = 255;\n    let mut good_buffer = BytesMut::with_capacity(good_max);\n\n    let mut nwritten = 0;\n\n    unsafe {\n        // Test that one can append a header that is not already set.\n        new(&mut resp);\n        header_append(\n            resp,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            hdr_val.as_ptr(),\n            hdr_val.len(),\n        );\n        header_value_get(\n            resp,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            good_buffer.as_mut_ptr(),\n            good_max,\n            &mut nwritten,\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 3);\n        assert_eq!(\n            \"foo\",\n            HeaderValue::from_bytes(&good_buffer).expect(\"bytes from host are valid\")\n        );\n\n        // Test that an attempt to append a too-long header name fails.\n        let long_header =\n            Vec::from_iter(hdr_name.iter().cycle().take(HEADER_LEN_TOO_LONG).copied());\n        assert_eq!(\n            header_append(\n                resp,\n                long_header.as_ptr(),\n                long_header.len(),\n                hdr_val.as_ptr(),\n                hdr_val.len(),\n            ),\n            FastlyStatus::INVAL\n        );\n\n        // Test that an attempt to remove a too-long header name fails.\n        assert_eq!(\n            header_remove(resp, long_header.as_ptr(), long_header.len()),\n            FastlyStatus::INVAL\n        );\n\n        // Test that removing a previously-appended header succeeds.\n        nwritten = 0;\n        assert_eq!(\n            header_remove(resp, hdr_name.as_ptr(), hdr_name.len()),\n            FastlyStatus::OK\n        );\n        header_value_get(\n            resp,\n            hdr_name.as_ptr(),\n            hdr_name.len(),\n            good_buffer.as_mut_ptr(),\n            good_max,\n            &mut nwritten,\n        );\n        good_buffer.set_len(nwritten);\n        assert_eq!(nwritten, 0);\n        assert_eq!(\n            \"\",\n            HeaderValue::from_bytes(&good_buffer).expect(\"bytes from host are valid\")\n        );\n\n        // Test that attempting to remove a header that isn't there fails.\n        assert_eq!(\n            header_remove(resp, hdr_name.as_ptr(), hdr_name.len()),\n            FastlyStatus::INVAL\n        );\n    }\n}\n\nfn test_header_multi_value_set_and_get() {\n    let mut response = FastlyResponseHandle::new();\n    let hdr_name = HeaderName::from_static(\"header-name\");\n    let hdr_values = vec![\n        HeaderValue::from_static(\"foo\"),\n        HeaderValue::from_static(\"bar\"),\n    ];\n\n    let othr_hdr_name = HeaderName::from_static(\"other-header-name\");\n    let othr_hdr_values = vec![\n        HeaderValue::from_static(\"zip\"),\n        HeaderValue::from_static(\"zap\"),\n    ];\n\n    let good_max = 255;\n    let bad_max = 1;\n\n    // Test that one can set a header to multiple values and get those same values back.\n    response.set_header_values(&hdr_name, &hdr_values);\n    let header_values: Vec<HeaderValue> = response\n        .get_header_values(&hdr_name, good_max)\n        .collect::<Result<Vec<HeaderValue>, _>>()\n        .unwrap();\n\n    assert!(header_values.iter().any(|i| i == \"foo\"));\n    assert!(header_values.iter().any(|i| i == \"bar\"));\n    assert_eq!(hdr_values.len(), header_values.len());\n\n    // Test that one gets the expected error when the buffer size is too small.\n    match response\n        .get_header_values(&hdr_name, bad_max)\n        .collect::<Result<Vec<HeaderValue>, _>>()\n    {\n        Err(BufferSizeError { .. }) => (),\n        _ => panic!(\"Expected BufferSizeError\"),\n    }\n\n    // Test that an attempt to get values for a not-there header results in a 0-length vector.\n    let not_there_hdr_name = HeaderName::from_static(\"not-there-header\");\n    let result = response\n        .get_header_values(&not_there_hdr_name, good_max)\n        .collect::<Result<Vec<HeaderValue>, _>>()\n        .unwrap();\n    assert_eq!(result.len(), 0);\n\n    // Test that one can set multiple header names on a response and get all those names back\n    response.set_header_values(&othr_hdr_name, &othr_hdr_values);\n    let header_names: Vec<HeaderName> = response\n        .get_header_names(good_max)\n        .collect::<Result<Vec<HeaderName>, _>>()\n        .unwrap();\n\n    assert!(header_names.iter().any(|i| i == \"header-name\"));\n    assert!(header_names.iter().any(|i| i == \"other-header-name\"));\n    assert_eq!(header_names.len(), 2);\n\n    // Test that one gets the expected error when the buffer size is too small.\n    match response\n        .get_header_names(bad_max)\n        .collect::<Result<Vec<HeaderName>, _>>()\n    {\n        Err(BufferSizeError { .. }) => (),\n        _ => panic!(\"Expected BufferSizeError\"),\n    }\n\n    // Test that an attempt to get names for a headerless response results in a 0-length vector.\n    let headerless_response = FastlyResponseHandle::new();\n    let no_header_names: Vec<HeaderName> = headerless_response\n        .get_header_names(good_max)\n        .collect::<Result<Vec<HeaderName>, _>>()\n        .unwrap();\n\n    assert_eq!(0, no_header_names.len());\n}\n\nfn main() {\n    test_status_set_and_get();\n    test_version_set_and_get();\n    test_header_value_get_and_insert();\n    test_header_append_and_remove();\n    test_header_multi_value_set_and_get();\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/reusable-sandboxes.rs",
    "content": "//! A guest program that tests the hostcalls for fetching multiple requests per sandbox.\n\nuse fastly::{Request, Response};\nuse fastly::handle::{BodyHandle, RequestHandle};\nuse fastly_shared::{FastlyStatus, INVALID_REQUEST_HANDLE};\nuse fastly_sys::fastly_http_downstream::*;\n\nfn is_ready(handle: u32) -> bool {\n    let mut ready_out: u32 = 0;\n    unsafe {\n        let status = fastly_sys::fastly_async_io::is_ready(handle, &mut ready_out);\n        assert_eq!(status, FastlyStatus::OK);\n    }\n    ready_out == 1\n}\n\nfn main() {\n    let mut counter = 0;\n    let mut req = Request::from_client();\n\n    'outer: loop {\n        assert_eq!(req.take_body().into_string(), counter.to_string());\n\n        // Make sure we're registered to receive the next request before\n        // responding to avoid flakes.\n        let mask = NextRequestOptionsMask::empty();\n        let opts = NextRequestOptions::default();\n\n        let mut pending = INVALID_REQUEST_HANDLE;\n        let status = unsafe {\n            next_request(mask, &opts, &mut pending)\n        };\n\n        if status != FastlyStatus::OK {\n            return;\n        }\n\n        // Respond to downstream.\n        counter += 1;\n        let resp = Response::from_body(format!(\"Response #{counter}\"));\n        resp.send_to_client_impl(false, false);\n\n        // And fetch our next incoming request.\n        let mut rh = INVALID_REQUEST_HANDLE;\n        let mut bh = INVALID_REQUEST_HANDLE;\n\n        while !is_ready(pending) {\n            std::thread::sleep(std::time::Duration::from_millis(100));\n        }\n\n        let status = unsafe { next_request_wait(pending, &mut rh, &mut bh) };\n\n        match status {\n            FastlyStatus::OK => {},\n            FastlyStatus::NONE => break 'outer,\n            _ => panic!(\"unexpected result: {status:?}\"),\n        }\n\n        let bh = unsafe { BodyHandle::from_u32(bh) };\n        let rh = {\n            let mut new = RequestHandle::new();\n            *new.as_u32_mut() = rh;\n            new\n        };\n\n        req = Request::from_handles(rh, Some(bh));\n    }\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/secret-store.rs",
    "content": "//! A guest program to test that secret store works properly.\n\nuse fastly::secret_store::Secret;\nuse fastly::SecretStore;\n\nfn main() {\n    // Check we can't get a store that does not exist\n    match SecretStore::open(\"nonexistent\") {\n        Err(_) => {}\n        _ => panic!(),\n    }\n\n    let store_one = SecretStore::open(\"store_one\").unwrap();\n    assert_eq!(\n        store_one.get(\"first\").unwrap().plaintext(),\n        \"This is some data\"\n    );\n    assert_eq!(store_one.get(\"second\").unwrap().plaintext(), \"More data\");\n\n    assert_eq!(store_one.get(\"fourth\").unwrap().plaintext(), \"my-env-value\");\n\n    match store_one.try_get(\"third\").unwrap() {\n        None => {}\n        _ => panic!(),\n    }\n\n    let store_two = SecretStore::open(\"store_two\").unwrap();\n    assert_eq!(store_two.get(\"first\").unwrap().plaintext(), \"first secret\");\n    assert_eq!(\n        store_two.get(\"second\").unwrap().plaintext(),\n        \"another secret\",\n    );\n\n    let hello_bytes = \"hello, wasm_world!\".as_bytes().to_vec();\n    let secret = Secret::from_bytes(hello_bytes).unwrap();\n    assert_eq!(\"hello, wasm_world!\", secret.plaintext());\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/shielding.rs",
    "content": "use fastly::{shielding::Shield, Error, Request, Response};\nuse http::StatusCode;\n\n#[fastly::main]\nfn main(request: Request) -> Result<Response, Error> {\n    let Some(shield_name) = request.get_header_str(\"shield\") else {\n        return Ok(\n            Response::from_status(StatusCode::BAD_REQUEST).with_body(\"No 'shield' header found\")\n        );\n    };\n\n    let Ok(shield) = Shield::new(shield_name) else {\n        return Ok(Response::from_status(StatusCode::INTERNAL_SERVER_ERROR)\n            .with_body(format!(\"Invalid shield name '{shield_name}'\")));\n    };\n\n    match request.get_path() {\n        \"/is-shield\" => {\n            Ok(Response::from_status(StatusCode::OK).with_body(shield.running_on().to_string()))\n        }\n\n        \"/shield-to\" => {\n            let Some(shield_type) = request.get_header_str(\"shield-type\") else {\n                return Ok(Response::from_status(StatusCode::CONFLICT)\n                    .with_body(\"No 'shield-type' header found\"));\n            };\n\n            if shield_type != \"unencrypted\" && shield_type != \"encrypted\" {\n                return Ok(Response::from_status(StatusCode::NOT_ACCEPTABLE)\n                    .with_body(\"Invalid 'shield-type' header found\"));\n            }\n\n            let backend = if shield_type == \"unencrypted\" {\n                shield.unencrypted_backend().unwrap()\n            } else {\n                shield.encrypted_backend().unwrap()\n            };\n\n            request.send(backend).map_err(Into::into)\n        }\n\n        _ => Ok(Response::from_status(StatusCode::NOT_FOUND)),\n    }\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/sleep.rs",
    "content": "fn main() {\n    std::thread::sleep(std::time::Duration::from_millis(100))\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/streaming-response.rs",
    "content": "use fastly::Response;\nuse std::io::Write;\n\nfn main() {\n    let mut stream = Response::new().stream_to_client();\n\n    for i in 0..1000 {\n        writeln!(stream, \"{}\", i).unwrap();\n    }\n\n    stream.finish().unwrap();\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/teapot-status.rs",
    "content": "//! Send a [`418 I'm a teapot`][tea] response downstream.\n//!\n//! `teapot-status.wasm` will create a [`418 I'm a teapot`][tea] response, per [RFC2324][rfc]. This\n//! status code is used to clearly indicate that a response came from the guest program.\n//!\n//! [rfc]: https://tools.ietf.org/html/rfc2324\n//! [tea]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418\n\nuse fastly_sys::{\n    fastly_http_body as http_body, fastly_http_resp as http_resp, BodyHandle, ResponseHandle,\n};\n\nfn main() {\n    let mut body: BodyHandle = 0;\n    let mut resp: ResponseHandle = 0;\n    unsafe {\n        // Create a new response, set its status code, and send it downstream.\n        http_resp::new(&mut resp)\n            .result()\n            .expect(\"can create a new response\");\n        http_resp::status_set(resp, 418)\n            .result()\n            .expect(\"can set the status code\");\n        http_body::new(&mut body)\n            .result()\n            .expect(\"can create a new body\");\n        http_resp::send_downstream(resp, body, 0)\n            .result()\n            .expect(\"can send the response downstream\");\n    }\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/unknown-import.rs",
    "content": "use fastly::{Request, Response};\nuse std::os::raw::{c_float, c_int};\n\n/// This test fixture forwards the client request to a backend after calling a custom imported\n/// function that is not defined by Viceroy.\nfn main() {\n    let client_req = Request::from_client();\n\n    if client_req.contains_header(\"call-it\") {\n        let unknown_result = unsafe { unknown_function(42, 120.0) };\n        // With the default mode, we don't even end up running this program. In trapping mode, we don't\n        // make it past the function call above. It's only in \"default value\" mode that we make it here,\n        // where the answer should be zero.\n        assert_eq!(unknown_result, 0);\n    }\n\n    // Forward the request to the given backend\n    client_req\n        .send(\"TheOrigin\")\n        .unwrap_or_else(|_| Response::from_status(500))\n        .send_to_client();\n}\n\n#[link(wasm_import_module = \"unknown_module\")]\nunsafe extern \"C\" {\n    #[link_name = \"unknown_function\"]\n    pub fn unknown_function(arg1: c_int, arg2: c_float) -> c_int;\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/upstream-async.rs",
    "content": "//! This test fixture runs essentially the same test three different ways: using `wait`, `poll`, and `select`\n//! on `PendingRequest`s.\n//!\n//! The test makes async requests to two different backends, each of which returns an identifying header,\n//! and then checks that both responses are eventually returned and processed.\n\nuse fastly::{\n    http::request::{select, PendingRequest, PollResult},\n    Request, Response,\n};\n\n/// Set up async requests to two distinct backends.\nfn send_async_reqs() -> (PendingRequest, PendingRequest) {\n    let req1 = Request::get(\"http://www.example1.com/\")\n        .send_async(\"backend1\")\n        .unwrap();\n    let req2 = Request::get(\"http://www.example2.com/\")\n        .send_async(\"backend2\")\n        .unwrap();\n    (req1, req2)\n}\n\n/// A structure to process responses as they come in, and ensure that they are as expected.\nstruct ResponseTracker {\n    /// Have we processed the response from `backend1` yet?\n    response1: bool,\n    /// Have we processed the response from `backend2` yet?\n    response2: bool,\n}\n\nimpl ResponseTracker {\n    fn new() -> Self {\n        Self {\n            response1: false,\n            response2: false,\n        }\n    }\n\n    /// Digest a response, updating tracker state accordingly.\n    ///\n    /// Panics if a response from the given backend has already been seen, or if the response doesn't\n    /// contain the expected identifying header.\n    fn process(&mut self, resp: Response) {\n        if resp.get_header(\"Backend-1-Response\").is_some() {\n            assert!(!self.response1);\n            self.response1 = true;\n        } else if resp.get_header(\"Backend-2-Response\").is_some() {\n            assert!(!self.response2);\n            self.response2 = true;\n        } else {\n            panic!(\"Response did not include backend header\");\n        }\n    }\n\n    /// After both responses have been processed, assert that they have updated the tracker state as expected.\n    fn assert_complete(self) {\n        assert!(self.response1);\n        assert!(self.response2);\n    }\n}\n\n/// Run the test using the `wait` API, just waiting on and processing each response in sequence\nfn test_wait() {\n    let mut tracker = ResponseTracker::new();\n    let (req1, req2) = send_async_reqs();\n    tracker.process(req1.wait().unwrap());\n    tracker.process(req2.wait().unwrap());\n    tracker.assert_complete();\n}\n\nfn test_poll() {\n    let req1 = Request::get(\"http://www.example1.com/\")\n        .send_async(\"backend1\")\n        .unwrap();\n\n    // req1 should not be ready until a request is sent to backend2\n    let PollResult::Pending(req1) = req1.poll() else {\n        panic!(\"req1 finished too soon\")\n    };\n\n    // sending req2 should unblock req1, and req2 itself should return immediately.\n    let req2 = Request::get(\"http://www.example2.com/\")\n        .send_async(\"backend2\")\n        .unwrap();\n\n    // avoid races by resolving the responses to both requests in a loop\n    let mut tracker = ResponseTracker::new();\n    let mut reqs = vec![req1, req2];\n\n    while !reqs.is_empty() {\n        for req in std::mem::replace(&mut reqs, Vec::new()) {\n            match req.poll() {\n                PollResult::Pending(req) => reqs.push(req),\n                PollResult::Done(resp_result) => tracker.process(resp_result.unwrap()),\n            }\n        }\n    }\n\n    tracker.assert_complete();\n}\n\n/// Run the test using the `select` API, processing each response as it is returned.\nfn test_select() {\n    let mut tracker = ResponseTracker::new();\n    let (req1, req2) = send_async_reqs();\n    let mut reqs = vec![req1, req2];\n\n    while !reqs.is_empty() {\n        let (resp_result, rest) = select(reqs);\n        tracker.process(resp_result.unwrap());\n        reqs = rest;\n    }\n\n    tracker.assert_complete();\n}\n\nfn main() {\n    test_wait();\n    test_poll();\n    test_select();\n\n    // If we made it through the gauntlet above without panicking, we're good: return 200 OK\n    Response::from_status(200).send_to_client();\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/upstream-dynamic.rs",
    "content": "use fastly::experimental::BackendCreationError;\nuse fastly::{Backend, Error, Request, Response};\n\n/// Pass everything from the downstream request through to the backend, then pass everything back\n/// from the upstream request to the downstream response.\nfn main() -> Result<(), Error> {\n    let client_req = Request::from_client();\n\n    let backend = if let Some(dynamic_string) = client_req.get_header_str(\"Dynamic-Backend\") {\n        let mut basic_builder = Backend::builder(\"dynamic-backend\", dynamic_string);\n\n        if let Some(override_host) = client_req.get_header_str(\"With-Override\") {\n            basic_builder = basic_builder.override_host(override_host);\n        }\n\n        match basic_builder.finish() {\n            Ok(x) => x,\n            Err(err) => {\n                match err {\n                    BackendCreationError::Disallowed => Response::from_status(403).send_to_client(),\n                    BackendCreationError::NameInUse => Response::from_status(409).send_to_client(),\n                    _ => Response::from_status(500).send_to_client(),\n                }\n\n                return Ok(());\n            }\n        }\n    } else if let Some(static_string) = client_req.get_header_str(\"Static-Backend\") {\n        Backend::from_name(static_string)?\n    } else {\n        panic!(\"Couldn't find a backend to use!\");\n    };\n\n    if let Some(supplementary_backend) = client_req.get_header_str(\"Supplementary-Backend\") {\n        match Backend::builder(supplementary_backend, \"fastly.com\").finish() {\n            Ok(_) => {}\n            Err(err) => {\n                match err {\n                    BackendCreationError::Disallowed => Response::from_status(403).send_to_client(),\n                    BackendCreationError::NameInUse => Response::from_status(409).send_to_client(),\n                    _ => Response::from_status(500).send_to_client(),\n                }\n\n                return Ok(());\n            }\n        }\n    }\n\n    client_req\n        .send(backend)\n        .unwrap_or_else(|_| Response::from_status(503))\n        .send_to_client();\n\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/upstream-streaming.rs",
    "content": "use fastly::Request;\nuse std::io::Write;\n\nfn main() {\n    let (mut stream, req) = Request::post(\"http://www.example.com/\")\n        .send_async_streaming(\"origin\")\n        .unwrap();\n\n    for i in 0..1000 {\n        writeln!(stream, \"{}\", i).unwrap();\n    }\n\n    stream.finish().unwrap();\n    req.wait().unwrap().send_to_client();\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/upstream.rs",
    "content": "use fastly::{Backend, Request, Response};\n\n/// This test fixture simply forwards the client request to a desired backend, specified by\n/// a special `Viceroy-Backend` header.\nfn main() {\n    let client_req = Request::from_client();\n\n    // Extract the desired backend from a the `Viceroy-Backend` header\n    let backend_name = client_req\n        .get_header_str(\"Viceroy-Backend\")\n        .expect(\"No backend header\");\n    let backend = Backend::from_name(backend_name).expect(\"Could not parse backend name\");\n\n    // Forward the request to the given backend\n    client_req\n        .send(backend)\n        .unwrap_or_else(|_| Response::from_status(500))\n        .send_to_client();\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/vcpu_time_test.rs",
    "content": "use anyhow::anyhow;\nuse fastly::{Error, Request, Response};\nuse fastly_shared::FastlyStatus;\nuse hex_literal::hex;\nuse sha2::{Sha512, Digest};\nuse std::time::Instant;\n\n#[link(wasm_import_module = \"fastly_compute_runtime\")]\nunsafe extern \"C\" {\n    #[link_name = \"get_vcpu_ms\"]\n    pub fn get_vcpu_ms(ms_out: *mut u64) -> FastlyStatus;\n}\n\nfn current_vcpu_ms() -> Result<u64, anyhow::Error> {\n    let mut vcpu_time = 0u64;\n    let vcpu_time_result = unsafe { get_vcpu_ms(&mut vcpu_time) };\n    if vcpu_time_result != FastlyStatus::OK {\n        return Err(anyhow!(\"Got bad response from get_vcpu_ms: {:?}\", vcpu_time_result));\n    }\n    Ok(vcpu_time)\n}\n\nfn test_that_waiting_for_servers_increases_only_wall_time(client_req: Request) -> Result<(), Error> {\n    let wall_initial_time = Instant::now();\n    let vcpu_initial_time = current_vcpu_ms()?;\n    let Ok(_) = client_req.send(\"slow-server\") else {\n        Response::from_status(500).send_to_client();\n        return Ok(());\n    };\n    let wall_elapsed_time = wall_initial_time.elapsed().as_millis();\n    let vcpu_final_time = current_vcpu_ms()?;\n\n    assert!( (vcpu_final_time - vcpu_initial_time) < 1000 );\n    assert!(wall_elapsed_time > 3000 );\n\n    Ok(())\n}\n\nfn test_that_computing_factorial_increases_vcpu_time() -> Result<(), Error> {\n    let vcpu_initial_time = current_vcpu_ms()?;\n\n    let block = vec![0; 4096];\n    let mut written = 0;\n    let mut hasher = Sha512::new();\n    while written < (1024 * 1024 * 1024) {\n        hasher.update(&block);\n        written += block.len();\n    }\n    let result = hasher.finalize();\n    assert_eq!(result[..], hex!(\"\nc5041ae163cf0f65600acfe7f6a63f212101687\nd41a57a4e18ffd2a07a452cd8175b8f5a4868dd\n2330bfe5ae123f18216bdbc9e0f80d131e64b94\n913a7b40bb5\n\")[..]);\n\n    let vcpu_final_time = current_vcpu_ms()?;\n    assert!(vcpu_final_time - vcpu_initial_time > 10000);\n    Ok(())\n}\n\nfn main() -> Result<(), Error> {\n    let client_req = Request::from_client();\n\n    test_that_waiting_for_servers_increases_only_wall_time(client_req)?;\n    test_that_computing_factorial_increases_vcpu_time()?;\n\n    Response::from_status(200).send_to_client();\n    Ok(())\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/write-and-read-body.rs",
    "content": "use fastly::Body;\nuse std::io::{Read, Write};\n\nfn main() {\n    let mut body = Body::new();\n    body.write(b\"Hello\").unwrap();\n    body.write(b\", \").unwrap();\n\n    let mut body2 = Body::new();\n    body2.write(b\"Viceroy\").unwrap();\n    body2.write(b\"!\").unwrap();\n\n    body.append(body2);\n\n    let mut buf: Vec<u8> = vec![0, 0];\n    let res = body.read(buf.as_mut_slice());\n    assert_eq!(res.unwrap(), 2);\n    assert_eq!(buf, &b\"He\"[..]);\n\n    assert_eq!(body.into_string(), \"llo, Viceroy!\");\n}\n"
  },
  {
    "path": "test-fixtures/src/bin/write-body.rs",
    "content": "//! A small test fixture used for testing APIs on bodies.\n//!\n//! A small note: This program uses the `fastly_sys` crate directly, rather than the `fastly` crate\n//! (see #50) because [`BodyHandle`][handle-write] doesn't expose a way to write to the front of a\n//! body.\n//!\n//! The bare ABI types are used so that we can test that writing to the front of a body works as\n//! expected.\n//!\n//! [handle-write]: https://docs.rs/fastly/0.4.0/fastly/body/struct.BodyHandle.html#method.write_bytes\n\nuse {\n    fastly_shared::BodyWriteEnd,\n    fastly_sys::{\n        fastly_http_body as http_body, fastly_http_resp as http_resp, BodyHandle, ResponseHandle,\n    },\n};\n\nfn main() {\n    // Initialize a body, using the `new` hostcall.\n    let mut resp_body: BodyHandle = 0;\n    unsafe {\n        http_body::new(&mut resp_body)\n            .result()\n            .expect(\"can create a new body\");\n    }\n\n    // Write the string \"Viceroy\" to the response body, calling `write` twice.\n    {\n        let msg_1 = \"Vice\";\n        let mut nwritten_1 = 0;\n        unsafe {\n            http_body::write(\n                resp_body,\n                msg_1.as_ptr(),\n                msg_1.len(),\n                BodyWriteEnd::Back,\n                &mut nwritten_1,\n            )\n            .result()\n            .expect(\"can write to the end of a body\");\n        }\n        assert_eq!(nwritten_1, msg_1.len());\n\n        let msg_2 = \"roy\";\n        let mut nwritten_2 = 0;\n        unsafe {\n            http_body::write(\n                resp_body,\n                msg_2.as_ptr(),\n                msg_2.len(),\n                BodyWriteEnd::Back,\n                &mut nwritten_2,\n            )\n            .result()\n            .expect(\"can write to the end of a body\");\n        }\n        assert_eq!(nwritten_2, msg_2.len());\n    }\n\n    // Allocate another body, and write a \"!\" to it. We now have two bodies: \"Viceroy\" and \"!\"\n    let other_body = {\n        let mut other_body: BodyHandle = 0;\n        let msg = \"!\";\n        let mut nwritten = 0;\n        unsafe {\n            http_body::new(&mut other_body)\n                .result()\n                .expect(\"can create a new body\");\n            http_body::write(\n                other_body,\n                msg.as_ptr(),\n                msg.len(),\n                BodyWriteEnd::Back,\n                &mut nwritten,\n            )\n            .result()\n            .expect(\"can write to the end of another body\");\n        }\n        assert_eq!(nwritten, msg.len());\n        other_body\n    };\n\n    // Append the \"!\" to our response body, so it contains \"Viceroy!\"\n    unsafe {\n        http_body::append(resp_body, other_body)\n            .result()\n            .expect(\"bodies can be appended\");\n    }\n\n    // Write \"Hello, \" to the *front* of our response body. It should now say \"Hello, Viceroy!\"\n    {\n        let hello_msg = \"Hello, \";\n        let mut nwritten = 0;\n        unsafe {\n            http_body::write(\n                resp_body,\n                hello_msg.as_ptr(),\n                hello_msg.len(),\n                BodyWriteEnd::Front,\n                &mut nwritten,\n            )\n            .result()\n            .expect(\"can write to the end of a body\");\n        }\n        assert_eq!(nwritten, hello_msg.len());\n    }\n\n    // Finally, send the response downstream.\n    let mut resp: ResponseHandle = 0;\n    unsafe {\n        http_resp::new(&mut resp)\n            .result()\n            .expect(\"can create a new response\");\n        http_resp::send_downstream(resp, resp_body, 0)\n            .result()\n            .expect(\"can send the response downstream\");\n    }\n}\n"
  },
  {
    "path": "test-fixtures/src/limits.rs",
    "content": "// MAX_HEADER_NAME_LEN is defined in src/wiggle_abi/headers.rs but cannot\n// be imported due to compilation of this workspace to the wasi32-wasm target.\n// In a more perfect world, the Hyper crate would export a constant for this\n// value since it panics when attempting to parse header names longer than\n// 32,768.\npub const MAX_HEADER_NAME_LEN: usize = (1 << 16) - 1;\n"
  },
  {
    "path": "wasm_abi/adapter/Cargo.toml",
    "content": "[package]\nname = \"viceroy-component-adapter\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n[dependencies]\nwasi = { workspace = true }\nwit-bindgen-rust-macro = { workspace = true }\nbyte-array-literals = { workspace = true }\nbitflags = { workspace = true }\nfastly-shared = { workspace = true }\n\n[build-dependencies]\nwasm-encoder = { workspace = true }\nobject = { workspace = true }\n\n[lib]\ntest = false\ncrate-type = [\"cdylib\"]\n\n[workspace]\nresolver = \"3\"\n\n[workspace.dependencies]\nbitflags = { version = \"2.5.0\", default-features = false }\nbyte-array-literals = { path = \"./byte-array-literals\" }\nobject = { version = \"0.33\", default-features = false, features = [\"archive\"] }\nwasi = { version = \"0.11.0\", default-features = false }\nwasm-encoder = { version = \"0.239.0\", features = [\"wasmparser\"] }\nwit-bindgen-rust-macro = { version = \"0.46.0\", default-features = false }\nwit-component = \"0.239.0\"\nfastly-shared = \"0.11.5\"\n\n[profile.release.package.viceroy-component-adapter]\nopt-level = 's'\nstrip = 'debuginfo'\n\n[profile.release-library]\ninherits = \"release\"\n\n[profile.release-library-noshift]\ninherits = \"release\"\n\n[profile.release-noshift]\ninherits = \"release\"\n\n[profile.dev.package.viceroy-component-adapter]\n# Make dev look like a release build since this adapter module won't work with\n# a debug build that uses data segments and such.\nincremental = false\nopt-level = 's'\n# Omit assertions, which include failure messages which require string\n# initializers.\ndebug-assertions = false\n# Omit integer overflow checks, which include failure messages which require\n# string initializers.\noverflow-checks = false\n\n[features]\ndefault = [\"exports\"]\n# Include the exports, in addition to the imports. Disable this to build an\n# imports-only \"library\" adapter, which is useful for building libraries that\n# don't have their own `main` function.\nexports = []\n# Build an adapter that doesn't shift the memory address, which falls back\n# to rely on `cabi_realloc` or `memory.grow` to maintain WASI state.\n# This is only needed when using wit-bindgen and wac locally.\nnoshift = []\n\n[lints.rust.unexpected_cfgs]\nlevel = \"warn\"\n# rust 1.84 compat, by @dgohman-fastly: The \"std_feature\" option in the macro requests bindings that\n# include cfg(feature = \"std\") conditionals around things that depend on std, which we do because our adapter can't use std.\n# -> So we are disabling the warning for now.\ncheck-cfg = ['cfg(feature, values(\"std\"))']\n"
  },
  {
    "path": "wasm_abi/adapter/README.md",
    "content": "# The Fastly Component Adapter\n\nThis crate builds a wasm module that adapts both the preview1 api and the fastly\ncompute host calls to the component model. It started as a fork of the\n[wasi_snapshot_preview1] component adapter with the `proxy` feature manually\nexpanded out, with all of the fastly-specific functionality mostly being added\nin the `src/fastly` tree. The exception to this is the `http-incoming` export defined\nby the compute world of `compute.wit`, whose definition is in `src/lib.rs`\ninstead of being defined in `src/fastly`, as the `wit-bindgen::generate!` makes\nassumptions about relative module paths that make it hard to define elsewhere.\n\n## Adding New Host Calls\n\nWhen adding new witx host calls, the adapter will need to be updated to know how\nthey should be adapted to WIT APIs. In most cases, this will involve\nupdating the `/wasm_abi/wit/deps/fastly/compute.wit` package to describe what the\ncomponent imports of the new host call should look like, implementing it in both\n`/src/wiggle_abi` and `/src/component`, and then finally adding a\nversion of the host call to the adapter in `src/fastly`. The top-level `bindings`\nmodule contains the bindings to the imported WIT API.\n\n[wasi_snapshot_preview1]: https://github.com/bytecodealliance/wasmtime/tree/main/crates/wasi-preview1-component-adapter\n"
  },
  {
    "path": "wasm_abi/adapter/build.rs",
    "content": "use std::env;\nuse std::path::PathBuf;\n\nfn main() {\n    let out_dir = PathBuf::from(env::var(\"OUT_DIR\").unwrap());\n\n    let wasm = build_raw_intrinsics();\n    let archive = build_archive(&wasm);\n\n    std::fs::write(out_dir.join(\"libwasm-raw-intrinsics.a\"), &archive).unwrap();\n    println!(\"cargo:rustc-link-lib=static=wasm-raw-intrinsics\");\n    println!(\n        \"cargo:rustc-link-search=native={}\",\n        out_dir.to_str().unwrap()\n    );\n\n    // Some specific flags to `wasm-ld` to inform the shape of this adapter.\n    // Notably we're importing memory from the main module and additionally our\n    // own module has no stack at all since it's specifically allocated at\n    // startup.\n    println!(\"cargo:rustc-link-arg=--import-memory\");\n    // Set the stack pointer to use the first page of memory, as the stack space has been reserved by the `xqd_codegen::shift_mem` module.\n    #[cfg(not(feature = \"noshift\"))]\n    println!(\"cargo:rustc-link-arg=-zstack-size=65536\");\n    #[cfg(feature = \"noshift\")]\n    println!(\"cargo:rustc-link-arg=-zstack-size=0\");\n}\n\n/// This function will produce a wasm module which is itself an object file\n/// that is the basic equivalent of:\n///\n/// ```rust\n/// std::arch::global_asm!(\n///     \"\n///         .globaltype internal_state_ptr, i32\n///         internal_state_ptr:\n///     \"\n/// );\n///\n/// #[no_mangle]\n/// extern \"C\" fn get_state_ptr() -> *mut u8 {\n///     unsafe {\n///         let ret: *mut u8;\n///         std::arch::asm!(\n///             \"\n///                 global.get internal_state_ptr\n///             \",\n///             out(local) ret,\n///             options(nostack, readonly)\n///         );\n///         ret\n///     }\n/// }\n///\n/// #[no_mangle]\n/// extern \"C\" fn set_state_ptr(val: *mut u8) {\n///     unsafe {\n///         std::arch::asm!(\n///             \"\n///                 local.get {}\n///                 global.set internal_state_ptr\n///             \",\n///             in(local) val,\n///             options(nostack, readonly)\n///         );\n///     }\n/// }\n///\n/// // And likewise for `allocation_state`, `get_allocation_state`, and `set_allocation_state`\n/// ```\n///\n/// The main trickiness here is getting the `reloc.CODE` and `linking` sections\n/// right.\nfn build_raw_intrinsics() -> Vec<u8> {\n    use wasm_encoder::Instruction::*;\n    use wasm_encoder::*;\n\n    let mut module = Module::new();\n\n    let mut types = TypeSection::new();\n    types.ty().function([], [ValType::I32]);\n    types.ty().function([ValType::I32], []);\n    module.section(&types);\n\n    // Declare the functions, using the type we just added.\n    let mut funcs = FunctionSection::new();\n    funcs.function(0);\n    funcs.function(1);\n    funcs.function(0);\n    funcs.function(1);\n    module.section(&funcs);\n\n    // Declare the globals.\n    let mut globals = GlobalSection::new();\n    // internal_state_ptr\n    globals.global(\n        GlobalType {\n            val_type: ValType::I32,\n            mutable: true,\n            shared: false,\n        },\n        &ConstExpr::i32_const(0),\n    );\n    // allocation_state\n    globals.global(\n        GlobalType {\n            val_type: ValType::I32,\n            mutable: true,\n            shared: false,\n        },\n        // Default to StackAllocated, as the space is reserved by the `xqd_codegen::shift_mem` module.\n        // And it won't trigger the `[allocate_stack_via_realloc](https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wit-component/src/gc.rs#L122)` function\n        // in `wit-component`.\n        #[cfg(not(feature = \"noshift\"))]\n        &ConstExpr::i32_const(2),\n        #[cfg(feature = \"noshift\")]\n        &ConstExpr::i32_const(0),\n    );\n    module.section(&globals);\n\n    // Here the `code` section is defined. This is tricky because an offset is\n    // needed within the code section itself for the `reloc.CODE` section\n    // later. At this time `wasm-encoder` doesn't give enough functionality to\n    // use the high-level APIs. so everything is done manually here.\n    //\n    // First the function bodies are created and then they're appended into a\n    // code section.\n\n    let mut code = Vec::new();\n    4u32.encode(&mut code); // number of functions\n\n    let global_get = 0x23;\n    let global_set = 0x24;\n\n    let encode = |code: &mut _, global, instruction| {\n        assert!(global < 0x7F);\n\n        let mut body = Vec::new();\n        0u32.encode(&mut body); // no locals\n        if instruction == global_set {\n            LocalGet(0).encode(&mut body);\n        }\n        let global_offset = body.len() + 1;\n        // global.get $global ;; but with maximal encoding of $global\n        body.extend_from_slice(&[instruction, 0x80u8 + global, 0x80, 0x80, 0x80, 0x00]);\n        End.encode(&mut body);\n        body.len().encode(code); // length of the function\n        let offset = code.len() + global_offset;\n        code.extend_from_slice(&body); // the function itself\n        offset\n    };\n\n    let internal_state_ptr_ref1 = encode(&mut code, 0, global_get); // get_state_ptr\n    let internal_state_ptr_ref2 = encode(&mut code, 0, global_set); // set_state_ptr\n    let allocation_state_ref1 = encode(&mut code, 1, global_get); // get_allocation_state\n    let allocation_state_ref2 = encode(&mut code, 1, global_set); // set_allocation_state\n\n    module.section(&RawSection {\n        id: SectionId::Code as u8,\n        data: &code,\n    });\n\n    // Here the linking section is constructed. There is one symbol for each function and global. The injected\n    // globals here are referenced in the relocations below.\n    //\n    // More information about this format is at\n    // https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md\n    {\n        let mut linking = Vec::new();\n        linking.push(0x02); // version\n\n        linking.push(0x08); // `WASM_SYMBOL_TABLE`\n        let mut subsection = Vec::new();\n        6u32.encode(&mut subsection); // 6 symbols (4 functions + 2 globals)\n\n        subsection.push(0x00); // SYMTAB_FUNCTION\n        0x00.encode(&mut subsection); // flags\n        0u32.encode(&mut subsection); // function index\n        \"get_state_ptr\".encode(&mut subsection); // symbol name\n\n        subsection.push(0x00); // SYMTAB_FUNCTION\n        0x00.encode(&mut subsection); // flags\n        1u32.encode(&mut subsection); // function index\n        \"set_state_ptr\".encode(&mut subsection); // symbol name\n\n        subsection.push(0x00); // SYMTAB_FUNCTION\n        0x00.encode(&mut subsection); // flags\n        2u32.encode(&mut subsection); // function index\n        \"get_allocation_state\".encode(&mut subsection); // symbol name\n\n        subsection.push(0x00); // SYMTAB_FUNCTION\n        0x00.encode(&mut subsection); // flags\n        3u32.encode(&mut subsection); // function index\n        \"set_allocation_state\".encode(&mut subsection); // symbol name\n\n        subsection.push(0x02); // SYMTAB_GLOBAL\n        0x02.encode(&mut subsection); // flags (WASM_SYM_BINDING_LOCAL)\n        0u32.encode(&mut subsection); // global index\n        \"internal_state_ptr\".encode(&mut subsection); // symbol name\n\n        subsection.push(0x02); // SYMTAB_GLOBAL\n        0x00.encode(&mut subsection); // flags\n        1u32.encode(&mut subsection); // global index\n        \"allocation_state\".encode(&mut subsection); // symbol name\n\n        subsection.encode(&mut linking);\n        module.section(&CustomSection {\n            name: \"linking\".into(),\n            data: linking.into(),\n        });\n    }\n\n    // A `reloc.CODE` section is appended here with relocations for the\n    // `global`-referencing instructions that were added.\n    {\n        let mut reloc = Vec::new();\n        3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed)\n        4u32.encode(&mut reloc); // 4 relocations\n\n        reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB\n        internal_state_ptr_ref1.encode(&mut reloc); // offset\n        4u32.encode(&mut reloc); // symbol index\n\n        reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB\n        internal_state_ptr_ref2.encode(&mut reloc); // offset\n        4u32.encode(&mut reloc); // symbol index\n\n        reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB\n        allocation_state_ref1.encode(&mut reloc); // offset\n        5u32.encode(&mut reloc); // symbol index\n\n        reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB\n        allocation_state_ref2.encode(&mut reloc); // offset\n        5u32.encode(&mut reloc); // symbol index\n\n        module.section(&CustomSection {\n            name: \"reloc.CODE\".into(),\n            data: reloc.into(),\n        });\n    }\n\n    module.finish()\n}\n\n/// This function produces the output of `llvm-ar crus libfoo.a foo.o` given\n/// the object file above as input. The archive is what's eventually fed to\n/// LLD.\n///\n/// Like above this is still tricky, mainly around the production of the symbol\n/// table.\nfn build_archive(wasm: &[u8]) -> Vec<u8> {\n    use object::{bytes_of, endian::BigEndian, U32Bytes};\n\n    let mut archive = Vec::new();\n    archive.extend_from_slice(&object::archive::MAGIC);\n\n    // The symbol table is in the \"GNU\" format which means it has a structure\n    // that looks like:\n    //\n    // * a big-endian 32-bit integer for the number of symbols\n    // * N big-endian 32-bit integers for the offset to the object file, within\n    //   the entire archive, for which object has the symbol\n    // * N nul-delimited strings for each symbol\n    //\n    // Here we're building an archive with just a few symbols so it's a bit\n    // easier. Note though we don't know the offset of our `intrinsics.o` up\n    // front so it's left as 0 for now and filled in later.\n\n    let syms = [\n        \"get_state_ptr\",\n        \"set_state_ptr\",\n        \"get_allocation_state\",\n        \"set_allocation_state\",\n        \"allocation_state\",\n    ];\n\n    let mut symbol_table = Vec::new();\n    symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, syms.len() as u32)));\n    for _ in syms.iter() {\n        symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0)));\n    }\n    for s in syms.iter() {\n        symbol_table.extend_from_slice(&std::ffi::CString::new(*s).unwrap().into_bytes_with_nul());\n    }\n\n    archive.extend_from_slice(bytes_of(&object::archive::Header {\n        name: *b\"/               \",\n        date: *b\"0           \",\n        uid: *b\"0     \",\n        gid: *b\"0     \",\n        mode: *b\"0       \",\n        size: format!(\"{:<10}\", symbol_table.len())\n            .as_bytes()\n            .try_into()\n            .unwrap(),\n        terminator: object::archive::TERMINATOR,\n    }));\n    let symtab_offset = archive.len();\n    archive.extend_from_slice(&symbol_table);\n\n    // All archive members must start on even offsets\n    if archive.len() & 1 == 1 {\n        archive.push(0x00);\n    }\n\n    // Now that we have the starting offset of the `intrinsics.o` file go back\n    // and fill in the offset within the symbol table generated earlier.\n    let member_offset = archive.len();\n    for (index, _) in syms.iter().enumerate() {\n        let index = index + 1;\n        archive[symtab_offset + (index * 4)..][..4].copy_from_slice(bytes_of(&U32Bytes::new(\n            BigEndian,\n            member_offset.try_into().unwrap(),\n        )));\n    }\n\n    archive.extend_from_slice(object::bytes_of(&object::archive::Header {\n        name: *b\"intrinsics.o    \",\n        date: *b\"0           \",\n        uid: *b\"0     \",\n        gid: *b\"0     \",\n        mode: *b\"644     \",\n        size: format!(\"{:<10}\", wasm.len()).as_bytes().try_into().unwrap(),\n        terminator: object::archive::TERMINATOR,\n    }));\n    archive.extend_from_slice(&wasm);\n    archive\n}\n"
  },
  {
    "path": "wasm_abi/adapter/byte-array-literals/Cargo.toml",
    "content": "[package]\nname = \"byte-array-literals\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n[lib]\nproc-macro = true\ntest = false\ndoctest = false\n"
  },
  {
    "path": "wasm_abi/adapter/byte-array-literals/README.md",
    "content": "# byte-array-literals\n\nThis crate exists to solve a very peculiar problem for the\n`wasi-preview1-component-adapter`: we want to use string literals in our\nsource code, but the resulting binary (when compiled for\nwasm32-unknown-unknown) cannot contain any data sections.\n\nThe answer that @sunfishcode discovered is that these string literals, if\nrepresented as an array of u8 literals, these will somehow not end up in the\ndata section, at least when compiled with opt-level='s' on today's rustc\n(1.69.0). So, this crate exists to transform these literals using a proc\nmacro.\n\nIt is very possible this cheat code will abruptly stop working in some future\ncompiler, but we'll cross that bridge when we get to it. If it does, it will\nmanifest as additional constant data being present the same way using a normal\nstring literal does, so it will be a loud failure when it happens.\n"
  },
  {
    "path": "wasm_abi/adapter/byte-array-literals/src/lib.rs",
    "content": "extern crate proc_macro;\n\nuse proc_macro::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree};\n\n/// Expand a `str` literal into a byte array.\n#[proc_macro]\npub fn str(input: TokenStream) -> TokenStream {\n    let rv = convert_str(input);\n\n    vec![TokenTree::Group(Group::new(\n        Delimiter::Bracket,\n        rv.into_iter().collect(),\n    ))]\n    .into_iter()\n    .collect()\n}\n\n/// The same as `str` but appends a `'\\n'`.\n#[proc_macro]\npub fn str_nl(input: TokenStream) -> TokenStream {\n    let mut rv = convert_str(input);\n\n    rv.push(TokenTree::Literal(Literal::u8_suffixed(b'\\n')));\n\n    vec![TokenTree::Group(Group::new(\n        Delimiter::Bracket,\n        rv.into_iter().collect(),\n    ))]\n    .into_iter()\n    .collect()\n}\n\nfn convert_str(input: TokenStream) -> Vec<TokenTree> {\n    let mut it = input.into_iter();\n\n    let mut tokens = Vec::new();\n    match it.next() {\n        Some(TokenTree::Literal(l)) => {\n            for b in to_string(l).into_bytes() {\n                tokens.push(TokenTree::Literal(Literal::u8_suffixed(b)));\n                tokens.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));\n            }\n        }\n        _ => panic!(),\n    }\n\n    assert!(it.next().is_none());\n    tokens\n}\n\nfn to_string(lit: Literal) -> String {\n    let formatted = lit.to_string();\n\n    let mut it = formatted.chars();\n    assert_eq!(it.next(), Some('\"'));\n\n    let mut rv = String::new();\n    loop {\n        match it.next() {\n            Some('\"') => match it.next() {\n                Some(_) => panic!(),\n                None => break,\n            },\n            Some('\\\\') => match it.next() {\n                Some('x') => {\n                    let hi = it.next().unwrap().to_digit(16).unwrap();\n                    let lo = it.next().unwrap().to_digit(16).unwrap();\n                    let v = (hi << 16) | lo;\n                    rv.push(v as u8 as char);\n                }\n                Some('u') => {\n                    assert_eq!(it.next(), Some('{'));\n                    let mut c = it.next().unwrap();\n                    let mut ch = 0;\n                    while let Some(v) = c.to_digit(16) {\n                        ch *= 16;\n                        ch |= v;\n                        c = it.next().unwrap();\n                    }\n                    assert_eq!(c, '}');\n                    rv.push(::std::char::from_u32(ch).unwrap());\n                }\n                Some('0') => rv.push('\\0'),\n                Some('\\\\') => rv.push('\\\\'),\n                Some('\\\"') => rv.push('\\\"'),\n                Some('r') => rv.push('\\r'),\n                Some('n') => rv.push('\\n'),\n                Some('t') => rv.push('\\t'),\n                Some(_) => panic!(),\n                None => panic!(),\n            },\n            Some(c) => rv.push(c),\n            None => panic!(),\n        }\n    }\n\n    rv\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/descriptors.rs",
    "content": "use crate::bindings::wasi::cli::{stderr, stdin, stdout};\nuse crate::bindings::wasi::io::streams::{InputStream, OutputStream};\nuse crate::{State, TrappingUnwrap};\nuse core::cell::{Cell, OnceCell, UnsafeCell};\nuse core::mem::MaybeUninit;\nuse wasi::{Errno, Fd};\n\npub const MAX_DESCRIPTORS: usize = 128;\n\n#[repr(C)]\npub enum Descriptor {\n    /// A closed descriptor, holding a reference to the previous closed\n    /// descriptor to support reusing them.\n    Closed(Option<Fd>),\n\n    /// Input and/or output wasi-streams, along with stream metadata.\n    Streams(Streams),\n\n    Bad,\n}\n\n/// Input and/or output wasi-streams, along with a stream type that\n/// identifies what kind of stream they are and possibly supporting\n/// type-specific operations like seeking.\npub struct Streams {\n    /// The input stream, if present.\n    pub input: OnceCell<InputStream>,\n\n    /// The output stream, if present.\n    pub output: OnceCell<OutputStream>,\n\n    /// Information about the source of the stream.\n    pub type_: StreamType,\n}\n\nimpl Streams {\n    /// Return the input stream, initializing it on the fly if needed.\n    pub fn get_read_stream(&self) -> Result<&InputStream, Errno> {\n        match self.input.get() {\n            Some(wasi_stream) => Ok(wasi_stream),\n\n            // proxy worlds don't have filesystem access\n            None => Err(wasi::ERRNO_BADF),\n        }\n    }\n\n    /// Return the output stream, initializing it on the fly if needed.\n    pub fn get_write_stream(&self) -> Result<&OutputStream, Errno> {\n        match self.output.get() {\n            Some(wasi_stream) => Ok(wasi_stream),\n\n            // proxy worlds don't have filesystem access\n            None => Err(wasi::ERRNO_BADF),\n        }\n    }\n}\n\npub enum StreamType {\n    /// Streams for implementing stdio.\n    Stdio,\n}\n\n#[repr(C)]\npub struct Descriptors {\n    /// Storage of mapping from preview1 file descriptors to preview2 file\n    /// descriptors.\n    table: UnsafeCell<MaybeUninit<[Descriptor; MAX_DESCRIPTORS]>>,\n    table_len: Cell<u16>,\n\n    /// Points to the head of a free-list of closed file descriptors.\n    closed: Option<Fd>,\n}\n\nimpl Descriptors {\n    pub fn new(_state: &State) -> Self {\n        let d = Descriptors {\n            table: UnsafeCell::new(MaybeUninit::uninit()),\n            table_len: Cell::new(0),\n            closed: None,\n        };\n\n        fn new_once<T>(val: T) -> OnceCell<T> {\n            let cell = OnceCell::new();\n            let _ = cell.set(val);\n            cell\n        }\n\n        d.push(Descriptor::Streams(Streams {\n            input: new_once(stdin::get_stdin()),\n            output: OnceCell::new(),\n            type_: StreamType::Stdio,\n        }))\n        .trapping_unwrap();\n        d.push(Descriptor::Streams(Streams {\n            input: OnceCell::new(),\n            output: new_once(stdout::get_stdout()),\n            type_: StreamType::Stdio,\n        }))\n        .trapping_unwrap();\n        d.push(Descriptor::Streams(Streams {\n            input: OnceCell::new(),\n            output: new_once(stderr::get_stderr()),\n            type_: StreamType::Stdio,\n        }))\n        .trapping_unwrap();\n\n        d\n    }\n\n    fn push(&self, desc: Descriptor) -> Result<Fd, Errno> {\n        unsafe {\n            let table = (*self.table.get()).as_mut_ptr();\n            let len = usize::try_from(self.table_len.get()).trapping_unwrap();\n            if len >= (*table).len() {\n                return Err(wasi::ERRNO_NOMEM);\n            }\n            (&raw mut (*table)[len]).write(desc);\n            self.table_len.set(u16::try_from(len + 1).trapping_unwrap());\n            Ok(Fd::from(u32::try_from(len).trapping_unwrap()))\n        }\n    }\n\n    fn table(&self) -> &[Descriptor] {\n        unsafe {\n            std::slice::from_raw_parts(\n                (*self.table.get()).as_ptr().cast(),\n                usize::try_from(self.table_len.get()).trapping_unwrap(),\n            )\n        }\n    }\n\n    fn table_mut(&mut self) -> &mut [Descriptor] {\n        unsafe {\n            std::slice::from_raw_parts_mut(\n                (*self.table.get()).as_mut_ptr().cast(),\n                usize::try_from(self.table_len.get()).trapping_unwrap(),\n            )\n        }\n    }\n\n    pub fn open(&mut self, d: Descriptor) -> Result<Fd, Errno> {\n        match self.closed {\n            // No closed descriptors: expand table\n            None => self.push(d),\n            Some(freelist_head) => {\n                // Pop an item off the freelist\n                let freelist_desc = self.get_mut(freelist_head).trapping_unwrap();\n                let next_closed = match freelist_desc {\n                    Descriptor::Closed(next) => *next,\n                    _ => unreachable!(\"impossible: freelist points to a closed descriptor\"),\n                };\n                // Write descriptor to the entry at the head of the list\n                *freelist_desc = d;\n                // Point closed to the following item\n                self.closed = next_closed;\n                Ok(freelist_head)\n            }\n        }\n    }\n\n    pub fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> {\n        self.table()\n            .get(usize::try_from(fd).trapping_unwrap())\n            .ok_or(wasi::ERRNO_BADF)\n    }\n\n    pub fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> {\n        self.table_mut()\n            .get_mut(usize::try_from(fd).trapping_unwrap())\n            .ok_or(wasi::ERRNO_BADF)\n    }\n\n    // Close an fd.\n    pub fn close(&mut self, fd: Fd) -> Result<(), Errno> {\n        // Throw an error if closing an fd which is already closed\n        match self.get(fd)? {\n            Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?,\n            _ => {}\n        }\n        // Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list:\n        let last_closed = self.closed;\n        let prev = std::mem::replace(self.get_mut(fd)?, Descriptor::Closed(last_closed));\n        self.closed = Some(fd);\n        drop(prev);\n        Ok(())\n    }\n\n    // Expand the table by pushing a closed descriptor to the end. Used for renumbering.\n    fn push_closed(&mut self) -> Result<(), Errno> {\n        let old_closed = self.closed;\n        let new_closed = self.push(Descriptor::Closed(old_closed))?;\n        self.closed = Some(new_closed);\n        Ok(())\n    }\n\n    // Implementation of fd_renumber\n    pub fn renumber(&mut self, from_fd: Fd, to_fd: Fd) -> Result<(), Errno> {\n        // First, ensure from_fd is in bounds:\n        let _ = self.get(from_fd)?;\n        // Expand table until to_fd is in bounds as well:\n        while self.table_len.get() as u32 <= to_fd {\n            self.push_closed()?;\n        }\n        // Throw an error if renumbering a closed fd\n        match self.get(from_fd)? {\n            Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?,\n            _ => {}\n        }\n        // Close from_fd and put its contents into to_fd\n        if from_fd != to_fd {\n            // Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list:\n            let last_closed = self.closed;\n            let desc = std::mem::replace(self.get_mut(from_fd)?, Descriptor::Closed(last_closed));\n            self.closed = Some(from_fd);\n            // TODO FIXME if this overwrites a preopen, do we need to clear it from the preopen table?\n            *self.get_mut(to_fd)? = desc;\n        }\n        Ok(())\n    }\n\n    // A bunch of helper functions implemented in terms of the above pub functions:\n\n    pub fn get_read_stream(&self, fd: Fd) -> Result<&InputStream, Errno> {\n        match self.get(fd)? {\n            Descriptor::Streams(streams) => streams.get_read_stream(),\n            Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF),\n        }\n    }\n\n    pub fn get_write_stream(&self, fd: Fd) -> Result<&OutputStream, Errno> {\n        match self.get(fd)? {\n            Descriptor::Streams(streams) => streams.get_write_stream(),\n            Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF),\n        }\n    }\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/fastly/cache.rs",
    "content": "use super::{convert_result, BodyHandle, FastlyStatus, RequestHandle};\nuse crate::fastly::INVALID_HANDLE;\nuse crate::{alloc_result_opt, TrappingUnwrap};\nuse core::mem::ManuallyDrop;\nuse core::ops::Deref;\n\npub type CacheHandle = u32;\npub type CacheBusyHandle = u32;\npub type CacheReplaceHandle = u32;\n\npub type CacheObjectLength = u64;\npub type CacheDurationNs = u64;\npub type CacheHitCount = u64;\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n#[repr(C)]\npub struct CacheLookupOptions {\n    pub request_headers: RequestHandle,\n    pub service: *const u8,\n    pub service_len: u32,\n}\n\nbitflags::bitflags! {\n    #[repr(transparent)]\n    #[derive(Copy, Clone)]\n    pub struct CacheLookupOptionsMask: u32 {\n        const _RESERVED = 1 << 0;\n        const REQUEST_HEADERS = 1 << 1;\n        const SERVICE = 1 << 2;\n        const ALWAYS_USE_REQUESTED_RANGE = 1 << 3;\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n#[repr(C)]\npub struct CacheReplaceOptions {\n    pub request_headers: RequestHandle,\n    pub replace_strategy: u32,\n    pub service: *const u8,\n    pub service_len: u32,\n}\n\nbitflags::bitflags! {\n    #[repr(transparent)]\n    #[derive(Copy, Clone)]\n    pub struct CacheReplaceOptionsMask: u32 {\n        const _RESERVED = 1 << 0;\n        const REQUEST_HEADERS = 1 << 1;\n        const REPLACE_STRATEGY = 1 << 2;\n        const SERVICE = 1 << 3;\n        const ALWAYS_USE_REQUESTED_RANGE = 1 << 4;\n    }\n}\n\n#[repr(u32)]\npub enum CacheReplaceStrategy {\n    Immediate = 1,\n    ImmediateForceMiss = 2,\n    Wait = 3,\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n#[repr(C)]\npub struct CacheWriteOptions {\n    pub max_age_ns: u64,\n    pub request_headers: RequestHandle,\n    pub vary_rule_ptr: *const u8,\n    pub vary_rule_len: usize,\n    pub initial_age_ns: u64,\n    pub stale_while_revalidate_ns: u64,\n    pub surrogate_keys_ptr: *const u8,\n    pub surrogate_keys_len: usize,\n    pub length: CacheObjectLength,\n    pub user_metadata_ptr: *const u8,\n    pub user_metadata_len: usize,\n    pub edge_max_age_ns: u64,\n    pub service: *const u8,\n    pub service_len: u32,\n}\n\nbitflags::bitflags! {\n    #[repr(transparent)]\n    #[derive(Copy, Clone)]\n    pub struct CacheWriteOptionsMask: u32 {\n        const _RESERVED = 1 << 0;\n        const REQUEST_HEADERS = 1 << 1;\n        const VARY_RULE = 1 << 2;\n        const INITIAL_AGE_NS = 1 << 3;\n        const STALE_WHILE_REVALIDATE_NS = 1 << 4;\n        const SURROGATE_KEYS = 1 << 5;\n        const LENGTH = 1 << 6;\n        const USER_METADATA = 1 << 7;\n        const SENSITIVE_DATA = 1 << 8;\n        const EDGE_MAX_AGE_NS = 1 << 9;\n        const SERVICE = 1 << 10;\n    }\n}\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n#[repr(C)]\npub struct CacheGetBodyOptions {\n    pub from: u64,\n    pub to: u64,\n}\n\nbitflags::bitflags! {\n    #[repr(transparent)]\n    pub struct CacheGetBodyOptionsMask: u32 {\n        const _RESERVED = 1 << 0;\n        const FROM = 1 << 1;\n        const TO = 1 << 2;\n    }\n}\n\nbitflags::bitflags! {\n    #[repr(transparent)]\n    pub struct CacheLookupState: u32 {\n        const FOUND = 1 << 0;\n        const USABLE = 1 << 1;\n        const STALE = 1 << 2;\n        const MUST_INSERT_OR_UPDATE = 1 << 3;\n        const USABLE_IF_ERROR = 1 << 4;\n    }\n}\n\nmod cache {\n    use super::*;\n    use crate::bindings::fastly::compute::{cache, http_req};\n    use core::slice;\n\n    impl From<cache::LookupState> for CacheLookupState {\n        fn from(value: cache::LookupState) -> Self {\n            let mut flags = Self::empty();\n            flags.set(Self::FOUND, value.contains(cache::LookupState::FOUND));\n            flags.set(Self::USABLE, value.contains(cache::LookupState::USABLE));\n            flags.set(Self::STALE, value.contains(cache::LookupState::STALE));\n            flags.set(\n                Self::MUST_INSERT_OR_UPDATE,\n                value.contains(cache::LookupState::MUST_INSERT_OR_UPDATE),\n            );\n            flags.set(\n                Self::USABLE_IF_ERROR,\n                value.contains(cache::LookupState::USABLE_IF_ERROR),\n            );\n            flags\n        }\n    }\n\n    impl TryFrom<u32> for cache::ReplaceStrategy {\n        type Error = FastlyStatus;\n\n        fn try_from(value: u32) -> Result<Self, Self::Error> {\n            match value {\n                1 => Ok(Self::Immediate),\n                2 => Ok(Self::ImmediateForceMiss),\n                3 => Ok(Self::Wait),\n                _ => Err(FastlyStatus::INVALID_ARGUMENT),\n            }\n        }\n    }\n\n    /// Converts a witx `CacheLookupOptions` and `CacheLookupOptionsMask` into\n    /// Wit types. This initializes the `request_headers` and `extra` fields to\n    /// `None`; callers are expected to overwrite this with their own value.\n    unsafe fn convert_lookup_options<'a>(\n        options_mask: CacheLookupOptionsMask,\n    ) -> Result<cache::LookupOptions<'a>, FastlyStatus> {\n        let options = cache::LookupOptions {\n            // This is expected to be filled in by the caller.\n            request_headers: None,\n\n            always_use_requested_range: options_mask\n                .contains(CacheLookupOptionsMask::ALWAYS_USE_REQUESTED_RANGE),\n\n            // This is expected to be filled in by the caller.\n            extra: None,\n        };\n\n        Ok(options)\n    }\n\n    #[export_name = \"fastly_cache#lookup\"]\n    pub fn lookup(\n        cache_key_ptr: *const u8,\n        cache_key_len: usize,\n        options_mask: CacheLookupOptionsMask,\n        options: *const CacheLookupOptions,\n        cache_handle_out: *mut CacheHandle,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheLookupOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n\n        let cache_key = unsafe { slice::from_raw_parts(main_ptr!(cache_key_ptr), cache_key_len) };\n\n        let mut new_options = match unsafe { convert_lookup_options(options_mask) } {\n            Ok(tuple) => tuple,\n            Err(err) => return err,\n        };\n\n        let request_headers;\n        if options_mask.contains(CacheLookupOptionsMask::REQUEST_HEADERS) {\n            request_headers = match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            };\n            new_options.request_headers = request_headers.as_deref();\n        }\n\n        let options = new_options;\n\n        let res = cache::Entry::lookup(cache_key, &options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(cache_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    /// In order to borrow the `request_headers` with the needed lifetime, we\n    /// oblige the caller to pass it in. This initializes the `extra` field to\n    /// `None`; callers are expected to overwrite this with their own value.\n    unsafe fn write_options<'a>(\n        mask: CacheWriteOptionsMask,\n        options: *const CacheWriteOptions,\n        request_headers: Option<&'a ManuallyDrop<http_req::Request>>,\n    ) -> Result<cache::WriteOptions<'a>, FastlyStatus> {\n        macro_rules! make_vec {\n            ($ptr_field:ident, $len_field:ident) => {\n                unsafe { crate::make_vec!(main_ptr!((*options).$ptr_field), (*options).$len_field) }\n            };\n        }\n        macro_rules! make_string {\n            ($ptr_field:ident, $len_field:ident) => {\n                crate::make_string!(\n                    unsafe_main_ptr!((*options).$ptr_field),\n                    (*options).$len_field\n                )\n            };\n        }\n\n        let vary_rule = make_string!(vary_rule_ptr, vary_rule_len);\n        let surrogate_keys = make_string!(surrogate_keys_ptr, surrogate_keys_len);\n        let user_metadata = make_vec!(user_metadata_ptr, user_metadata_len);\n        Ok(cache::WriteOptions {\n            max_age_ns: (*options).max_age_ns,\n            request_headers: request_headers.map(ManuallyDrop::deref),\n            vary_rule: if mask.contains(CacheWriteOptionsMask::VARY_RULE) {\n                Some(ManuallyDrop::into_inner(vary_rule))\n            } else {\n                None\n            },\n            initial_age_ns: if mask.contains(CacheWriteOptionsMask::INITIAL_AGE_NS) {\n                Some((*options).initial_age_ns)\n            } else {\n                None\n            },\n            stale_while_revalidate_ns: if mask\n                .contains(CacheWriteOptionsMask::STALE_WHILE_REVALIDATE_NS)\n            {\n                Some((*options).stale_while_revalidate_ns)\n            } else {\n                None\n            },\n            surrogate_keys: if mask.contains(CacheWriteOptionsMask::SURROGATE_KEYS) {\n                Some(ManuallyDrop::into_inner(surrogate_keys))\n            } else {\n                None\n            },\n            length: if mask.contains(CacheWriteOptionsMask::LENGTH) {\n                Some((*options).length)\n            } else {\n                None\n            },\n            user_metadata: if mask.contains(CacheWriteOptionsMask::USER_METADATA) {\n                Some(ManuallyDrop::into_inner(user_metadata))\n            } else {\n                None\n            },\n            edge_max_age_ns: if mask.contains(CacheWriteOptionsMask::EDGE_MAX_AGE_NS) {\n                Some((*options).edge_max_age_ns)\n            } else {\n                None\n            },\n            sensitive_data: mask.contains(CacheWriteOptionsMask::SENSITIVE_DATA),\n\n            // This is expected to be filled in by the caller.\n            extra: None,\n        })\n    }\n\n    #[export_name = \"fastly_cache#insert\"]\n    pub fn insert(\n        cache_key_ptr: *const u8,\n        cache_key_len: usize,\n        options_mask: CacheWriteOptionsMask,\n        options: *const CacheWriteOptions,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheWriteOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n\n        let cache_key = unsafe { slice::from_raw_parts(main_ptr!(cache_key_ptr), cache_key_len) };\n\n        let request_headers = if options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS) {\n            match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            }\n        } else {\n            None\n        };\n\n        let options =\n            match unsafe { write_options(options_mask, options, request_headers.as_ref()) } {\n                Ok(options) => options,\n                Err(err) => return err,\n            };\n\n        let res = cache::insert(cache_key, &options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#transaction_lookup\"]\n    pub fn transaction_lookup(\n        cache_key_ptr: *const u8,\n        cache_key_len: usize,\n        options_mask: CacheLookupOptionsMask,\n        options: *const CacheLookupOptions,\n        cache_handle_out: *mut CacheHandle,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheLookupOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n\n        let cache_key = unsafe { slice::from_raw_parts(main_ptr!(cache_key_ptr), cache_key_len) };\n        let mut new_options = match unsafe { convert_lookup_options(options_mask) } {\n            Ok(tuple) => tuple,\n            Err(err) => return err,\n        };\n        let request_headers;\n        if options_mask.contains(CacheLookupOptionsMask::REQUEST_HEADERS) {\n            request_headers = match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            };\n            new_options.request_headers = request_headers.as_deref();\n        }\n\n        let options = new_options;\n\n        let res = cache::Entry::transaction_lookup(cache_key, &options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(cache_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#transaction_lookup_async\"]\n    pub fn transaction_lookup_async(\n        cache_key_ptr: *const u8,\n        cache_key_len: usize,\n        options_mask: CacheLookupOptionsMask,\n        options: *const CacheLookupOptions,\n        cache_handle_out: *mut CacheBusyHandle,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheLookupOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n\n        let cache_key = unsafe { slice::from_raw_parts(main_ptr!(cache_key_ptr), cache_key_len) };\n        let mut new_options = match unsafe { convert_lookup_options(options_mask) } {\n            Ok(tuple) => tuple,\n            Err(err) => return err,\n        };\n        let request_headers;\n        if options_mask.contains(CacheLookupOptionsMask::REQUEST_HEADERS) {\n            request_headers = match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            };\n            new_options.request_headers = request_headers.as_deref();\n        }\n\n        let options = new_options;\n\n        let res = cache::Entry::transaction_lookup_async(cache_key, &options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(cache_handle_out) = res.take_handle();\n                }\n\n                // We just created a new `CacheBusyHandle` so forget the\n                // recently consumed one.\n                crate::State::with::<FastlyStatus>(|state| {\n                    state\n                        .recently_consumed_cache_busy_handle\n                        .set(INVALID_HANDLE);\n                    Ok(())\n                });\n\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#cache_busy_handle_wait\"]\n    pub fn cache_busy_handle_wait(\n        handle: CacheBusyHandle,\n        cache_handle_out: *mut CacheHandle,\n    ) -> FastlyStatus {\n        let cache_busy_handle = unsafe { cache::PendingEntry::from_handle(handle) };\n        match cache::await_entry(cache_busy_handle) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(cache_handle_out) = res.take_handle();\n                }\n\n                // Remember that we just consumed `handle` so that if there's\n                // a subsequent call to `close`, we can avoid double-closing it.\n                crate::State::with::<FastlyStatus>(|state| {\n                    state.recently_consumed_cache_busy_handle.set(handle);\n                    Ok(())\n                });\n\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#transaction_insert\"]\n    pub fn transaction_insert(\n        handle: CacheHandle,\n        options_mask: CacheWriteOptionsMask,\n        options: *const CacheWriteOptions,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheWriteOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        let request_headers = if options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS) {\n            match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            }\n        } else {\n            None\n        };\n        let options =\n            match unsafe { write_options(options_mask, options, request_headers.as_ref()) } {\n                Ok(options) => options,\n                Err(err) => return err,\n            };\n\n        let res = handle.transaction_insert(&options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#transaction_insert_and_stream_back\"]\n    pub fn transaction_insert_and_stream_back(\n        handle: CacheHandle,\n        options_mask: CacheWriteOptionsMask,\n        options: *const CacheWriteOptions,\n        body_handle_out: *mut BodyHandle,\n        cache_handle_out: *mut CacheHandle,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheWriteOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        let request_headers = if options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS) {\n            match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            }\n        } else {\n            None\n        };\n        let options =\n            match unsafe { write_options(options_mask, options, request_headers.as_ref()) } {\n                Ok(options) => options,\n                Err(err) => return err,\n            };\n\n        let res = handle.transaction_insert_and_stream_back(&options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok((body_handle, cache_handle)) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = body_handle.take_handle();\n                    *main_ptr!(cache_handle_out) = cache_handle.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#transaction_update\"]\n    pub fn transaction_update(\n        handle: CacheHandle,\n        options_mask: CacheWriteOptionsMask,\n        options: *const CacheWriteOptions,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheWriteOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        let request_headers = if options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS) {\n            match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            }\n        } else {\n            None\n        };\n        let options =\n            match unsafe { write_options(options_mask, options, request_headers.as_ref()) } {\n                Ok(options) => options,\n                Err(err) => return err,\n            };\n\n        let res = handle.transaction_update(&options);\n\n        std::mem::forget(options);\n\n        convert_result(res)\n    }\n\n    #[export_name = \"fastly_cache#transaction_cancel\"]\n    pub fn transaction_cancel(handle: CacheHandle) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        convert_result(handle.transaction_cancel())\n    }\n\n    #[export_name = \"fastly_cache#close_busy\"]\n    pub fn close_busy(handle: CacheBusyHandle) -> FastlyStatus {\n        // If `handle` is the handle that we recently consumed in\n        // `cache_busy_handle_wait`, don't close it, as it's already closed.\n        let status = crate::State::with::<FastlyStatus>(|state| {\n            let old = state\n                .recently_consumed_cache_busy_handle\n                .replace(INVALID_HANDLE);\n            if handle == old {\n                Err(FastlyStatus::BADF)\n            } else {\n                Ok(())\n            }\n        });\n        if status == FastlyStatus::BADF {\n            return FastlyStatus::OK;\n        }\n\n        let handle = unsafe { cache::PendingEntry::from_handle(handle) };\n        convert_result(cache::close_pending_entry(handle))\n    }\n\n    #[export_name = \"fastly_cache#close\"]\n    pub fn close(handle: CacheHandle) -> FastlyStatus {\n        // If `handle` is the handle that we recently consumed in\n        // `replace_insert`, don't close it, as it's already closed.\n        let status = crate::State::with::<FastlyStatus>(|state| {\n            let old = state\n                .recently_consumed_cache_replace_handle\n                .replace(INVALID_HANDLE);\n            if handle == old {\n                Err(FastlyStatus::BADF)\n            } else {\n                Ok(())\n            }\n        });\n        if status == FastlyStatus::BADF {\n            return FastlyStatus::OK;\n        }\n\n        // The witx `close` is shared between cache entries and replace entries.\n        // We set a bit in the returned handle index to distinguish the two.\n        if is_replace_entry(handle) {\n            let handle = decode_replace_entry(handle);\n            let handle = unsafe { cache::ReplaceEntry::from_handle(handle) };\n            convert_result(cache::close_replace_entry(handle))\n        } else {\n            let handle = unsafe { cache::Entry::from_handle(handle) };\n            convert_result(cache::close_entry(handle))\n        }\n    }\n\n    #[export_name = \"fastly_cache#get_state\"]\n    pub fn get_state(\n        handle: CacheHandle,\n        cache_lookup_state_out: *mut CacheLookupState,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        match handle.get_state() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(cache_lookup_state_out) = res.into();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#get_user_metadata\"]\n    pub fn get_user_metadata(\n        handle: CacheHandle,\n        user_metadata_out_ptr: *mut u8,\n        user_metadata_out_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(user_metadata_out_ptr),\n            user_metadata_out_len,\n            main_ptr!(nwritten_out),\n            { handle.get_user_metadata(u64::try_from(user_metadata_out_len).trapping_unwrap(),) }\n        )\n    }\n\n    impl From<(CacheGetBodyOptionsMask, CacheGetBodyOptions)> for cache::GetBodyOptions<'_> {\n        fn from((mask, value): (CacheGetBodyOptionsMask, CacheGetBodyOptions)) -> Self {\n            Self {\n                from: mask\n                    .contains(CacheGetBodyOptionsMask::FROM)\n                    .then_some(value.from),\n                to: mask\n                    .contains(CacheGetBodyOptionsMask::TO)\n                    .then_some(value.to),\n                extra: None,\n            }\n        }\n    }\n\n    #[export_name = \"fastly_cache#get_body\"]\n    pub fn get_body(\n        handle: CacheHandle,\n        options_mask: CacheGetBodyOptionsMask,\n        options: *const CacheGetBodyOptions,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        let options = unsafe { cache::GetBodyOptions::from((options_mask, *main_ptr!(options))) };\n\n        let res = handle.get_body(&options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#get_length\"]\n    pub fn get_length(handle: CacheHandle, length_out: *mut CacheObjectLength) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        match handle.get_length() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(length_out) = res;\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#get_max_age_ns\"]\n    pub fn get_max_age_ns(handle: CacheHandle, duration_out: *mut CacheDurationNs) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        match handle.get_max_age_ns() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(duration_out) = res;\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#get_stale_while_revalidate_ns\"]\n    pub fn get_stale_while_revalidate_ns(\n        handle: CacheHandle,\n        duration_out: *mut CacheDurationNs,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        match handle.get_stale_while_revalidate_ns() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(duration_out) = res;\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#get_age_ns\"]\n    pub fn get_age_ns(handle: CacheHandle, duration_out: *mut CacheDurationNs) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        match handle.get_age_ns() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(duration_out) = res;\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#get_hits\"]\n    pub fn get_hits(handle: CacheHandle, hits_out: *mut CacheHitCount) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { cache::Entry::from_handle(handle) });\n        match handle.get_hits() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(hits_out) = res;\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace\"]\n    pub fn replace(\n        cache_key_ptr: *const u8,\n        cache_key_len: usize,\n        options_mask: CacheReplaceOptionsMask,\n        options: *const CacheReplaceOptions,\n        cache_handle_out: *mut CacheHandle,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheReplaceOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n        let cache_key = unsafe { slice::from_raw_parts(main_ptr!(cache_key_ptr), cache_key_len) };\n\n        let replace_strategy = if options_mask.contains(CacheReplaceOptionsMask::REPLACE_STRATEGY) {\n            match cache::ReplaceStrategy::try_from(unsafe { (*options).replace_strategy }) {\n                Ok(r) => Some(r),\n                Err(e) => return e,\n            }\n        } else {\n            None\n        };\n\n        let request_headers = if options_mask.contains(CacheReplaceOptionsMask::REQUEST_HEADERS) {\n            match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            }\n        } else {\n            None\n        };\n        let options = cache::ReplaceOptions {\n            request_headers: request_headers.as_deref(),\n            replace_strategy,\n            always_use_requested_range: options_mask\n                .contains(CacheReplaceOptionsMask::ALWAYS_USE_REQUESTED_RANGE),\n            extra: None,\n        };\n\n        let res = cache::ReplaceEntry::replace(cache_key, &options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(cache_handle_out) = encode_replace_entry(res.take_handle());\n                }\n\n                // We just created a new `CacheReplaceHandle` so forget the\n                // recently consumed one.\n                crate::State::with::<FastlyStatus>(|state| {\n                    state\n                        .recently_consumed_cache_replace_handle\n                        .set(INVALID_HANDLE);\n                    Ok(())\n                });\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_insert\"]\n    pub fn replace_insert(\n        handle: CacheReplaceHandle,\n        options_mask: CacheWriteOptionsMask,\n        options: *const CacheWriteOptions,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        if options_mask.contains(CacheWriteOptionsMask::SERVICE) {\n            return FastlyStatus::UNSUPPORTED;\n        }\n\n        let options = unsafe_main_ptr!(options);\n\n        let replace_handle = decode_replace_entry(handle);\n        let replace_handle = unsafe { cache::ReplaceEntry::from_handle(replace_handle) };\n        let request_headers = if options_mask.contains(CacheWriteOptionsMask::REQUEST_HEADERS) {\n            match unsafe { (*options).request_headers } {\n                INVALID_HANDLE => None,\n                request_headers => Some(ManuallyDrop::new(unsafe {\n                    http_req::Request::from_handle(request_headers)\n                })),\n            }\n        } else {\n            None\n        };\n        let options =\n            match unsafe { write_options(options_mask, options, request_headers.as_ref()) } {\n                Ok(options) => options,\n                Err(err) => return err,\n            };\n\n        let res = cache::replace_insert(replace_handle, &options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n\n                // Remember that we just consumed `handle` so that if there's\n                // a subsequent call to `close`, we can avoid double-closing it.\n                crate::State::with::<FastlyStatus>(|state| {\n                    state.recently_consumed_cache_replace_handle.set(handle);\n                    Ok(())\n                });\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_get_age_ns\"]\n    pub fn replace_get_age_ns(\n        handle: CacheReplaceHandle,\n        duration_out: *mut CacheDurationNs,\n    ) -> FastlyStatus {\n        let handle = decode_replace_entry(handle);\n        let handle = ManuallyDrop::new(unsafe { cache::ReplaceEntry::from_handle(handle) });\n        match handle.get_age_ns() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(duration_out) = res;\n                }\n\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_get_body\"]\n    pub fn replace_get_body(\n        handle: CacheReplaceHandle,\n        options_mask: CacheGetBodyOptionsMask,\n        options: *const CacheGetBodyOptions,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let handle = decode_replace_entry(handle);\n        let handle = ManuallyDrop::new(unsafe { cache::ReplaceEntry::from_handle(handle) });\n        let options = unsafe { cache::GetBodyOptions::from((options_mask, *main_ptr!(options))) };\n\n        let res = handle.get_body(&options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_get_hits\"]\n    pub fn replace_get_hits(\n        handle: CacheReplaceHandle,\n        hits_out: *mut CacheHitCount,\n    ) -> FastlyStatus {\n        let handle = decode_replace_entry(handle);\n        let handle = ManuallyDrop::new(unsafe { cache::ReplaceEntry::from_handle(handle) });\n        match handle.get_hits() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(hits_out) = res;\n                }\n\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_get_length\"]\n    pub fn replace_get_length(\n        handle: CacheReplaceHandle,\n        length_out: *mut CacheObjectLength,\n    ) -> FastlyStatus {\n        let handle = decode_replace_entry(handle);\n        let handle = ManuallyDrop::new(unsafe { cache::ReplaceEntry::from_handle(handle) });\n        match handle.get_length() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(length_out) = res;\n                }\n\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_get_max_age_ns\"]\n    pub fn replace_get_max_age_ns(\n        handle: CacheReplaceHandle,\n        duration_out: *mut CacheDurationNs,\n    ) -> FastlyStatus {\n        let handle = decode_replace_entry(handle);\n        let handle = ManuallyDrop::new(unsafe { cache::ReplaceEntry::from_handle(handle) });\n        match handle.get_max_age_ns() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(duration_out) = res;\n                }\n\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_get_stale_while_revalidate_ns\"]\n    pub fn replace_get_stale_while_revalidate_ns(\n        handle: CacheReplaceHandle,\n        duration_out: *mut CacheDurationNs,\n    ) -> FastlyStatus {\n        let handle = decode_replace_entry(handle);\n        let handle = ManuallyDrop::new(unsafe { cache::ReplaceEntry::from_handle(handle) });\n        match handle.get_stale_while_revalidate_ns() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(duration_out) = res;\n                }\n\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_get_state\"]\n    pub fn replace_get_state(\n        handle: CacheReplaceHandle,\n        cache_lookup_state_out: *mut CacheLookupState,\n    ) -> FastlyStatus {\n        let handle = decode_replace_entry(handle);\n        let handle = ManuallyDrop::new(unsafe { cache::ReplaceEntry::from_handle(handle) });\n        match handle.get_state() {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(cache_lookup_state_out) = res.into();\n                }\n\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_cache#replace_get_user_metadata\"]\n    pub fn replace_get_user_metadata(\n        handle: CacheReplaceHandle,\n        user_metadata_out_ptr: *mut u8,\n        user_metadata_out_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let handle = decode_replace_entry(handle);\n        let handle = ManuallyDrop::new(unsafe { cache::ReplaceEntry::from_handle(handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(user_metadata_out_ptr),\n            user_metadata_out_len,\n            main_ptr!(nwritten_out),\n            { handle.get_user_metadata(u64::try_from(user_metadata_out_len).trapping_unwrap(),) }\n        )\n    }\n}\n\n/// The witx `fastly_cache#close` function works on both `CacheHandle` values and\n/// `CacheReplace` values. In the WIT API, these are separate resources. To\n/// distinguish them, we add a bit to the handle value that we expose to witx that\n/// otherwise not used in the Canonical ABI.\n///\n/// The Canonical ABI doesn't use the high four bits, but we don't use the\n/// most-significant bit here to avoid handle values that may look negative to\n/// witx users.\nconst REPLACE_ENTRY_MARKER: u32 = 0x4000_0000;\n\n/// Convert a `CacheReplaceHandle` index into a `CacheHandle` index with the\n/// special flag set indicating that it's a replace entry.\nfn encode_replace_entry(cache_entry: CacheReplaceHandle) -> CacheHandle {\n    assert!(!is_replace_entry(cache_entry));\n    cache_entry | REPLACE_ENTRY_MARKER\n}\n\n/// Convert a `CacheHandle` index that holds an encoded replace entry into a\n/// `CacheReplaceHandle` index.\nfn decode_replace_entry(cache_entry: CacheHandle) -> CacheReplaceHandle {\n    assert!(is_replace_entry(cache_entry));\n    cache_entry & !REPLACE_ENTRY_MARKER\n}\n\n/// Test whether the given `CacheHandle` holds an encoded replace entry.\nfn is_replace_entry(cache_entry: CacheHandle) -> bool {\n    (cache_entry & REPLACE_ENTRY_MARKER) == REPLACE_ENTRY_MARKER\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/fastly/config_store.rs",
    "content": "use super::FastlyStatus;\nuse crate::{alloc_result_opt, bindings::fastly::compute::config_store, TrappingUnwrap};\nuse core::mem::ManuallyDrop;\n\npub type ConfigStoreHandle = u32;\n\n#[export_name = \"fastly_config_store#open\"]\npub fn open(\n    name: *const u8,\n    name_len: usize,\n    store_handle_out: *mut ConfigStoreHandle,\n) -> FastlyStatus {\n    let name = crate::make_str!(unsafe_main_ptr!(name), name_len);\n    match config_store::Store::open(name) {\n        Ok(res) => {\n            unsafe {\n                *main_ptr!(store_handle_out) = res.take_handle();\n            }\n            FastlyStatus::OK\n        }\n        // As a special case, `fastly_config_store#open` uses `BADF` to indicate not found.\n        Err(config_store::OpenError::NotFound) => FastlyStatus::BADF,\n        // As a special case, `fastly_config_store#open` uses `NONE` to indicate an empty name.\n        Err(config_store::OpenError::InvalidSyntax) if name_len == 0 => FastlyStatus::NONE,\n        // As a special case, `fastly_config_store#open` uses `UNSUPPORTED` to indicate an empty name.\n        Err(config_store::OpenError::NameTooLong) => FastlyStatus::UNSUPPORTED,\n        Err(e) => e.into(),\n    }\n}\n\n#[export_name = \"fastly_config_store#get\"]\npub fn get(\n    store_handle: ConfigStoreHandle,\n    key: *const u8,\n    key_len: usize,\n    value: *mut u8,\n    value_max_len: usize,\n    nwritten: *mut usize,\n) -> FastlyStatus {\n    let key = crate::make_str!(unsafe_main_ptr!(key), key_len);\n    let store_handle = ManuallyDrop::new(unsafe { config_store::Store::from_handle(store_handle) });\n    alloc_result_opt!(\n        unsafe_main_ptr!(value),\n        value_max_len,\n        main_ptr!(nwritten),\n        { store_handle.get(key, u64::try_from(value_max_len).trapping_unwrap()) }\n    )\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/fastly/core.rs",
    "content": "// The following type aliases are used for readability of definitions in this module. They should\n// not be confused with types of similar names in the `fastly` crate which are used to provide safe\n// wrappers around these definitions.\n\nuse super::{convert_result, FastlyStatus};\nuse crate::fastly::decode_ip_address;\nuse crate::{\n    alloc_result, alloc_result_opt, handle_buffer_len, make_vec, with_buffer, write_bool_result,\n    TrappingUnwrap,\n};\nuse core::mem::ManuallyDrop;\n\nimpl From<crate::bindings::fastly::compute::http_types::HttpVersion> for u32 {\n    fn from(value: crate::bindings::fastly::compute::http_types::HttpVersion) -> Self {\n        use crate::bindings::fastly::compute::http_types::HttpVersion;\n        match value {\n            HttpVersion::Http09 => 0,\n            HttpVersion::Http10 => 1,\n            HttpVersion::Http11 => 2,\n            HttpVersion::H2 => 3,\n            HttpVersion::H3 => 4,\n        }\n    }\n}\n\nimpl TryFrom<u32> for crate::bindings::fastly::compute::http_types::HttpVersion {\n    type Error = u32;\n\n    fn try_from(value: u32) -> Result<Self, Self::Error> {\n        use crate::bindings::fastly::compute::http_types::HttpVersion;\n        match value {\n            0 => Ok(HttpVersion::Http09),\n            1 => Ok(HttpVersion::Http10),\n            2 => Ok(HttpVersion::Http11),\n            3 => Ok(HttpVersion::H2),\n            4 => Ok(HttpVersion::H3),\n            _ => Err(value),\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\n#[repr(u32)]\npub enum BodyWriteEnd {\n    Back = 0,\n    Front = 1,\n}\n\n/// Determines how the framing headers (`Content-Length`/`Transfer-Encoding`) are set for a\n/// request or response.\n#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\n#[repr(u32)]\npub enum FramingHeadersMode {\n    /// Determine the framing headers automatically based on the message body, and discard any framing\n    /// headers already set in the message. This is the default behavior.\n    ///\n    /// In automatic mode, a `Content-Length` is used when the size of the body can be determined\n    /// before it is sent. Requests/responses sent in streaming mode, where headers are sent immediately\n    /// but the content of the body is streamed later, will receive a `Transfer-Encoding: chunked`\n    /// to accommodate the dynamic generation of the body.\n    Automatic = 0,\n\n    /// Use the exact framing headers set in the message, falling back to [`Automatic`][`Self::Automatic`]\n    /// if invalid.\n    ///\n    /// In \"from headers\" mode, any `Content-Length` or `Transfer-Encoding` headers will be honored.\n    /// You must ensure that those headers have correct values permitted by the\n    /// [HTTP/1.1 specification][spec]. If the provided headers are not permitted by the spec,\n    /// the headers will revert to automatic mode and a log diagnostic will be issued about what was\n    /// wrong. If a `Content-Length` is permitted by the spec, but the value doesn't match the size of\n    /// the actual body, the body will either be truncated (if it is too long), or the connection will\n    /// be hung up early (if it is too short).\n    ///\n    /// [spec]: https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1\n    ManuallyFromHeaders = 1,\n}\n\n/// Determines whether the client is encouraged to stop using the current connection and to open a\n/// new one for the next request.\n///\n/// Most applications do not need to change this setting.\n#[doc(hidden)]\n#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\n#[repr(u32)]\npub enum HttpKeepaliveMode {\n    /// This is the default behavior.\n    Automatic = 0,\n\n    /// Send `Connection: close` in HTTP/1 and a GOAWAY frame in HTTP/2 and HTTP/3.  This prompts\n    /// the client to close the current connection and to open a new one for the next request.\n    NoKeepalive = 1,\n}\n\nimpl From<crate::bindings::fastly::compute::http_downstream::BotCategory> for u32 {\n    fn from(value: crate::bindings::fastly::compute::http_downstream::BotCategory) -> Self {\n        use crate::bindings::fastly::compute::http_downstream::BotCategory::*;\n        match value {\n            None => 0,\n            Suspected => 1,\n            Accessibility => 2,\n            AiCrawler => 3,\n            AiFetcher => 4,\n            ContentFetcher => 5,\n            MonitoringSiteTools => 6,\n            OnlineMarketing => 7,\n            PagePreview => 8,\n            PlatformIntegrations => 9,\n            Research => 10,\n            SearchEngineCrawler => 11,\n            SearchEngineOptimization => 12,\n            SecurityTools => 13,\n            Extra(resource) => resource.as_raw(),\n        }\n    }\n}\n\npub type AclHandle = u32;\npub type AsyncItemHandle = u32;\npub type BodyHandle = u32;\npub type DictionaryHandle = u32;\npub type KVStoreHandle = u32;\npub type ObjectStoreHandle = u32;\npub type PendingObjectStoreDeleteHandle = u32;\npub type PendingObjectStoreInsertHandle = u32;\npub type KVStoreLookupHandle = u32;\npub type KVStoreInsertHandle = u32;\npub type KVStoreDeleteHandle = u32;\npub type KVStoreListHandle = u32;\npub type PendingObjectStoreLookupHandle = u32;\npub type PendingRequestHandle = u32;\npub type RequestHandle = u32;\npub type RequestPromiseHandle = u32;\npub type ResponseHandle = u32;\npub type SecretHandle = u32;\npub type SecretStoreHandle = u32;\n\npub const INVALID_HANDLE: u32 = u32::MAX - 1;\n\n#[repr(C)]\npub struct DynamicBackendConfig {\n    pub host_override: *const u8,\n    pub host_override_len: u32,\n    pub connect_timeout_ms: u32,\n    pub first_byte_timeout_ms: u32,\n    pub between_bytes_timeout_ms: u32,\n    pub ssl_min_version: u32,\n    pub ssl_max_version: u32,\n    pub cert_hostname: *const u8,\n    pub cert_hostname_len: u32,\n    pub ca_cert: *const u8,\n    pub ca_cert_len: u32,\n    pub ciphers: *const u8,\n    pub ciphers_len: u32,\n    pub sni_hostname: *const u8,\n    pub sni_hostname_len: u32,\n    pub client_certificate: *const u8,\n    pub client_certificate_len: u32,\n    pub client_key: SecretHandle,\n    pub http_keepalive_time_ms: u32,\n    pub tcp_keepalive_enable: u32,\n    pub tcp_keepalive_interval_secs: u32,\n    pub tcp_keepalive_probes: u32,\n    pub tcp_keepalive_time_secs: u32,\n    pub max_connections: u32,\n    pub max_use: u32,\n    pub max_lifetime_ms: u32,\n}\n\nimpl Default for DynamicBackendConfig {\n    fn default() -> Self {\n        DynamicBackendConfig {\n            host_override: std::ptr::null(),\n            host_override_len: 0,\n            connect_timeout_ms: 0,\n            first_byte_timeout_ms: 0,\n            between_bytes_timeout_ms: 0,\n            ssl_min_version: 0,\n            ssl_max_version: 0,\n            cert_hostname: std::ptr::null(),\n            cert_hostname_len: 0,\n            ca_cert: std::ptr::null(),\n            ca_cert_len: 0,\n            ciphers: std::ptr::null(),\n            ciphers_len: 0,\n            sni_hostname: std::ptr::null(),\n            sni_hostname_len: 0,\n            client_certificate: std::ptr::null(),\n            client_certificate_len: 0,\n            client_key: 0,\n            http_keepalive_time_ms: 0,\n            tcp_keepalive_enable: 0,\n            tcp_keepalive_interval_secs: 0,\n            tcp_keepalive_probes: 0,\n            tcp_keepalive_time_secs: 0,\n            max_connections: 0,\n            max_use: 0,\n            max_lifetime_ms: 0,\n        }\n    }\n}\n\nbitflags::bitflags! {\n    /// `Content-Encoding` codings.\n    #[derive(Default)]\n    #[repr(transparent)]\n    pub struct ContentEncodings: u32 {\n        const GZIP = 1 << 0;\n    }\n}\n\nimpl From<ContentEncodings> for crate::bindings::fastly::compute::http_req::ContentEncodings {\n    fn from(value: ContentEncodings) -> Self {\n        let mut flags = Self::empty();\n        flags.set(Self::GZIP, value.contains(ContentEncodings::GZIP));\n        flags\n    }\n}\n\nbitflags::bitflags! {\n    /// `BackendConfigOptions` codings.\n    #[derive(Default)]\n    #[repr(transparent)]\n    pub struct BackendConfigOptions: u32 {\n        const RESERVED = 1 << 0;\n        const HOST_OVERRIDE = 1 << 1;\n        const CONNECT_TIMEOUT = 1 << 2;\n        const FIRST_BYTE_TIMEOUT = 1 << 3;\n        const BETWEEN_BYTES_TIMEOUT = 1 << 4;\n        const USE_TLS = 1 << 5;\n        const TLS_MIN_VERSION = 1 << 6;\n        const TLS_MAX_VERSION = 1 << 7;\n        const CERT_HOSTNAME = 1 << 8;\n        const CA_CERT = 1 << 9;\n        const CIPHERS = 1 << 10;\n        const SNI_HOSTNAME = 1 << 11;\n        const DONT_POOL = 1 << 12;\n        const CLIENT_CERT = 1 << 13;\n        const GRPC = 1 << 14;\n        const KEEPALIVE = 1 << 15;\n        const POOLING_LIMITS = 1 << 16;\n        const PREFER_IPV4 = 1 << 17;\n    }\n}\n\nbitflags::bitflags! {\n    /// `InspectConfigOptions` codings.\n    #[derive(Default)]\n    #[repr(transparent)]\n    pub struct InspectConfigOptions: u32 {\n        const RESERVED = 1 << 0;\n        const CORP = 1 << 1;\n        const WORKSPACE = 1 << 2;\n        const OVERRIDE_CLIENT_IP = 1 << 3;\n    }\n}\n\n#[repr(C)]\npub struct InspectConfig {\n    pub corp: *const u8,\n    pub corp_len: u32,\n    pub workspace: *const u8,\n    pub workspace_len: u32,\n    pub override_client_ip_ptr: *const u8,\n    pub override_client_ip_len: u32,\n}\n\npub mod fastly_abi {\n    use super::*;\n\n    pub const ABI_VERSION: u64 = 1;\n\n    #[export_name = \"fastly_abi#init\"]\n    /// Tell the runtime what ABI version this program is using (FASTLY_ABI_VERSION)\n    pub fn init(abi_version: u64) -> FastlyStatus {\n        if abi_version != ABI_VERSION {\n            FastlyStatus::UNKNOWN_ERROR\n        } else {\n            FastlyStatus::OK\n        }\n    }\n}\n\npub mod fastly_compute_runtime {\n    use super::*;\n\n    #[export_name = \"fastly_compute_runtime#get_vcpu_ms\"]\n    pub fn get_vcpu_ms(vcpu_time_ms_out: *mut u64) -> FastlyStatus {\n        let time = crate::bindings::fastly::compute::compute_runtime::get_vcpu_ms();\n        unsafe {\n            *main_ptr!(vcpu_time_ms_out) = time;\n        }\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_compute_runtime#get_heap_mib\"]\n    pub fn get_heap_mib(heap_mb_out: *mut u32) -> FastlyStatus {\n        let heap = crate::bindings::fastly::compute::compute_runtime::get_heap_mib();\n        unsafe {\n            *main_ptr!(heap_mb_out) = heap;\n        }\n        FastlyStatus::OK\n    }\n}\n\npub mod fastly_http_body {\n    use super::*;\n    use crate::bindings::fastly::compute::http_body;\n    use core::slice;\n\n    #[export_name = \"fastly_http_body#append\"]\n    pub fn append(dst_handle: BodyHandle, src_handle: BodyHandle) -> FastlyStatus {\n        let dst_handle = ManuallyDrop::new(unsafe { http_body::Body::from_handle(dst_handle) });\n        let src_handle = unsafe { http_body::Body::from_handle(src_handle) };\n        convert_result(http_body::append(&dst_handle, src_handle))\n    }\n\n    #[export_name = \"fastly_http_body#new\"]\n    pub fn new(handle_out: *mut BodyHandle) -> FastlyStatus {\n        match http_body::new() {\n            Ok(handle) => {\n                unsafe {\n                    *main_ptr!(handle_out) = handle.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_body#read\"]\n    pub fn read(\n        body_handle: BodyHandle,\n        buf: *mut u8,\n        buf_len: usize,\n        nread_out: *mut usize,\n    ) -> FastlyStatus {\n        let body_handle = ManuallyDrop::new(unsafe { http_body::Body::from_handle(body_handle) });\n        alloc_result!(unsafe_main_ptr!(buf), buf_len, main_ptr!(nread_out), {\n            http_body::read(&body_handle, u32::try_from(buf_len).trapping_unwrap())\n        })\n    }\n\n    // overeager warning for extern declarations is a rustc bug: https://github.com/rust-lang/rust/issues/79581\n    #[allow(clashing_extern_declarations)]\n    #[export_name = \"fastly_http_body#write\"]\n    pub fn write(\n        body_handle: BodyHandle,\n        buf: *const u8,\n        buf_len: usize,\n        end: BodyWriteEnd,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let body_handle = ManuallyDrop::new(unsafe { http_body::Body::from_handle(body_handle) });\n        let buf = unsafe { slice::from_raw_parts(main_ptr!(buf), buf_len) };\n        let res = match end {\n            BodyWriteEnd::Back => {\n                http_body::write(&body_handle, buf).map(|len| len.try_into().unwrap())\n            }\n            BodyWriteEnd::Front => http_body::write_front(&body_handle, buf).map(|()| buf_len),\n        };\n        match res {\n            Ok(len) => {\n                unsafe {\n                    *main_ptr!(nwritten_out) = len;\n                }\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    /// Close a body, freeing its resources and causing any sends to finish.\n    #[export_name = \"fastly_http_body#close\"]\n    pub fn close(body_handle: BodyHandle) -> FastlyStatus {\n        let body_handle = unsafe { http_body::Body::from_handle(body_handle) };\n        convert_result(http_body::close(body_handle))\n    }\n\n    #[export_name = \"fastly_http_body#abandon\"]\n    pub fn abandon(body_handle: BodyHandle) -> FastlyStatus {\n        let body_handle = unsafe { http_body::Body::from_handle(body_handle) };\n        drop(body_handle);\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_http_body#trailer_append\"]\n    pub fn trailer_append(\n        body_handle: BodyHandle,\n        name: *const u8,\n        name_len: usize,\n        value: *const u8,\n        value_len: usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let value = unsafe { slice::from_raw_parts(main_ptr!(value), value_len) };\n        let body_handle = ManuallyDrop::new(unsafe { http_body::Body::from_handle(body_handle) });\n        convert_result(http_body::append_trailer(&body_handle, name, value))\n    }\n\n    #[export_name = \"fastly_http_body#trailer_names_get\"]\n    pub fn trailer_names_get(\n        body_handle: BodyHandle,\n        buf: *mut u8,\n        buf_len: usize,\n        cursor: u32,\n        ending_cursor: *mut i64,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let body_handle = ManuallyDrop::new(unsafe { http_body::Body::from_handle(body_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(buf),\n            buf_len,\n            {\n                http_body::get_trailer_names(\n                    &body_handle,\n                    u64::try_from(buf_len).trapping_unwrap(),\n                    cursor,\n                )\n            },\n            |res| {\n                let res = match res {\n                    Ok(value) => Ok(value),\n                    Err(http_body::TrailerError::NotAvailableYet) => {\n                        return Err(FastlyStatus::AGAIN);\n                    }\n                    Err(http_body::TrailerError::Error(err)) => Err(err),\n                };\n                let (bytes, next) = handle_buffer_len!(res, main_ptr!(nwritten));\n                let written = bytes.len();\n                let end = match next {\n                    Some(next) => i64::from(next),\n                    None => -1,\n                };\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                    *main_ptr!(ending_cursor) = end;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_body#trailer_value_get\"]\n    pub fn trailer_value_get(\n        body_handle: BodyHandle,\n        name: *const u8,\n        name_len: usize,\n        value: *mut u8,\n        value_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let body_handle = ManuallyDrop::new(unsafe { http_body::Body::from_handle(body_handle) });\n\n        with_buffer!(\n            unsafe_main_ptr!(value),\n            value_max_len,\n            {\n                http_body::get_trailer_value(\n                    &body_handle,\n                    name,\n                    u64::try_from(value_max_len).trapping_unwrap(),\n                )\n            },\n            |res| {\n                let res = match res {\n                    Ok(value) => Ok(value),\n                    Err(http_body::TrailerError::NotAvailableYet) => {\n                        return Err(FastlyStatus::AGAIN);\n                    }\n                    Err(http_body::TrailerError::Error(err)) => Err(err),\n                };\n                let bytes =\n                    handle_buffer_len!(res, main_ptr!(nwritten)).ok_or(FastlyStatus::NONE)?;\n                let written = bytes.len();\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_body#trailer_values_get\"]\n    pub fn trailer_values_get(\n        body_handle: BodyHandle,\n        name: *const u8,\n        name_len: usize,\n        buf: *mut u8,\n        buf_len: usize,\n        cursor: u32,\n        ending_cursor: *mut i64,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let body_handle = ManuallyDrop::new(unsafe { http_body::Body::from_handle(body_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(buf),\n            buf_len,\n            {\n                http_body::get_trailer_values(\n                    &body_handle,\n                    name,\n                    u64::try_from(buf_len).trapping_unwrap(),\n                    cursor,\n                )\n            },\n            |res| {\n                let res = match res {\n                    Ok(value) => Ok(value),\n                    Err(http_body::TrailerError::NotAvailableYet) => {\n                        return Err(FastlyStatus::AGAIN);\n                    }\n                    Err(http_body::TrailerError::Error(err)) => Err(err),\n                };\n                let (bytes, next) = handle_buffer_len!(res, main_ptr!(nwritten));\n                let written = bytes.len();\n                let end = match next {\n                    Some(next) => i64::from(next),\n                    None => -1,\n                };\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                    *main_ptr!(ending_cursor) = end;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_body#known_length\"]\n    pub fn known_length(body_handle: BodyHandle, length_out: *mut u64) -> FastlyStatus {\n        let body_handle = ManuallyDrop::new(unsafe { http_body::Body::from_handle(body_handle) });\n        match http_body::get_known_length(&body_handle) {\n            Some(len) => {\n                unsafe {\n                    *main_ptr!(length_out) = len;\n                }\n                FastlyStatus::OK\n            }\n            None => FastlyStatus::NONE,\n        }\n    }\n}\n\npub mod fastly_log {\n    use core::slice;\n\n    use super::*;\n    use crate::bindings::fastly;\n\n    #[export_name = \"fastly_log#endpoint_get\"]\n    pub fn endpoint_get(\n        name: *const u8,\n        name_len: usize,\n        endpoint_handle_out: *mut u32,\n    ) -> FastlyStatus {\n        let name = crate::make_str!(unsafe_main_ptr!(name), name_len);\n        match fastly::compute::log::Endpoint::open(name) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(endpoint_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            // As a special case, `fastly_log#endpoint_get` uses `LIMITEXCEEDED` to indicate a\n            // too-long name.\n            Err(fastly::compute::log::OpenError::NameTooLong) => FastlyStatus::LIMITEXCEEDED,\n            Err(e) => e.into(),\n        }\n    }\n\n    // overeager warning for extern declarations is a rustc bug: https://github.com/rust-lang/rust/issues/79581\n    #[allow(clashing_extern_declarations)]\n    #[export_name = \"fastly_log#write\"]\n    pub fn write(\n        endpoint_handle: u32,\n        msg: *const u8,\n        msg_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let msg = unsafe { slice::from_raw_parts(main_ptr!(msg), msg_len) };\n        let endpoint_handle = ManuallyDrop::new(unsafe {\n            fastly::compute::log::Endpoint::from_handle(endpoint_handle)\n        });\n        endpoint_handle.write(msg);\n        unsafe {\n            *main_ptr!(nwritten_out) = msg_len;\n        }\n        FastlyStatus::OK\n    }\n}\npub mod fastly_http_downstream {\n    use super::*;\n    use crate::{\n        bindings::fastly::compute::{http_downstream, http_req},\n        fastly::encode_ip_address,\n        TrappingUnwrap,\n    };\n\n    bitflags::bitflags! {\n        #[derive(Default, Clone, Debug, PartialEq, Eq)]\n        #[repr(transparent)]\n        pub struct NextRequestOptionsMask: u32 {\n            const RESERVED = 1 << 0;\n            const TIMEOUT = 1 << 1;\n        }\n    }\n\n    #[repr(C)]\n    pub struct NextRequestOptions {\n        pub timeout_ms: u64,\n    }\n\n    #[export_name = \"fastly_http_downstream#next_request\"]\n    pub fn next_request(\n        options_mask: NextRequestOptionsMask,\n        options: *const NextRequestOptions,\n        handle_out: *mut RequestPromiseHandle,\n    ) -> FastlyStatus {\n        let options = http_downstream::NextRequestOptions {\n            timeout_ms: options_mask\n                .contains(NextRequestOptionsMask::TIMEOUT)\n                .then(|| unsafe { (*main_ptr!(options)).timeout_ms }),\n            extra: None,\n        };\n\n        let res = http_downstream::next_request(&options);\n\n        // Don't drop the options; even though this specific option type doesn't\n        // currently have pointers or handles, future versions of it might.\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(err) => {\n                unsafe {\n                    *main_ptr!(handle_out) = INVALID_HANDLE;\n                }\n\n                err.into()\n            }\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#next_request_wait\"]\n    pub fn next_request_wait(\n        handle: RequestPromiseHandle,\n        req_handle_out: *mut RequestHandle,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let promise_handle = unsafe { http_req::PendingRequest::from_handle(handle) };\n        match http_downstream::await_request(promise_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(req_handle_out) = res.0.take_handle();\n                    *main_ptr!(body_handle_out) = res.1.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Ok(None) => {\n                unsafe {\n                    *main_ptr!(req_handle_out) = INVALID_HANDLE;\n                    *main_ptr!(body_handle_out) = INVALID_HANDLE;\n                }\n\n                FastlyStatus::NONE\n            }\n            Err(err) => {\n                unsafe {\n                    *main_ptr!(req_handle_out) = INVALID_HANDLE;\n                    *main_ptr!(body_handle_out) = INVALID_HANDLE;\n                }\n\n                err.into()\n            }\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#next_request_abandon\"]\n    pub fn next_request_abandon(handle: RequestPromiseHandle) -> FastlyStatus {\n        let handle = unsafe { http_req::PendingResponse::from_handle(handle) };\n        drop(handle);\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_original_header_names\"]\n    pub fn downstream_original_header_names(\n        req_handle: RequestHandle,\n        buf: *mut u8,\n        buf_len: usize,\n        cursor: u32,\n        ending_cursor: *mut i64,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(buf),\n            buf_len,\n            {\n                http_downstream::downstream_original_header_names(\n                    &req_handle,\n                    u64::try_from(buf_len).trapping_unwrap(),\n                    cursor,\n                )\n            },\n            |res| {\n                let (bytes, next) = handle_buffer_len!(res, main_ptr!(nwritten));\n                let written = bytes.len();\n                let end = match next {\n                    Some(next) => i64::from(next),\n                    None => -1,\n                };\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                    *main_ptr!(ending_cursor) = end;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_original_header_count\"]\n    pub fn downstream_original_header_count(\n        req_handle: RequestHandle,\n        count_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_original_header_count(&req_handle) {\n            Ok(count) => {\n                unsafe {\n                    *main_ptr!(count_out) = count;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_client_ip_addr\"]\n    pub fn downstream_client_ip_addr(\n        req_handle: RequestHandle,\n        addr_octets_out: *mut u8,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_client_ip_addr(&req_handle) {\n            Some(ip_addr) => unsafe {\n                *main_ptr!(nwritten_out) = encode_ip_address(ip_addr, main_ptr!(addr_octets_out))\n            },\n            None => unsafe {\n                // This is how the witx host implementation would report when\n                // the IP address is unknown.\n                *main_ptr!(nwritten_out) = 0;\n            },\n        }\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_server_ip_addr\"]\n    pub fn downstream_server_ip_addr(\n        req_handle: RequestHandle,\n        addr_octets_out: *mut u8,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_server_ip_addr(&req_handle) {\n            Some(ip_addr) => unsafe {\n                *main_ptr!(nwritten_out) = encode_ip_address(ip_addr, main_ptr!(addr_octets_out))\n            },\n            None => unsafe {\n                // This is how the witx host implementation would report when\n                // the IP address is unknown.\n                *main_ptr!(nwritten_out) = 0;\n            },\n        }\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_client_h2_fingerprint\"]\n    pub fn downstream_client_h2_fingerprint(\n        req_handle: RequestHandle,\n        h2fp_out: *mut u8,\n        h2fp_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result!(\n            unsafe_main_ptr!(h2fp_out),\n            h2fp_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_client_h2_fingerprint(\n                    &req_handle,\n                    u64::try_from(h2fp_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_client_request_id\"]\n    pub fn downstream_client_request_id(\n        req_handle: RequestHandle,\n        reqid_out: *mut u8,\n        reqid_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result!(\n            unsafe_main_ptr!(reqid_out),\n            reqid_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_client_request_id(\n                    &req_handle,\n                    u64::try_from(reqid_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_client_oh_fingerprint\"]\n    pub fn downstream_client_oh_fingerprint(\n        req_handle: RequestHandle,\n        ohfp_out: *mut u8,\n        ohfp_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result!(\n            unsafe_main_ptr!(ohfp_out),\n            ohfp_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_client_oh_fingerprint(\n                    &req_handle,\n                    u64::try_from(ohfp_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_client_ddos_detected\"]\n    pub fn downstream_client_ddos_detected(\n        req_handle: RequestHandle,\n        ddos_detected_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_client_ddos_detected(&req_handle) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(ddos_detected_out) = res.into();\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_tls_cipher_openssl_name\"]\n    pub fn downstream_tls_cipher_openssl_name(\n        req_handle: RequestHandle,\n        cipher_out: *mut u8,\n        cipher_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(cipher_out),\n            cipher_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_tls_cipher_openssl_name(\n                    &req_handle,\n                    u64::try_from(cipher_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_tls_protocol\"]\n    pub fn downstream_tls_protocol(\n        req_handle: RequestHandle,\n        protocol_out: *mut u8,\n        protocol_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(protocol_out),\n            protocol_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_tls_protocol(\n                    &req_handle,\n                    u64::try_from(protocol_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_tls_client_hello\"]\n    pub fn downstream_tls_client_hello(\n        req_handle: RequestHandle,\n        client_hello_out: *mut u8,\n        client_hello_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(client_hello_out),\n            client_hello_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_tls_client_hello(\n                    &req_handle,\n                    u64::try_from(client_hello_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_tls_client_servername\"]\n    pub fn downstream_tls_client_servername(\n        req_handle: RequestHandle,\n        sni_out: *mut u8,\n        sni_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(sni_out),\n            sni_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_tls_client_servername(\n                    &req_handle,\n                    u64::try_from(sni_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_tls_ja3_md5\"]\n    pub fn downstream_tls_ja3_md5(\n        req_handle: RequestHandle,\n        ja3_md5_out: *mut u8,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(ja3_md5_out),\n            16,\n            main_ptr!(nwritten_out),\n            { http_downstream::downstream_tls_ja3_md5(&req_handle) }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_tls_ja4\"]\n    pub fn downstream_tls_ja4(\n        req_handle: RequestHandle,\n        ja4_out: *mut u8,\n        ja4_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(ja4_out),\n            ja4_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_tls_ja4(\n                    &req_handle,\n                    u64::try_from(ja4_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_compliance_region\"]\n    pub fn downstream_compliance_region(\n        req_handle: RequestHandle,\n        region_out: *mut u8,\n        region_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(region_out),\n            region_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_compliance_region(\n                    &req_handle,\n                    u64::try_from(region_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_tls_raw_client_certificate\"]\n    pub fn downstream_tls_raw_client_certificate(\n        req_handle: RequestHandle,\n        client_certificate_out: *mut u8,\n        client_certificate_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(client_certificate_out),\n            client_certificate_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_tls_raw_client_certificate(\n                    &req_handle,\n                    u64::try_from(client_certificate_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_tls_client_cert_verify_result\"]\n    pub fn downstream_tls_client_cert_verify_result(\n        req_handle: RequestHandle,\n        verify_result_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_tls_client_cert_verify_result(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(verify_result_out) = res.into();\n                }\n\n                FastlyStatus::OK\n            }\n\n            Ok(None) => FastlyStatus::NONE,\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#fastly_key_is_valid\"]\n    pub fn fastly_key_is_valid(req_handle: RequestHandle, is_valid_out: *mut u32) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::fastly_key_is_valid(&req_handle) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(is_valid_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_bot_analyzed\"]\n    pub fn downstream_bot_analyzed(\n        req_handle: RequestHandle,\n        bot_analyzed_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_bot_analyzed(&req_handle) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(bot_analyzed_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_bot_detected\"]\n    pub fn downstream_bot_detected(\n        req_handle: RequestHandle,\n        bot_analyzed_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_bot_detected(&req_handle) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(bot_analyzed_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_bot_name\"]\n    pub fn downstream_bot_name(\n        req_handle: RequestHandle,\n        bot_name_out: *mut u8,\n        bot_name_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(bot_name_out),\n            bot_name_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_bot_name(\n                    &req_handle,\n                    u64::try_from(bot_name_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_bot_category\"]\n    pub fn downstream_bot_category(\n        req_handle: RequestHandle,\n        bot_category_out: *mut u8,\n        bot_category_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(bot_category_out),\n            bot_category_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_bot_category(\n                    &req_handle,\n                    u64::try_from(bot_category_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_bot_category_kind\"]\n    pub fn downstream_bot_category_kind(\n        req_handle: RequestHandle,\n        category_kind_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_bot_category_kind(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(category_kind_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_bot_verified\"]\n    pub fn downstream_bot_verified(\n        req_handle: RequestHandle,\n        bot_verified_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_bot_verified(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(bot_verified_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_anonymous\"]\n    pub fn downstream_resvpnproxy_is_anonymous(\n        req_handle: RequestHandle,\n        is_anonymous_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_anonymous(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_anonymous_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_anonymous_vpn\"]\n    pub fn downstream_resvpnproxy_is_anonymous_vpn(\n        req_handle: RequestHandle,\n        is_anonymous_vpn_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_anonymous_vpn(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_anonymous_vpn_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_hosting_provider\"]\n    pub fn downstream_resvpnproxy_is_hosting_provider(\n        req_handle: RequestHandle,\n        is_hosting_provider_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_hosting_provider(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_hosting_provider_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_proxy_over_vpn\"]\n    pub fn downstream_resvpnproxy_is_proxy_over_vpn(\n        req_handle: RequestHandle,\n        is_proxy_over_vpn_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_proxy_over_vpn(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_proxy_over_vpn_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_public_proxy\"]\n    pub fn downstream_resvpnproxy_is_public_proxy(\n        req_handle: RequestHandle,\n        is_public_proxy_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_public_proxy(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_public_proxy_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_relay_proxy\"]\n    pub fn downstream_resvpnproxy_is_relay_proxy(\n        req_handle: RequestHandle,\n        is_relay_proxy_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_relay_proxy(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_relay_proxy_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_residential_proxy\"]\n    pub fn downstream_resvpnproxy_is_residential_proxy(\n        req_handle: RequestHandle,\n        is_residential_proxy_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_residential_proxy(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_residential_proxy_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_smart_dns_proxy\"]\n    pub fn downstream_resvpnproxy_is_smart_dns_proxy(\n        req_handle: RequestHandle,\n        is_smart_dns_proxy_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_smart_dns_proxy(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_smart_dns_proxy_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_tor_exit_node\"]\n    pub fn downstream_resvpnproxy_is_tor_exit_node(\n        req_handle: RequestHandle,\n        is_tor_exit_node_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_tor_exit_node(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_tor_exit_node_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_is_vpn_datacenter\"]\n    pub fn downstream_resvpnproxy_is_vpn_datacenter(\n        req_handle: RequestHandle,\n        is_vpn_datacenter_out: *mut u32,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_downstream::downstream_resvpnproxy_is_vpn_datacenter(&req_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(is_vpn_datacenter_out) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_downstream#downstream_resvpnproxy_vpn_service_name\"]\n    pub fn downstream_resvpnproxy_vpn_service_name(\n        req_handle: RequestHandle,\n        vpn_service_name_out: *mut u8,\n        vpn_service_name_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(vpn_service_name_out),\n            vpn_service_name_max_len,\n            main_ptr!(nwritten),\n            {\n                http_downstream::downstream_resvpnproxy_vpn_service_name(\n                    &req_handle,\n                    u64::try_from(vpn_service_name_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n}\n\npub mod fastly_http_req {\n    use core::slice;\n\n    use super::*;\n    use crate::{\n        bindings::fastly::{\n            self,\n            adapter::adapter_http_req,\n            compute::{backend, http_req, http_types, security},\n        },\n        TrappingUnwrap,\n    };\n\n    bitflags::bitflags! {\n        #[derive(Default, Clone, Debug, PartialEq, Eq)]\n        #[repr(transparent)]\n        pub struct SendErrorDetailMask: u32 {\n            const RESERVED = 1 << 0;\n            const DNS_ERROR_RCODE = 1 << 1;\n            const DNS_ERROR_INFO_CODE = 1 << 2;\n            const TLS_ALERT_ID = 1 << 3;\n            const H2_ERROR = 1 << 4;\n        }\n    }\n\n    #[repr(u32)]\n    #[derive(Clone, Copy, Debug, PartialEq, Eq)]\n    pub enum SendErrorDetailTag {\n        Uninitialized,\n        Ok,\n        DnsTimeout,\n        DnsError,\n        DestinationNotFound,\n        DestinationUnavailable,\n        DestinationIpUnroutable,\n        ConnectionRefused,\n        ConnectionTerminated,\n        ConnectionTimeout,\n        ConnectionLimitReached,\n        TlsCertificateError,\n        TlsConfigurationError,\n        HttpIncompleteResponse,\n        HttpResponseHeaderSectionTooLarge,\n        HttpResponseBodyTooLarge,\n        HttpResponseTimeout,\n        HttpResponseStatusInvalid,\n        HttpUpgradeFailed,\n        HttpProtocolError,\n        HttpRequestCacheKeyInvalid,\n        HttpRequestUriInvalid,\n        InternalError,\n        TlsAlertReceived,\n        TlsProtocolError,\n        H2Error,\n    }\n\n    #[repr(C)]\n    #[derive(Clone, Debug, PartialEq, Eq)]\n    pub struct SendErrorDetail {\n        pub tag: SendErrorDetailTag,\n        pub mask: SendErrorDetailMask,\n        pub dns_error_rcode: u16,\n        pub dns_error_info_code: u16,\n        pub tls_alert_id: u8,\n        pub h2_error_frame: u8,\n        pub h2_error_code: u32,\n    }\n\n    /// Convert from witx ABI values to a `CacheOverride`.\n    impl From<(u32, u32, u32, Option<ManuallyDrop<Vec<u8>>>)>\n        for fastly::compute::http_req::CacheOverride<'_>\n    {\n        fn from((tag, ttl, swr, sk): (u32, u32, u32, Option<ManuallyDrop<Vec<u8>>>)) -> Self {\n            let flag_present = |n: u32| tag & (1 << n) != 0;\n\n            if flag_present(0) {\n                fastly::compute::http_req::CacheOverride::Pass\n            } else if tag == 0 {\n                fastly::compute::http_req::CacheOverride::None\n            } else {\n                fastly::compute::http_req::CacheOverride::Override(\n                    fastly::compute::http_req::CacheOverrideDetails {\n                        ttl: flag_present(1).then_some(ttl),\n                        stale_while_revalidate: flag_present(2).then_some(swr),\n                        pci: flag_present(3),\n                        surrogate_key: sk.map(ManuallyDrop::into_inner),\n                        extra: None,\n                    },\n                )\n            }\n        }\n    }\n\n    impl Into<u32> for fastly::compute::http_req::ClientCertVerifyResult {\n        fn into(self) -> u32 {\n            use fastly::compute::http_req::ClientCertVerifyResult;\n            match self {\n                ClientCertVerifyResult::Ok => 0,\n                ClientCertVerifyResult::BadCertificate => 1,\n                ClientCertVerifyResult::CertificateRevoked => 2,\n                ClientCertVerifyResult::CertificateExpired => 3,\n                ClientCertVerifyResult::UnknownCa => 4,\n                ClientCertVerifyResult::CertificateMissing => 5,\n                ClientCertVerifyResult::CertificateUnknown => 6,\n            }\n        }\n    }\n\n    impl From<fastly::compute::http_req::SendErrorDetail> for SendErrorDetail {\n        fn from(err: fastly::compute::http_req::SendErrorDetail) -> Self {\n            match err {\n                http_req::SendErrorDetail::DnsTimeout => SendErrorDetailTag::DnsTimeout.into(),\n                http_req::SendErrorDetail::DnsError(dns_error) => dns_error.into(),\n                http_req::SendErrorDetail::DestinationNotFound => {\n                    SendErrorDetailTag::DestinationNotFound.into()\n                }\n                http_req::SendErrorDetail::DestinationUnavailable => {\n                    SendErrorDetailTag::DestinationUnavailable.into()\n                }\n                http_req::SendErrorDetail::DestinationIpUnroutable => {\n                    SendErrorDetailTag::DestinationIpUnroutable.into()\n                }\n                http_req::SendErrorDetail::ConnectionRefused => {\n                    SendErrorDetailTag::ConnectionRefused.into()\n                }\n                http_req::SendErrorDetail::ConnectionTerminated => {\n                    SendErrorDetailTag::ConnectionTerminated.into()\n                }\n                http_req::SendErrorDetail::ConnectionTimeout => {\n                    SendErrorDetailTag::ConnectionTimeout.into()\n                }\n                http_req::SendErrorDetail::ConnectionLimitReached => {\n                    SendErrorDetailTag::ConnectionLimitReached.into()\n                }\n                http_req::SendErrorDetail::TlsCertificateError => {\n                    SendErrorDetailTag::TlsCertificateError.into()\n                }\n                http_req::SendErrorDetail::TlsConfigurationError => {\n                    SendErrorDetailTag::TlsConfigurationError.into()\n                }\n                http_req::SendErrorDetail::HttpIncompleteResponse => {\n                    SendErrorDetailTag::HttpIncompleteResponse.into()\n                }\n                http_req::SendErrorDetail::HttpResponseHeaderSectionTooLarge => {\n                    SendErrorDetailTag::HttpResponseHeaderSectionTooLarge.into()\n                }\n                http_req::SendErrorDetail::HttpResponseBodyTooLarge => {\n                    SendErrorDetailTag::HttpResponseBodyTooLarge.into()\n                }\n                http_req::SendErrorDetail::HttpResponseTimeout => {\n                    SendErrorDetailTag::HttpResponseTimeout.into()\n                }\n                http_req::SendErrorDetail::HttpResponseStatusInvalid => {\n                    SendErrorDetailTag::HttpResponseStatusInvalid.into()\n                }\n                http_req::SendErrorDetail::HttpUpgradeFailed => {\n                    SendErrorDetailTag::HttpUpgradeFailed.into()\n                }\n                http_req::SendErrorDetail::HttpProtocolError => {\n                    SendErrorDetailTag::HttpProtocolError.into()\n                }\n                http_req::SendErrorDetail::HttpRequestCacheKeyInvalid => {\n                    SendErrorDetailTag::HttpRequestCacheKeyInvalid.into()\n                }\n                http_req::SendErrorDetail::HttpRequestUriInvalid => {\n                    SendErrorDetailTag::HttpRequestUriInvalid.into()\n                }\n                http_req::SendErrorDetail::InternalError => {\n                    SendErrorDetailTag::InternalError.into()\n                }\n                http_req::SendErrorDetail::TlsAlertReceived(tls_alert) => tls_alert.into(),\n                http_req::SendErrorDetail::TlsProtocolError => {\n                    SendErrorDetailTag::TlsProtocolError.into()\n                }\n                http_req::SendErrorDetail::H2Error(h2_error) => h2_error.into(),\n                http_req::SendErrorDetail::Extra(extra) => extra.into(),\n            }\n        }\n    }\n\n    impl From<http_req::DnsErrorDetail> for SendErrorDetail {\n        fn from(dns_error: http_req::DnsErrorDetail) -> Self {\n            let mut mask = SendErrorDetailMask::empty();\n            let mut dns_error_rcode = Default::default();\n            let mut dns_error_info_code = Default::default();\n            if let Some(rcode) = dns_error.rcode {\n                mask |= SendErrorDetailMask::DNS_ERROR_RCODE;\n                dns_error_rcode = rcode;\n            }\n            if let Some(info_code) = dns_error.info_code {\n                mask |= SendErrorDetailMask::DNS_ERROR_INFO_CODE;\n                dns_error_info_code = info_code;\n            }\n            Self {\n                tag: SendErrorDetailTag::DnsError,\n                mask,\n                dns_error_rcode,\n                dns_error_info_code,\n                tls_alert_id: Default::default(),\n                h2_error_frame: Default::default(),\n                h2_error_code: Default::default(),\n            }\n        }\n    }\n\n    impl From<http_req::TlsAlertReceivedDetail> for SendErrorDetail {\n        fn from(tls_alert: http_req::TlsAlertReceivedDetail) -> Self {\n            let mut mask = SendErrorDetailMask::empty();\n            let mut tls_alert_id = Default::default();\n            if let Some(id) = tls_alert.id {\n                mask |= SendErrorDetailMask::TLS_ALERT_ID;\n                tls_alert_id = id;\n            }\n            Self {\n                tag: SendErrorDetailTag::TlsAlertReceived,\n                mask,\n                dns_error_rcode: Default::default(),\n                dns_error_info_code: Default::default(),\n                tls_alert_id,\n                h2_error_frame: Default::default(),\n                h2_error_code: Default::default(),\n            }\n        }\n    }\n\n    impl From<http_req::H2ErrorDetail> for SendErrorDetail {\n        fn from(h2_err: http_req::H2ErrorDetail) -> Self {\n            let mut mask = SendErrorDetailMask::empty();\n            mask |= SendErrorDetailMask::H2_ERROR;\n\n            Self {\n                tag: SendErrorDetailTag::H2Error,\n                mask,\n                dns_error_rcode: Default::default(),\n                dns_error_info_code: Default::default(),\n                tls_alert_id: Default::default(),\n                h2_error_code: h2_err.error_code,\n                h2_error_frame: h2_err.frame_type,\n            }\n        }\n    }\n\n    impl From<http_req::ExtraSendErrorDetail> for SendErrorDetail {\n        fn from(_: http_req::ExtraSendErrorDetail) -> Self {\n            // We haven't implemented any extra send error types yet, so for now\n            // we just always claim that it's an internal error, since any\n            // occurrences of it would be an adapter bug.\n            Self {\n                tag: SendErrorDetailTag::InternalError,\n                mask: SendErrorDetailMask::empty(),\n                dns_error_rcode: Default::default(),\n                dns_error_info_code: Default::default(),\n                tls_alert_id: Default::default(),\n                h2_error_code: Default::default(),\n                h2_error_frame: Default::default(),\n            }\n        }\n    }\n\n    impl From<SendErrorDetailTag> for SendErrorDetail {\n        fn from(tag: SendErrorDetailTag) -> Self {\n            Self {\n                tag,\n                mask: SendErrorDetailMask::empty(),\n                dns_error_rcode: Default::default(),\n                dns_error_info_code: Default::default(),\n                tls_alert_id: Default::default(),\n                h2_error_frame: Default::default(),\n                h2_error_code: Default::default(),\n            }\n        }\n    }\n\n    fn encode_tls_version(val: u32) -> Result<http_types::TlsVersion, ()> {\n        match val {\n            0 => Ok(0x0301), // TLS 1.0\n            1 => Ok(0x0302), // TLS 1.1\n            2 => Ok(0x0303), // TLS 1.2\n            3 => Ok(0x0304), // TLS 1.3\n            _ => Err(()),\n        }\n    }\n\n    #[export_name = \"fastly_http_req#body_downstream_get\"]\n    pub fn body_downstream_get(\n        req_handle_out: *mut RequestHandle,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        crate::State::with::<FastlyStatus>(|state| {\n            unsafe {\n                *main_ptr!(req_handle_out) = state.request.get().trapping_unwrap();\n                *main_ptr!(body_handle_out) = state.request_body.get().trapping_unwrap();\n            }\n            Ok(())\n        })\n    }\n\n    #[export_name = \"fastly_http_req#cache_override_set\"]\n    pub fn cache_override_set(\n        req_handle: RequestHandle,\n        tag: u32,\n        ttl: u32,\n        swr: u32,\n    ) -> FastlyStatus {\n        let tag = ManuallyDrop::new(fastly::compute::http_req::CacheOverride::from((\n            tag, ttl, swr, None,\n        )));\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.set_cache_override(&tag))\n    }\n\n    #[export_name = \"fastly_http_req#cache_override_v2_set\"]\n    pub fn cache_override_v2_set(\n        req_handle: RequestHandle,\n        tag: u32,\n        ttl: u32,\n        swr: u32,\n        sk: *const u8,\n        sk_len: usize,\n    ) -> FastlyStatus {\n        let sk = make_vec!(unsafe_main_ptr!(sk), sk_len);\n        let tag = ManuallyDrop::new(fastly::compute::http_req::CacheOverride::from((\n            tag,\n            ttl,\n            swr,\n            Some(sk),\n        )));\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.set_cache_override(&tag))\n    }\n\n    #[export_name = \"fastly_http_req#framing_headers_mode_set\"]\n    pub fn framing_headers_mode_set(\n        req_handle: RequestHandle,\n        mode: FramingHeadersMode,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        let mode = match mode {\n            FramingHeadersMode::Automatic => {\n                fastly::compute::http_types::FramingHeadersMode::Automatic\n            }\n            FramingHeadersMode::ManuallyFromHeaders => {\n                fastly::compute::http_types::FramingHeadersMode::ManuallyFromHeaders\n            }\n        };\n\n        convert_result(req_handle.set_framing_headers_mode(mode))\n    }\n\n    #[export_name = \"fastly_http_req#downstream_tls_cipher_openssl_name\"]\n    pub fn downstream_tls_cipher_openssl_name(\n        cipher_out: *mut u8,\n        cipher_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        alloc_result_opt!(\n            unsafe_main_ptr!(cipher_out),\n            cipher_max_len,\n            main_ptr!(nwritten),\n            {\n                adapter_http_req::downstream_tls_cipher_openssl_name(\n                    u64::try_from(cipher_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_req#downstream_tls_protocol\"]\n    pub fn downstream_tls_protocol(\n        protocol_out: *mut u8,\n        protocol_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        alloc_result_opt!(\n            unsafe_main_ptr!(protocol_out),\n            protocol_max_len,\n            main_ptr!(nwritten),\n            {\n                adapter_http_req::downstream_tls_protocol(\n                    u64::try_from(protocol_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_req#downstream_tls_raw_client_certificate\"]\n    pub fn downstream_tls_raw_client_certificate(\n        client_certificate_out: *mut u8,\n        client_certificate_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        alloc_result_opt!(\n            unsafe_main_ptr!(client_certificate_out),\n            client_certificate_max_len,\n            main_ptr!(nwritten),\n            {\n                adapter_http_req::downstream_tls_raw_client_certificate_deprecated(\n                    u64::try_from(client_certificate_max_len).trapping_unwrap(),\n                )\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_req#header_append\"]\n    pub fn header_append(\n        req_handle: RequestHandle,\n        name: *const u8,\n        name_len: usize,\n        value: *const u8,\n        value_len: usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let value = unsafe { slice::from_raw_parts(main_ptr!(value), value_len) };\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.append_header(name, value))\n    }\n\n    #[export_name = \"fastly_http_req#header_insert\"]\n    pub fn header_insert(\n        req_handle: RequestHandle,\n        name: *const u8,\n        name_len: usize,\n        value: *const u8,\n        value_len: usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let value = unsafe { slice::from_raw_parts(main_ptr!(value), value_len) };\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.insert_header(name, value))\n    }\n\n    #[export_name = \"fastly_http_req#original_header_names_get\"]\n    pub fn original_header_names_get(\n        buf: *mut u8,\n        buf_len: usize,\n        cursor: u32,\n        ending_cursor: *mut i64,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        with_buffer!(\n            unsafe_main_ptr!(buf),\n            buf_len,\n            {\n                adapter_http_req::get_original_header_names(\n                    u64::try_from(buf_len).trapping_unwrap(),\n                    cursor,\n                )\n            },\n            |res| {\n                let (bytes, next) = handle_buffer_len!(res, main_ptr!(nwritten));\n                let written = bytes.len();\n                let end = match next {\n                    Some(next) => i64::from(next),\n                    None => -1,\n                };\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                    *main_ptr!(ending_cursor) = end;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_req#original_header_count\"]\n    pub fn original_header_count(count_out: *mut u32) -> FastlyStatus {\n        match adapter_http_req::original_header_count() {\n            Ok(count) => {\n                unsafe {\n                    *main_ptr!(count_out) = count;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_req#header_names_get\"]\n    pub fn header_names_get(\n        req_handle: RequestHandle,\n        buf: *mut u8,\n        buf_len: usize,\n        cursor: u32,\n        ending_cursor: *mut i64,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(buf),\n            buf_len,\n            { req_handle.get_header_names(u64::try_from(buf_len).trapping_unwrap(), cursor,) },\n            |res| {\n                let (bytes, next) = handle_buffer_len!(res, main_ptr!(nwritten));\n                let written = bytes.len();\n                let end = match next {\n                    Some(next) => i64::from(next),\n                    None => -1,\n                };\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                    *main_ptr!(ending_cursor) = end;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_req#header_values_get\"]\n    pub fn header_values_get(\n        req_handle: RequestHandle,\n        name: *const u8,\n        name_len: usize,\n        buf: *mut u8,\n        buf_len: usize,\n        cursor: u32,\n        ending_cursor: *mut i64,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(buf),\n            buf_len,\n            {\n                req_handle.get_header_values(\n                    unsafe { slice::from_raw_parts(main_ptr!(name), name_len) },\n                    u64::try_from(buf_len).trapping_unwrap(),\n                    cursor,\n                )\n            },\n            |res| {\n                let (bytes, next) = handle_buffer_len!(res, main_ptr!(nwritten));\n                let written = bytes.len();\n                let end = match next {\n                    Some(next) => i64::from(next),\n                    None => -1,\n                };\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                    *main_ptr!(ending_cursor) = end;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_req#header_values_set\"]\n    pub fn header_values_set(\n        req_handle: RequestHandle,\n        name: *const u8,\n        name_len: usize,\n        values: *const u8,\n        values_len: usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.set_header_values(\n            unsafe { slice::from_raw_parts(main_ptr!(name), name_len) },\n            unsafe { slice::from_raw_parts(main_ptr!(values), values_len) },\n        ))\n    }\n\n    #[export_name = \"fastly_http_req#header_value_get\"]\n    pub fn header_value_get(\n        req_handle: RequestHandle,\n        name: *const u8,\n        name_len: usize,\n        value: *mut u8,\n        value_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(value),\n            value_max_len,\n            { req_handle.get_header_value(name, u64::try_from(value_max_len).trapping_unwrap(),) },\n            |res| {\n                let res = handle_buffer_len!(res, main_ptr!(nwritten))\n                    .ok_or(FastlyStatus::INVALID_ARGUMENT)?;\n                unsafe {\n                    *main_ptr!(nwritten) = res.len();\n                }\n\n                std::mem::forget(res);\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_req#header_remove\"]\n    pub fn header_remove(\n        req_handle: RequestHandle,\n        name: *const u8,\n        name_len: usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.remove_header(name))\n    }\n\n    #[export_name = \"fastly_http_req#method_get\"]\n    pub fn method_get(\n        req_handle: RequestHandle,\n        method: *mut u8,\n        method_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result!(\n            unsafe_main_ptr!(method),\n            method_max_len,\n            main_ptr!(nwritten),\n            { req_handle.get_method(u64::try_from(method_max_len).trapping_unwrap()) }\n        )\n    }\n\n    #[export_name = \"fastly_http_req#method_set\"]\n    pub fn method_set(\n        req_handle: RequestHandle,\n        method: *const u8,\n        method_len: usize,\n    ) -> FastlyStatus {\n        let method = unsafe { slice::from_raw_parts(main_ptr!(method), method_len) };\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.set_method(method))\n    }\n\n    #[export_name = \"fastly_http_req#new\"]\n    pub fn new(req_handle_out: *mut RequestHandle) -> FastlyStatus {\n        match fastly::compute::http_req::Request::new() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(req_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_req#send\"]\n    pub fn send(\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend: *const u8,\n        backend_len: usize,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let body_handle = unsafe { fastly::compute::http_body::Body::from_handle(body_handle) };\n        let req_handle = unsafe { http_req::Request::from_handle(req_handle) };\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match http_req::send(req_handle, body_handle, &backend) {\n            Ok((resp_handle, resp_body_handle)) => {\n                unsafe {\n                    *main_ptr!(resp_handle_out) = resp_handle.take_handle();\n                    *main_ptr!(resp_body_handle_out) = resp_body_handle.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(e) => e.error.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_req#send_v2\"]\n    pub fn send_v2(\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend: *const u8,\n        backend_len: usize,\n        error_detail: *mut SendErrorDetail,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let body_handle = unsafe { fastly::compute::http_body::Body::from_handle(body_handle) };\n        let req_handle = unsafe { http_req::Request::from_handle(req_handle) };\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => {\n                unsafe {\n                    *main_ptr!(error_detail) = SendErrorDetailTag::DestinationNotFound.into();\n                    *main_ptr!(resp_handle_out) = INVALID_HANDLE;\n                    *main_ptr!(resp_body_handle_out) = INVALID_HANDLE;\n                }\n                return convert_result(Err(err));\n            }\n        };\n        match http_req::send(req_handle, body_handle, &backend) {\n            Ok((resp_handle, resp_body_handle)) => {\n                unsafe {\n                    *main_ptr!(error_detail) = SendErrorDetailTag::Ok.into();\n                    *main_ptr!(resp_handle_out) = resp_handle.take_handle();\n                    *main_ptr!(resp_body_handle_out) = resp_body_handle.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(err) => {\n                unsafe {\n                    *main_ptr!(error_detail) = err\n                        .detail\n                        .map(Into::into)\n                        .unwrap_or_else(|| SendErrorDetailTag::Uninitialized.into());\n                    *main_ptr!(resp_handle_out) = INVALID_HANDLE;\n                    *main_ptr!(resp_body_handle_out) = INVALID_HANDLE;\n                }\n\n                err.error.into()\n            }\n        }\n    }\n\n    #[export_name = \"fastly_http_req#send_v3\"]\n    pub fn send_v3(\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend: *const u8,\n        backend_len: usize,\n        error_detail: *mut SendErrorDetail,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let body_handle = unsafe { fastly::compute::http_body::Body::from_handle(body_handle) };\n        let req_handle = unsafe { http_req::Request::from_handle(req_handle) };\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => {\n                unsafe {\n                    *main_ptr!(error_detail) = SendErrorDetailTag::DestinationNotFound.into();\n                    *main_ptr!(resp_handle_out) = INVALID_HANDLE;\n                    *main_ptr!(resp_body_handle_out) = INVALID_HANDLE;\n                }\n                return convert_result(Err(err));\n            }\n        };\n        match http_req::send_uncached(req_handle, body_handle, &backend) {\n            Ok((resp_handle, resp_body_handle)) => {\n                unsafe {\n                    *main_ptr!(error_detail) = SendErrorDetailTag::Ok.into();\n                    *main_ptr!(resp_handle_out) = resp_handle.take_handle();\n                    *main_ptr!(resp_body_handle_out) = resp_body_handle.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(err) => {\n                unsafe {\n                    *main_ptr!(error_detail) = err\n                        .detail\n                        .map(Into::into)\n                        .unwrap_or_else(|| SendErrorDetailTag::Uninitialized.into());\n                    *main_ptr!(resp_handle_out) = INVALID_HANDLE;\n                    *main_ptr!(resp_body_handle_out) = INVALID_HANDLE;\n                }\n\n                err.error.into()\n            }\n        }\n    }\n\n    #[export_name = \"fastly_http_req#send_async\"]\n    pub fn send_async(\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend: *const u8,\n        backend_len: usize,\n        pending_req_handle_out: *mut PendingRequestHandle,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let body_handle = unsafe { fastly::compute::http_body::Body::from_handle(body_handle) };\n        let req_handle = unsafe { http_req::Request::from_handle(req_handle) };\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match http_req::send_async(req_handle, body_handle, &backend) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(pending_req_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_req#send_async_v2\"]\n    pub fn send_async_v2(\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend: *const u8,\n        backend_len: usize,\n        streaming: u32,\n        pending_req_handle_out: *mut PendingRequestHandle,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let req_handle = unsafe { http_req::Request::from_handle(req_handle) };\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        let res = if streaming == 0 {\n            let body_handle = unsafe { fastly::compute::http_body::Body::from_handle(body_handle) };\n            http_req::send_async_uncached(req_handle, body_handle, &backend)\n        } else {\n            let body_handle = ManuallyDrop::new(unsafe {\n                fastly::compute::http_body::Body::from_handle(body_handle)\n            });\n            http_req::send_async_uncached_streaming(req_handle, &body_handle, &backend)\n        };\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(pending_req_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_req#send_async_streaming\"]\n    pub fn send_async_streaming(\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        backend: *const u8,\n        backend_len: usize,\n        pending_req_handle_out: *mut PendingRequestHandle,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let body_handle = ManuallyDrop::new(unsafe {\n            fastly::compute::http_body::Body::from_handle(body_handle)\n        });\n        let req_handle = unsafe { http_req::Request::from_handle(req_handle) };\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match http_req::send_async_streaming(req_handle, &body_handle, &backend) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(pending_req_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_req#upgrade_websocket\"]\n    pub fn upgrade_websocket(backend: *const u8, backend_len: usize) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        convert_result(http_req::upgrade_websocket(&backend))\n    }\n\n    #[export_name = \"fastly_http_req#redirect_to_websocket_proxy_v2\"]\n    pub fn redirect_to_websocket_proxy_v2(\n        req: RequestHandle,\n        backend: *const u8,\n        backend_len: usize,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let req = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req) });\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        convert_result(req.redirect_to_websocket_proxy(&backend))\n    }\n\n    #[export_name = \"fastly_http_req#redirect_to_grip_proxy_v2\"]\n    pub fn redirect_to_grip_proxy_v2(\n        req: RequestHandle,\n        backend: *const u8,\n        backend_len: usize,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend), backend_len);\n        let req = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req) });\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        convert_result(req.redirect_to_grip_proxy(&backend))\n    }\n\n    #[export_name = \"fastly_http_req#register_dynamic_backend\"]\n    pub fn register_dynamic_backend(\n        name_prefix: *const u8,\n        name_prefix_len: usize,\n        target: *const u8,\n        target_len: usize,\n        config_mask: BackendConfigOptions,\n        config: *const DynamicBackendConfig,\n    ) -> FastlyStatus {\n        let name_prefix = crate::make_str!(unsafe_main_ptr!(name_prefix), name_prefix_len);\n        let target = crate::make_str!(unsafe_main_ptr!(target), target_len);\n        let config = unsafe_main_ptr!(config);\n\n        macro_rules! make_str {\n            ($ptr_field:ident, $len_field:ident) => {\n                unsafe { crate::make_str!(main_ptr!((*config).$ptr_field), (*config).$len_field) }\n            };\n        }\n\n        let host_override = make_str!(host_override, host_override_len);\n        let cert_hostname = make_str!(cert_hostname, cert_hostname_len);\n        let ca_cert = make_str!(ca_cert, ca_cert_len);\n        let ciphers = make_str!(ciphers, ciphers_len);\n        let sni_hostname = make_str!(sni_hostname, sni_hostname_len);\n        let client_cert = make_str!(client_certificate, client_certificate_len);\n\n        let tls_version_min = match encode_tls_version(unsafe { (*config).ssl_min_version }) {\n            Ok(tls_version_min) => tls_version_min,\n            Err(_) => return FastlyStatus::INVALID_ARGUMENT,\n        };\n        let tls_version_max = match encode_tls_version(unsafe { (*config).ssl_max_version }) {\n            Ok(tls_version_max) => tls_version_max,\n            Err(_) => return FastlyStatus::INVALID_ARGUMENT,\n        };\n\n        let builder = backend::DynamicBackendOptions::new();\n        if config_mask.contains(BackendConfigOptions::HOST_OVERRIDE) {\n            builder.override_host(host_override);\n        }\n        if config_mask.contains(BackendConfigOptions::CONNECT_TIMEOUT) {\n            builder.connect_timeout(unsafe { (*config).connect_timeout_ms });\n        }\n        if config_mask.contains(BackendConfigOptions::FIRST_BYTE_TIMEOUT) {\n            builder.first_byte_timeout(unsafe { (*config).first_byte_timeout_ms });\n        }\n        if config_mask.contains(BackendConfigOptions::BETWEEN_BYTES_TIMEOUT) {\n            builder.between_bytes_timeout(unsafe { (*config).between_bytes_timeout_ms });\n        }\n        if config_mask.contains(BackendConfigOptions::USE_TLS) {\n            builder.use_tls(true);\n        }\n        if config_mask.contains(BackendConfigOptions::TLS_MIN_VERSION) {\n            builder.tls_min_version(tls_version_min);\n        }\n        if config_mask.contains(BackendConfigOptions::TLS_MAX_VERSION) {\n            builder.tls_max_version(tls_version_max);\n        }\n        if config_mask.contains(BackendConfigOptions::CERT_HOSTNAME) {\n            builder.cert_hostname(cert_hostname);\n        }\n        if config_mask.contains(BackendConfigOptions::CA_CERT) {\n            builder.ca_certificate(ca_cert);\n        }\n        if config_mask.contains(BackendConfigOptions::CIPHERS) {\n            builder.tls_ciphers(ciphers);\n        }\n        if config_mask.contains(BackendConfigOptions::SNI_HOSTNAME) {\n            builder.sni_hostname(sni_hostname);\n        }\n        if config_mask.contains(BackendConfigOptions::CLIENT_CERT) {\n            let client_key =\n                unsafe { fastly::compute::secret_store::Secret::from_handle((*config).client_key) };\n            builder.client_cert(client_cert, &client_key);\n            core::mem::forget(client_key);\n        }\n        if config_mask.contains(BackendConfigOptions::KEEPALIVE) {\n            builder.http_keepalive_time_ms(unsafe { (*config).http_keepalive_time_ms });\n            builder.tcp_keepalive_enable(unsafe { (*config).tcp_keepalive_enable });\n            builder.tcp_keepalive_interval_secs(unsafe { (*config).tcp_keepalive_interval_secs });\n            builder.tcp_keepalive_probes(unsafe { (*config).tcp_keepalive_probes });\n            builder.tcp_keepalive_time_secs(unsafe { (*config).tcp_keepalive_time_secs });\n        }\n        if config_mask.contains(BackendConfigOptions::POOLING_LIMITS) {\n            builder.max_connections(unsafe { (*config).max_connections });\n            builder.max_use(unsafe { (*config).max_use });\n            builder.max_lifetime_ms(unsafe { (*config).max_lifetime_ms });\n        }\n        if config_mask.contains(BackendConfigOptions::DONT_POOL) {\n            builder.pooling(false);\n        }\n        if config_mask.contains(BackendConfigOptions::PREFER_IPV4) {\n            builder.prefer_ipv6(false);\n        }\n        if config_mask.contains(BackendConfigOptions::GRPC) {\n            builder.grpc(true);\n        }\n\n        let res = backend::register_dynamic_backend(name_prefix, target, builder);\n        let res = res.map(|_backend| ());\n\n        convert_result(res)\n    }\n\n    #[export_name = \"fastly_http_req#uri_get\"]\n    pub fn uri_get(\n        req_handle: RequestHandle,\n        uri: *mut u8,\n        uri_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        alloc_result!(unsafe_main_ptr!(uri), uri_max_len, main_ptr!(nwritten), {\n            req_handle.get_uri(u64::try_from(uri_max_len).trapping_unwrap())\n        })\n    }\n\n    #[export_name = \"fastly_http_req#uri_set\"]\n    pub fn uri_set(req_handle: RequestHandle, uri: *const u8, uri_len: usize) -> FastlyStatus {\n        let uri = unsafe { slice::from_raw_parts(main_ptr!(uri), uri_len) };\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.set_uri(uri))\n    }\n\n    #[export_name = \"fastly_http_req#version_get\"]\n    pub fn version_get(req_handle: RequestHandle, version: *mut u32) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match req_handle.get_version() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(version) = res.into();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_req#version_set\"]\n    pub fn version_set(req_handle: RequestHandle, version: u32) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        match http_types::HttpVersion::try_from(version) {\n            Ok(version) => convert_result(req_handle.set_version(version)),\n\n            Err(_) => FastlyStatus::INVALID_ARGUMENT,\n        }\n    }\n\n    #[export_name = \"fastly_http_req#pending_req_poll\"]\n    pub fn pending_req_poll(\n        pending_req_handle: PendingRequestHandle,\n        is_done_out: *mut i32,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let wit_handle = ManuallyDrop::new(unsafe {\n            http_req::PendingResponse::from_handle(pending_req_handle)\n        });\n        if wit_handle.is_ready() {\n            unsafe {\n                *main_ptr!(is_done_out) = 1;\n            }\n            pending_req_wait(pending_req_handle, resp_handle_out, resp_body_handle_out)\n        } else {\n            unsafe {\n                *main_ptr!(is_done_out) = 0;\n                *main_ptr!(resp_handle_out) = INVALID_HANDLE;\n                *main_ptr!(resp_body_handle_out) = INVALID_HANDLE;\n            }\n            FastlyStatus::OK\n        }\n    }\n\n    #[export_name = \"fastly_http_req#pending_req_poll_v2\"]\n    pub fn pending_req_poll_v2(\n        pending_req_handle: PendingRequestHandle,\n        error_detail: *mut SendErrorDetail,\n        is_done_out: *mut i32,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let wit_handle = ManuallyDrop::new(unsafe {\n            http_req::PendingResponse::from_handle(pending_req_handle)\n        });\n        if wit_handle.is_ready() {\n            let status = pending_req_wait_v2(\n                pending_req_handle,\n                error_detail,\n                resp_handle_out,\n                resp_body_handle_out,\n            );\n            unsafe {\n                *main_ptr!(is_done_out) =\n                    i32::from((*main_ptr!(error_detail)).tag == SendErrorDetailTag::Ok);\n            }\n            status\n        } else {\n            unsafe {\n                *main_ptr!(is_done_out) = 0;\n                *main_ptr!(resp_handle_out) = INVALID_HANDLE;\n                *main_ptr!(resp_body_handle_out) = INVALID_HANDLE;\n            }\n            FastlyStatus::OK\n        }\n    }\n\n    #[export_name = \"fastly_http_req#pending_req_select\"]\n    pub fn pending_req_select(\n        pending_req_handles: *const PendingRequestHandle,\n        pending_req_handles_len: usize,\n        done_index_out: *mut i32,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        // `http-req.select-request` traps if there are no handles or too many handles; this\n        // check preserves the witx `pending-req-select` behavior.\n        if pending_req_handles_len == 0\n            || pending_req_handles_len >= fastly_shared::MAX_PENDING_REQS as usize\n        {\n            return FastlyStatus::INVALID_ARGUMENT;\n        }\n        unsafe {\n            let reqs =\n                slice::from_raw_parts(main_ptr!(pending_req_handles), pending_req_handles_len);\n            let idx = select_wrapper(reqs);\n            let status = pending_req_wait(\n                *main_ptr!(pending_req_handles.add(idx as usize)),\n                resp_handle_out,\n                resp_body_handle_out,\n            );\n            *main_ptr!(done_index_out) = idx as i32;\n            status\n        }\n    }\n\n    #[export_name = \"fastly_http_req#pending_req_select_v2\"]\n    pub fn pending_req_select_v2(\n        pending_req_handles: *const PendingRequestHandle,\n        pending_req_handles_len: usize,\n        error_detail: *mut SendErrorDetail,\n        done_index_out: *mut i32,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        // `http-req.select-request` traps if there are no handles or too many handles; this\n        // check preserves the witx `pending-req-select` behavior.\n        if pending_req_handles_len == 0\n            || pending_req_handles_len >= fastly_shared::MAX_PENDING_REQS as usize\n        {\n            return FastlyStatus::INVALID_ARGUMENT;\n        }\n        unsafe {\n            let reqs =\n                slice::from_raw_parts(main_ptr!(pending_req_handles), pending_req_handles_len);\n            let idx = select_wrapper(reqs);\n            pending_req_wait_v2(\n                *main_ptr!(pending_req_handles.add(idx as usize)),\n                error_detail,\n                resp_handle_out,\n                resp_body_handle_out,\n            );\n            *main_ptr!(done_index_out) = idx as i32;\n        }\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_http_req#pending_req_wait\"]\n    pub fn pending_req_wait(\n        pending_req_handle: PendingRequestHandle,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let pending_resp_handle =\n            unsafe { http_req::PendingResponse::from_handle(pending_req_handle) };\n        match http_req::await_response(pending_resp_handle) {\n            Ok((resp, body)) => unsafe {\n                *main_ptr!(resp_handle_out) = resp.take_handle();\n                *main_ptr!(resp_body_handle_out) = body.take_handle();\n                FastlyStatus::OK\n            },\n\n            Err(e) => unsafe {\n                *main_ptr!(resp_handle_out) = INVALID_HANDLE;\n                *main_ptr!(resp_body_handle_out) = INVALID_HANDLE;\n                e.error.into()\n            },\n        }\n    }\n\n    #[export_name = \"fastly_http_req#pending_req_wait_v2\"]\n    pub fn pending_req_wait_v2(\n        pending_req_handle: PendingRequestHandle,\n        error_detail: *mut SendErrorDetail,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let pending_resp_handle =\n            unsafe { http_req::PendingResponse::from_handle(pending_req_handle) };\n        match http_req::await_response(pending_resp_handle) {\n            Ok((resp_handle, resp_body_handle)) => unsafe {\n                *main_ptr!(error_detail) = SendErrorDetailTag::Ok.into();\n                *main_ptr!(resp_handle_out) = resp_handle.take_handle();\n                *main_ptr!(resp_body_handle_out) = resp_body_handle.take_handle();\n                FastlyStatus::OK\n            },\n            Err(e) => unsafe {\n                if let Some(detail) = e.detail {\n                    *main_ptr!(error_detail) = detail.into();\n                } else {\n                    *main_ptr!(error_detail) = SendErrorDetailTag::Uninitialized.into();\n                }\n                *main_ptr!(resp_handle_out) = INVALID_HANDLE;\n                *main_ptr!(resp_body_handle_out) = INVALID_HANDLE;\n                e.error.into()\n            },\n        }\n    }\n\n    #[export_name = \"fastly_http_req#close\"]\n    pub fn close(req_handle: RequestHandle) -> FastlyStatus {\n        let req_handle = unsafe { http_req::Request::from_handle(req_handle) };\n        convert_result(http_req::close(req_handle))\n    }\n\n    #[export_name = \"fastly_http_req#auto_decompress_response_set\"]\n    pub fn auto_decompress_response_set(\n        req_handle: RequestHandle,\n        encodings: ContentEncodings,\n    ) -> FastlyStatus {\n        let req_handle = ManuallyDrop::new(unsafe { http_req::Request::from_handle(req_handle) });\n        convert_result(req_handle.set_auto_decompress_response(encodings.into()))\n    }\n\n    #[export_name = \"fastly_http_req#inspect\"]\n    pub fn inspect(\n        ds_req: RequestHandle,\n        ds_body: BodyHandle,\n        info_mask: InspectConfigOptions,\n        info: *mut InspectConfig,\n        buf: *mut u8,\n        buf_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let info = unsafe_main_ptr!(info);\n        macro_rules! make_string {\n            ($ptr_field:ident, $len_field:ident) => {\n                unsafe { crate::make_string!(main_ptr!((*info).$ptr_field), (*info).$len_field) }\n            };\n        }\n\n        let corp = if info_mask.contains(InspectConfigOptions::CORP) {\n            Some(ManuallyDrop::into_inner(make_string!(corp, corp_len)))\n        } else {\n            None\n        };\n        let workspace = if info_mask.contains(InspectConfigOptions::WORKSPACE) {\n            Some(ManuallyDrop::into_inner(make_string!(\n                workspace,\n                workspace_len\n            )))\n        } else {\n            None\n        };\n        let override_client_ip = if info_mask.contains(InspectConfigOptions::OVERRIDE_CLIENT_IP) {\n            unsafe {\n                decode_ip_address(\n                    main_ptr!((*info).override_client_ip_ptr),\n                    (*info).override_client_ip_len as usize,\n                )\n            }\n        } else {\n            None\n        };\n        let options = security::InspectOptions {\n            corp,\n            workspace,\n            override_client_ip,\n            extra: None,\n        };\n\n        let ds_body =\n            ManuallyDrop::new(unsafe { fastly::compute::http_body::Body::from_handle(ds_body) });\n        let ds_req = ManuallyDrop::new(unsafe { http_req::Request::from_handle(ds_req) });\n\n        let res = alloc_result!(unsafe_main_ptr!(buf), buf_len, main_ptr!(nwritten_out), {\n            security::inspect(\n                &ds_req,\n                &ds_body,\n                &options,\n                u64::try_from(buf_len).trapping_unwrap(),\n            )\n        });\n\n        std::mem::forget(options);\n\n        res\n    }\n\n    #[export_name = \"fastly_http_req#on_behalf_of\"]\n    pub fn on_behalf_of(\n        _request_handle: RequestHandle,\n        _service: *const u8,\n        _service_len: usize,\n    ) -> FastlyStatus {\n        FastlyStatus::UNSUPPORTED\n    }\n}\n\npub mod fastly_http_resp {\n    use core::slice;\n\n    use super::*;\n    use crate::bindings::fastly::{self, compute::http_resp};\n    use crate::fastly::encode_ip_address;\n\n    #[export_name = \"fastly_http_resp#header_append\"]\n    pub fn header_append(\n        resp_handle: ResponseHandle,\n        name: *const u8,\n        name_len: usize,\n        value: *const u8,\n        value_len: usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let value = unsafe { slice::from_raw_parts(main_ptr!(value), value_len) };\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        convert_result(resp_handle.append_header(name, value))\n    }\n\n    #[export_name = \"fastly_http_resp#header_insert\"]\n    pub fn header_insert(\n        resp_handle: ResponseHandle,\n        name: *const u8,\n        name_len: usize,\n        value: *const u8,\n        value_len: usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let value = unsafe { slice::from_raw_parts(main_ptr!(value), value_len) };\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        convert_result(resp_handle.insert_header(name, value))\n    }\n\n    #[export_name = \"fastly_http_resp#header_names_get\"]\n    pub fn header_names_get(\n        resp_handle: ResponseHandle,\n        buf: *mut u8,\n        buf_len: usize,\n        cursor: u32,\n        ending_cursor: *mut i64,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(buf),\n            buf_len,\n            { resp_handle.get_header_names(u64::try_from(buf_len).trapping_unwrap(), cursor,) },\n            |res| {\n                let (bytes, next) = handle_buffer_len!(res, main_ptr!(nwritten));\n                let written = bytes.len();\n                let end = match next {\n                    Some(next) => i64::from(next),\n                    None => -1,\n                };\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                    *main_ptr!(ending_cursor) = end;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_resp#header_value_get\"]\n    pub fn header_value_get(\n        resp_handle: ResponseHandle,\n        name: *const u8,\n        name_len: usize,\n        value: *mut u8,\n        value_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(value),\n            value_max_len,\n            { resp_handle.get_header_value(name, u64::try_from(value_max_len).trapping_unwrap(),) },\n            |res| {\n                let res = handle_buffer_len!(res, main_ptr!(nwritten))\n                    .ok_or(FastlyStatus::INVALID_ARGUMENT)?;\n                unsafe {\n                    *main_ptr!(nwritten) = res.len();\n                }\n\n                std::mem::forget(res);\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_resp#header_values_get\"]\n    pub fn header_values_get(\n        resp_handle: ResponseHandle,\n        name: *const u8,\n        name_len: usize,\n        buf: *mut u8,\n        buf_len: usize,\n        cursor: u32,\n        ending_cursor: *mut i64,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        with_buffer!(\n            unsafe_main_ptr!(buf),\n            buf_len,\n            {\n                resp_handle.get_header_values(\n                    name,\n                    u64::try_from(buf_len).trapping_unwrap(),\n                    cursor,\n                )\n            },\n            |res| {\n                let (bytes, next) = handle_buffer_len!(res, main_ptr!(nwritten));\n                let written = bytes.len();\n                let end = match next {\n                    Some(next) => i64::from(next),\n                    None => -1,\n                };\n\n                std::mem::forget(bytes);\n\n                unsafe {\n                    *main_ptr!(nwritten) = written;\n                    *main_ptr!(ending_cursor) = end;\n                }\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_resp#header_values_set\"]\n    pub fn header_values_set(\n        resp_handle: ResponseHandle,\n        name: *const u8,\n        name_len: usize,\n        values: *const u8,\n        values_len: usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let values = unsafe { slice::from_raw_parts(main_ptr!(values), values_len) };\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        convert_result(resp_handle.set_header_values(name, values))\n    }\n\n    #[export_name = \"fastly_http_resp#header_remove\"]\n    pub fn header_remove(\n        resp_handle: ResponseHandle,\n        name: *const u8,\n        name_len: usize,\n    ) -> FastlyStatus {\n        let name = unsafe { slice::from_raw_parts(main_ptr!(name), name_len) };\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        convert_result(resp_handle.remove_header(name))\n    }\n\n    #[export_name = \"fastly_http_resp#new\"]\n    pub fn new(handle_out: *mut ResponseHandle) -> FastlyStatus {\n        match http_resp::Response::new() {\n            Ok(handle) => {\n                unsafe {\n                    *main_ptr!(handle_out) = handle.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_resp#send_downstream\"]\n    pub fn send_downstream(\n        resp_handle: ResponseHandle,\n        body_handle: BodyHandle,\n        streaming: u32,\n    ) -> FastlyStatus {\n        let resp_handle = unsafe { http_resp::Response::from_handle(resp_handle) };\n        let res = if streaming == 0 {\n            let body_handle = unsafe { fastly::compute::http_body::Body::from_handle(body_handle) };\n            http_resp::send_downstream(resp_handle, body_handle)\n        } else {\n            let body_handle = ManuallyDrop::new(unsafe {\n                fastly::compute::http_body::Body::from_handle(body_handle)\n            });\n            http_resp::send_downstream_streaming(resp_handle, &body_handle)\n        };\n        convert_result(res)\n    }\n\n    #[export_name = \"fastly_http_resp#status_get\"]\n    pub fn status_get(resp_handle: ResponseHandle, status: *mut u16) -> FastlyStatus {\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        match resp_handle.get_status() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(status) = res;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_resp#status_set\"]\n    pub fn status_set(resp_handle: ResponseHandle, status: u16) -> FastlyStatus {\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        convert_result(resp_handle.set_status(status))\n    }\n\n    #[export_name = \"fastly_http_resp#version_get\"]\n    pub fn version_get(resp_handle: ResponseHandle, version: *mut u32) -> FastlyStatus {\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        match resp_handle.get_version() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(version) = res.into();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_resp#version_set\"]\n    pub fn version_set(resp_handle: ResponseHandle, version: u32) -> FastlyStatus {\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        match crate::bindings::fastly::compute::http_types::HttpVersion::try_from(version) {\n            Ok(version) => convert_result(resp_handle.set_version(version)),\n\n            Err(_) => FastlyStatus::INVALID_ARGUMENT,\n        }\n    }\n\n    #[export_name = \"fastly_http_resp#framing_headers_mode_set\"]\n    pub fn framing_headers_mode_set(\n        resp_handle: ResponseHandle,\n        mode: FramingHeadersMode,\n    ) -> FastlyStatus {\n        let mode = match mode {\n            FramingHeadersMode::Automatic => {\n                fastly::compute::http_types::FramingHeadersMode::Automatic\n            }\n            FramingHeadersMode::ManuallyFromHeaders => {\n                fastly::compute::http_types::FramingHeadersMode::ManuallyFromHeaders\n            }\n        };\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n\n        convert_result(resp_handle.set_framing_headers_mode(mode))\n    }\n\n    #[doc(hidden)]\n    #[export_name = \"fastly_http_resp#http_keepalive_mode_set\"]\n    pub fn http_keepalive_mode_set(\n        resp_handle: ResponseHandle,\n        mode: HttpKeepaliveMode,\n    ) -> FastlyStatus {\n        let mode = match mode {\n            HttpKeepaliveMode::Automatic => fastly::compute::http_resp::KeepaliveMode::Automatic,\n            HttpKeepaliveMode::NoKeepalive => {\n                fastly::compute::http_resp::KeepaliveMode::NoKeepalive\n            }\n        };\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n\n        convert_result(resp_handle.set_http_keepalive_mode(mode))\n    }\n\n    #[export_name = \"fastly_http_resp#close\"]\n    pub fn close(resp_handle: ResponseHandle) -> FastlyStatus {\n        let resp_handle = unsafe { http_resp::Response::from_handle(resp_handle) };\n        convert_result(fastly::compute::http_resp::close(resp_handle))\n    }\n\n    #[export_name = \"fastly_http_resp#get_addr_dest_ip\"]\n    pub fn get_addr_dest_ip(\n        resp_handle: ResponseHandle,\n        addr_octets_out: *mut u8,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        match resp_handle.get_remote_ip_addr() {\n            Some(ip_addr) => {\n                unsafe {\n                    *main_ptr!(nwritten_out) =\n                        encode_ip_address(ip_addr, main_ptr!(addr_octets_out));\n                }\n                FastlyStatus::OK\n            }\n            None => FastlyStatus::NONE,\n        }\n    }\n\n    #[export_name = \"fastly_http_resp#get_addr_dest_port\"]\n    pub fn get_addr_dest_port(resp_handle: ResponseHandle, port_out: *mut u16) -> FastlyStatus {\n        let resp_handle =\n            ManuallyDrop::new(unsafe { http_resp::Response::from_handle(resp_handle) });\n        match resp_handle.get_remote_port() {\n            Some(port) => {\n                unsafe {\n                    *main_ptr!(port_out) = port;\n                }\n                FastlyStatus::OK\n            }\n            None => FastlyStatus::NONE,\n        }\n    }\n}\n\npub mod fastly_dictionary {\n    use super::*;\n    use crate::bindings::fastly::compute::dictionary;\n\n    #[export_name = \"fastly_dictionary#open\"]\n    pub fn open(\n        name: *const u8,\n        name_len: usize,\n        dict_handle_out: *mut DictionaryHandle,\n    ) -> FastlyStatus {\n        let name = crate::make_str!(unsafe_main_ptr!(name), name_len);\n        match dictionary::Dictionary::open(name) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(dict_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            // As a special case, `fastly_dictionary#open` uses `BADF` to indicate not found.\n            Err(dictionary::OpenError::NotFound) => FastlyStatus::BADF,\n            // As a special case, `fastly_dictionary#open` uses `NONE` to indicate an empty name.\n            Err(dictionary::OpenError::InvalidSyntax) if name_len == 0 => FastlyStatus::NONE,\n            // As a special case, `fastly_dictionary#open` uses `UNSUPPORTED` to indicate a\n            // too-long name.\n            Err(dictionary::OpenError::NameTooLong) => FastlyStatus::UNSUPPORTED,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_dictionary#get\"]\n    pub fn get(\n        dict_handle: DictionaryHandle,\n        key: *const u8,\n        key_len: usize,\n        value: *mut u8,\n        value_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let key = crate::make_str!(unsafe_main_ptr!(key), key_len);\n        let dict_handle =\n            ManuallyDrop::new(unsafe { dictionary::Dictionary::from_handle(dict_handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(value),\n            value_max_len,\n            main_ptr!(nwritten),\n            { dict_handle.lookup(key, u64::try_from(value_max_len).trapping_unwrap()) }\n        )\n    }\n}\n\npub mod fastly_geo {\n    use super::*;\n    use crate::bindings::fastly::compute::geo;\n    use crate::fastly::decode_ip_address;\n\n    #[export_name = \"fastly_geo#lookup\"]\n    pub fn lookup(\n        addr_octets: *const u8,\n        addr_len: usize,\n        buf: *mut u8,\n        buf_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let addr = match unsafe { decode_ip_address(main_ptr!(addr_octets), addr_len) } {\n            Some(addr) => addr,\n            None => return FastlyStatus::INVALID_ARGUMENT,\n        };\n        alloc_result!(unsafe_main_ptr!(buf), buf_len, main_ptr!(nwritten_out), {\n            geo::lookup(addr, u64::try_from(buf_len).trapping_unwrap())\n        })\n    }\n}\n\npub mod fastly_device_detection {\n    use super::*;\n    use crate::bindings::fastly::compute::device_detection;\n\n    #[export_name = \"fastly_device_detection#lookup\"]\n    pub fn lookup(\n        user_agent: *const u8,\n        user_agent_max_len: usize,\n        buf: *mut u8,\n        buf_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let user_agent = crate::make_str!(unsafe_main_ptr!(user_agent), user_agent_max_len);\n        alloc_result_opt!(unsafe_main_ptr!(buf), buf_len, main_ptr!(nwritten_out), {\n            device_detection::lookup(user_agent, u64::try_from(buf_len).trapping_unwrap())\n        })\n    }\n}\n\npub mod fastly_erl {\n    use super::*;\n    use crate::bindings::fastly::compute::erl;\n\n    #[export_name = \"fastly_erl#check_rate\"]\n    pub fn check_rate(\n        rc: *const u8,\n        rc_max_len: usize,\n        entry: *const u8,\n        entry_max_len: usize,\n        delta: u32,\n        window: u32,\n        limit: u32,\n        pb: *const u8,\n        pb_max_len: usize,\n        ttl: u32,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let rc = crate::make_str!(unsafe_main_ptr!(rc), rc_max_len);\n        let entry = crate::make_str!(unsafe_main_ptr!(entry), entry_max_len);\n        let pb = crate::make_str!(unsafe_main_ptr!(pb), pb_max_len);\n        let rc = match erl::RateCounter::open(rc) {\n            Ok(rc) => rc,\n            Err(err) => return convert_result(Err(err)),\n        };\n        let pb = match erl::PenaltyBox::open(pb) {\n            Ok(pb) => pb,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match rc.check_rate(entry, delta, window, limit, &pb, ttl) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res.into();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_erl#ratecounter_increment\"]\n    pub fn ratecounter_increment(\n        rc: *const u8,\n        rc_max_len: usize,\n        entry: *const u8,\n        entry_max_len: usize,\n        delta: u32,\n    ) -> FastlyStatus {\n        let rc = crate::make_str!(unsafe_main_ptr!(rc), rc_max_len);\n        let entry = crate::make_str!(unsafe_main_ptr!(entry), entry_max_len);\n        let rc = match erl::RateCounter::open(rc) {\n            Ok(rc) => rc,\n            Err(err) => return convert_result(Err(err)),\n        };\n        convert_result(rc.increment(entry, delta))\n    }\n\n    #[export_name = \"fastly_erl#ratecounter_lookup_rate\"]\n    pub fn ratecounter_lookup_rate(\n        rc: *const u8,\n        rc_max_len: usize,\n        entry: *const u8,\n        entry_max_len: usize,\n        window: u32,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let rc = crate::make_str!(unsafe_main_ptr!(rc), rc_max_len);\n        let entry = crate::make_str!(unsafe_main_ptr!(entry), entry_max_len);\n        let rc = match erl::RateCounter::open(rc) {\n            Ok(rc) => rc,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match rc.lookup_rate(entry, window) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_erl#ratecounter_lookup_count\"]\n    pub fn ratecounter_lookup_count(\n        rc: *const u8,\n        rc_max_len: usize,\n        entry: *const u8,\n        entry_max_len: usize,\n        duration: u32,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let rc = crate::make_str!(unsafe_main_ptr!(rc), rc_max_len);\n        let entry = crate::make_str!(unsafe_main_ptr!(entry), entry_max_len);\n        let rc = match erl::RateCounter::open(rc) {\n            Ok(rc) => rc,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match rc.lookup_count(entry, duration) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_erl#penaltybox_add\"]\n    pub fn penaltybox_add(\n        pb: *const u8,\n        pb_max_len: usize,\n        entry: *const u8,\n        entry_max_len: usize,\n        ttl: u32,\n    ) -> FastlyStatus {\n        let pb = crate::make_str!(unsafe_main_ptr!(pb), pb_max_len);\n        let entry = crate::make_str!(unsafe_main_ptr!(entry), entry_max_len);\n        let pb = match erl::PenaltyBox::open(pb) {\n            Ok(pb) => pb,\n            Err(err) => return convert_result(Err(err)),\n        };\n        convert_result(pb.add(entry, ttl))\n    }\n\n    #[export_name = \"fastly_erl#penaltybox_has\"]\n    pub fn penaltybox_has(\n        pb: *const u8,\n        pb_max_len: usize,\n        entry: *const u8,\n        entry_max_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let pb = crate::make_str!(unsafe_main_ptr!(pb), pb_max_len);\n        let entry = crate::make_str!(unsafe_main_ptr!(entry), entry_max_len);\n        let pb = match erl::PenaltyBox::open(pb) {\n            Ok(pb) => pb,\n            Err(err) => return convert_result(Err(err)),\n        };\n        let res = pb.has(entry);\n        let value = unsafe_main_ptr!(value);\n        write_bool_result!(res, value)\n    }\n}\n\npub mod fastly_object_store {\n    use super::*;\n    use crate::bindings::fastly::compute::kv_store;\n\n    #[export_name = \"fastly_object_store#open\"]\n    pub fn open(\n        name_ptr: *const u8,\n        name_len: usize,\n        object_store_handle_out: *mut ObjectStoreHandle,\n    ) -> FastlyStatus {\n        let name = crate::make_str!(unsafe_main_ptr!(name_ptr), name_len);\n        match kv_store::Store::open(name) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(object_store_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(kv_store::OpenError::NotFound) => {\n                unsafe {\n                    *main_ptr!(object_store_handle_out) = INVALID_HANDLE;\n                }\n                FastlyStatus::INVALID_ARGUMENT\n            }\n            Err(e) => {\n                unsafe {\n                    *main_ptr!(object_store_handle_out) = INVALID_HANDLE;\n                }\n                e.into()\n            }\n        }\n    }\n\n    #[export_name = \"fastly_object_store#lookup\"]\n    pub fn lookup(\n        object_store_handle: ObjectStoreHandle,\n        key_ptr: *const u8,\n        key_len: usize,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let key = crate::make_str!(unsafe_main_ptr!(key_ptr), key_len);\n        let object_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(object_store_handle) });\n        // initialize out handle, in case of Err\n        unsafe {\n            *main_ptr!(body_handle_out) = INVALID_HANDLE;\n        }\n        let handle = match object_store_handle.lookup(key) {\n            // 200, unless the body take fails\n            Ok(Some(entry)) => entry\n                .take_body()\n                .map(|body| body.take_handle())\n                .unwrap_or(INVALID_HANDLE),\n            // 404\n            Ok(None) => INVALID_HANDLE,\n            // 400, reproducing old weird behavior of the original hostcall\n            Err(kv_store::KvError::BadRequest) => INVALID_HANDLE,\n            Err(e) => return e.into(),\n        };\n        // set true handle if we didn't early return error\n        unsafe {\n            *main_ptr!(body_handle_out) = handle;\n        }\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_object_store#lookup_async\"]\n    pub fn lookup_async(\n        object_store_handle: ObjectStoreHandle,\n        key_ptr: *const u8,\n        key_len: usize,\n        pending_body_handle_out: *mut PendingObjectStoreLookupHandle,\n    ) -> FastlyStatus {\n        let key = crate::make_str!(unsafe_main_ptr!(key_ptr), key_len);\n        let object_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(object_store_handle) });\n        match object_store_handle.lookup_async(key) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(pending_body_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_object_store#pending_lookup_wait\"]\n    pub fn await_pending_lookup(\n        pending_body_handle: PendingObjectStoreLookupHandle,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let pending_body_handle =\n            unsafe { kv_store::PendingLookup::from_handle(pending_body_handle) };\n        // initialize out handle, in case of Err\n        unsafe {\n            *main_ptr!(body_handle_out) = INVALID_HANDLE;\n        }\n        let handle = match kv_store::await_lookup(pending_body_handle) {\n            // 200, unless the body take fails\n            Ok(Some(entry)) => entry\n                .take_body()\n                .map(|body| body.take_handle())\n                .unwrap_or(INVALID_HANDLE),\n            // 404\n            Ok(None) => INVALID_HANDLE,\n            // 400, reproducing old weird behavior of the original hostcall\n            Err(kv_store::KvError::BadRequest) => INVALID_HANDLE,\n            Err(e) => return e.into(),\n        };\n        // set true handle if we didn't early return error\n        unsafe {\n            *main_ptr!(body_handle_out) = handle;\n        }\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_object_store#insert\"]\n    pub fn insert(\n        object_store_handle: ObjectStoreHandle,\n        key_ptr: *const u8,\n        key_len: usize,\n        body_handle: BodyHandle,\n    ) -> FastlyStatus {\n        let key = crate::make_str!(unsafe_main_ptr!(key_ptr), key_len);\n        let object_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(object_store_handle) });\n        let body_handle =\n            unsafe { crate::bindings::fastly::compute::http_body::Body::from_handle(body_handle) };\n        let options = kv_store::InsertOptions {\n            mode: kv_store::InsertMode::Overwrite,\n            if_generation_match: None,\n            metadata: None,\n            time_to_live_sec: None,\n            background_fetch: false,\n            extra: None,\n        };\n\n        let res = object_store_handle.insert(key, body_handle, &options);\n\n        std::mem::forget(options);\n\n        convert_result(res)\n    }\n\n    #[export_name = \"fastly_object_store#insert_async\"]\n    pub fn insert_async(\n        object_store_handle: ObjectStoreHandle,\n        key_ptr: *const u8,\n        key_len: usize,\n        body_handle: BodyHandle,\n        pending_body_handle_out: *mut PendingObjectStoreInsertHandle,\n    ) -> FastlyStatus {\n        let key = crate::make_str!(unsafe_main_ptr!(key_ptr), key_len);\n        let object_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(object_store_handle) });\n        let body_handle =\n            unsafe { crate::bindings::fastly::compute::http_body::Body::from_handle(body_handle) };\n        let options = kv_store::InsertOptions {\n            mode: kv_store::InsertMode::Overwrite,\n            if_generation_match: None,\n            metadata: None,\n            time_to_live_sec: None,\n            background_fetch: false,\n            extra: None,\n        };\n\n        let res = object_store_handle.insert_async(key, body_handle, &options);\n\n        // Don't drop the options. Even though we didn't pass any actual strings here,\n        // the `drop` function includes code contains calls to deallocation functions,\n        // which require linking in an allocator, which the adapter can't link in.\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(pending_body_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_object_store#pending_insert_wait\"]\n    pub fn await_pending_insert(\n        pending_body_handle: PendingObjectStoreInsertHandle,\n    ) -> FastlyStatus {\n        let pending_body_handle =\n            unsafe { kv_store::PendingInsert::from_handle(pending_body_handle) };\n        convert_result(kv_store::await_insert(pending_body_handle))\n    }\n\n    #[export_name = \"fastly_object_store#delete_async\"]\n    pub fn delete_async(\n        object_store_handle: ObjectStoreHandle,\n        key_ptr: *const u8,\n        key_len: usize,\n        pending_body_handle_out: *mut PendingObjectStoreDeleteHandle,\n    ) -> FastlyStatus {\n        let key = crate::make_str!(unsafe_main_ptr!(key_ptr), key_len);\n        let object_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(object_store_handle) });\n        match object_store_handle.delete_async(key) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(pending_body_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_object_store#pending_delete_wait\"]\n    pub fn pending_delete_wait(\n        pending_body_handle: PendingObjectStoreDeleteHandle,\n    ) -> FastlyStatus {\n        let pending_body_handle =\n            unsafe { kv_store::PendingDelete::from_handle(pending_body_handle) };\n        match kv_store::await_delete(pending_body_handle) {\n            Ok(_) => FastlyStatus::OK,\n            Err(e) => e.into(),\n        }\n    }\n}\n\npub mod fastly_kv_store {\n    use super::*;\n    use crate::bindings::fastly::compute::kv_store;\n    use core::slice;\n\n    #[repr(C)]\n    #[derive(Default, Clone, Copy)]\n    pub enum InsertMode {\n        #[default]\n        Overwrite,\n        Add,\n        Append,\n        Prepend,\n    }\n\n    impl From<InsertMode> for kv_store::InsertMode {\n        fn from(value: InsertMode) -> Self {\n            match value {\n                InsertMode::Overwrite => Self::Overwrite,\n                InsertMode::Add => Self::Add,\n                InsertMode::Append => Self::Append,\n                InsertMode::Prepend => Self::Prepend,\n            }\n        }\n    }\n\n    #[repr(C)]\n    pub struct InsertConfig {\n        pub mode: InsertMode,\n        pub unused: u32,\n        pub metadata: *const u8,\n        pub metadata_len: u32,\n        pub time_to_live_sec: u32,\n        pub if_generation_match: u64,\n    }\n\n    impl Default for InsertConfig {\n        fn default() -> Self {\n            InsertConfig {\n                mode: InsertMode::Overwrite,\n                unused: 0,\n                metadata: std::ptr::null(),\n                metadata_len: 0,\n                time_to_live_sec: 0,\n                if_generation_match: 0,\n            }\n        }\n    }\n\n    #[repr(C)]\n    #[derive(Default, Copy, Clone)]\n    pub enum ListModeInternal {\n        #[default]\n        Strong,\n        Eventual,\n    }\n\n    impl From<ListModeInternal> for kv_store::ListMode {\n        fn from(value: ListModeInternal) -> Self {\n            match value {\n                ListModeInternal::Strong => Self::Strong,\n                ListModeInternal::Eventual => Self::Eventual,\n            }\n        }\n    }\n\n    #[repr(C)]\n    pub struct ListConfig {\n        pub mode: ListModeInternal,\n        pub cursor: *const u8,\n        pub cursor_len: u32,\n        pub limit: u32,\n        pub prefix: *const u8,\n        pub prefix_len: u32,\n    }\n\n    impl Default for ListConfig {\n        fn default() -> Self {\n            ListConfig {\n                mode: ListModeInternal::Strong,\n                cursor: std::ptr::null(),\n                cursor_len: 0,\n                limit: 0,\n                prefix: std::ptr::null(),\n                prefix_len: 0,\n            }\n        }\n    }\n\n    #[repr(C)]\n    pub struct LookupConfig {\n        // reserved is just a placeholder,\n        // can be removed when something real is added\n        reserved: u32,\n    }\n\n    impl Default for LookupConfig {\n        fn default() -> Self {\n            LookupConfig { reserved: 0 }\n        }\n    }\n\n    #[repr(C)]\n    pub struct DeleteConfig {\n        // reserved is just a placeholder,\n        // can be removed when something real is added\n        reserved: u32,\n    }\n\n    impl Default for DeleteConfig {\n        fn default() -> Self {\n            DeleteConfig { reserved: 0 }\n        }\n    }\n\n    bitflags::bitflags! {\n        /// `InsertConfigOptions` codings.\n        #[derive(Default)]\n        #[repr(transparent)]\n        pub struct InsertConfigOptions: u32 {\n            const RESERVED = 1 << 0;\n            const BACKGROUND_FETCH = 1 << 1;\n            const RESERVED_2 = 1 << 2;\n            const METADATA = 1 << 3;\n            const TIME_TO_LIVE_SEC = 1 << 4;\n            const IF_GENERATION_MATCH = 1 << 5;\n        }\n        /// `ListConfigOptions` codings.\n        #[derive(Default)]\n        #[repr(transparent)]\n        pub struct ListConfigOptions: u32 {\n            const RESERVED = 1 << 0;\n            const CURSOR = 1 << 1;\n            const LIMIT = 1 << 2;\n            const PREFIX = 1 << 3;\n        }\n        /// `LookupConfigOptions` codings.\n        #[derive(Default)]\n        #[repr(transparent)]\n        pub struct LookupConfigOptions: u32 {\n            const RESERVED = 1 << 0;\n        }\n        /// `DeleteConfigOptions` codings.\n        #[derive(Default)]\n        #[repr(transparent)]\n        pub struct DeleteConfigOptions: u32 {\n            const RESERVED = 1 << 0;\n        }\n    }\n\n    #[repr(u32)]\n    #[derive(Clone, Copy, Debug, PartialEq, Eq)]\n    pub enum KvError {\n        Uninitialized,\n        Ok,\n        BadRequest,\n        NotFound,\n        PreconditionFailed,\n        PayloadTooLarge,\n        InternalError,\n        TooManyRequests,\n    }\n\n    impl From<kv_store::KvError> for KvError {\n        fn from(value: kv_store::KvError) -> Self {\n            use kv_store::KvError::*;\n            match value {\n                // use black_box here to prevent rustc/llvm from generating a switch table\n                BadRequest => std::hint::black_box(Self::BadRequest),\n                PreconditionFailed => Self::PreconditionFailed,\n                PayloadTooLarge => Self::PayloadTooLarge,\n                InternalError => Self::InternalError,\n                TooManyRequests => Self::TooManyRequests,\n                GenericError | Extra(_) => Self::Uninitialized,\n            }\n        }\n    }\n\n    #[export_name = \"fastly_kv_store#open\"]\n    pub fn open_v2(\n        name_ptr: *const u8,\n        name_len: usize,\n        kv_store_handle_out: *mut KVStoreHandle,\n    ) -> FastlyStatus {\n        let name = crate::make_str!(unsafe_main_ptr!(name_ptr), name_len);\n        match kv_store::Store::open(name) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(kv_store_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n\n            // As a special case, `fastly_kv_store#open` uses `INVALID_ARGUMENT` to indicate not found.\n            Err(kv_store::OpenError::NotFound) => {\n                unsafe {\n                    *main_ptr!(kv_store_handle_out) = INVALID_HANDLE;\n                }\n\n                FastlyStatus::INVALID_ARGUMENT\n            }\n\n            Err(e) => {\n                unsafe {\n                    *main_ptr!(kv_store_handle_out) = INVALID_HANDLE;\n                }\n\n                e.into()\n            }\n        }\n    }\n\n    #[export_name = \"fastly_kv_store#lookup\"]\n    pub fn lookup(\n        kv_store_handle: KVStoreHandle,\n        key_ptr: *const u8,\n        key_len: usize,\n        //  NOTE: mask and config are ignored in the wit definition while they're empty\n        _lookup_config_mask: LookupConfigOptions,\n        _lookup_config: *const LookupConfig,\n        body_handle_out: *mut KVStoreLookupHandle,\n    ) -> FastlyStatus {\n        let key = unsafe { slice::from_raw_parts(main_ptr!(key_ptr), key_len) };\n        let kv_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(kv_store_handle) });\n        match kv_store_handle.lookup_async(key) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_kv_store#lookup_wait\"]\n    pub fn lookup_wait(\n        lookup_handle: KVStoreLookupHandle,\n        body_handle_out: *mut BodyHandle,\n        metadata_out: *mut u8,\n        metadata_len: usize,\n        nwritten_out: *mut usize,\n        generation_out: *mut u32,\n        kv_error_out: *mut KvError,\n    ) -> FastlyStatus {\n        let lookup_handle = unsafe { kv_store::PendingLookup::from_handle(lookup_handle) };\n        let res = match kv_store::await_lookup(lookup_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Ok;\n                }\n\n                res\n            }\n            Ok(None) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::NotFound;\n                }\n\n                return FastlyStatus::OK;\n            }\n            Err(kv_store::KvError::GenericError) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Uninitialized;\n                }\n\n                return FastlyStatus::UNKNOWN_ERROR;\n            }\n            Err(e) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = e.into();\n                }\n\n                return FastlyStatus::OK;\n            }\n        };\n\n        with_buffer!(\n            unsafe_main_ptr!(metadata_out),\n            metadata_len,\n            { res.metadata(u64::try_from(metadata_len).trapping_unwrap()) },\n            |res| {\n                let buf = handle_buffer_len!(res, main_ptr!(nwritten_out));\n\n                unsafe {\n                    *main_ptr!(nwritten_out) = buf.as_ref().map(Vec::len).unwrap_or(0);\n                }\n\n                std::mem::forget(buf);\n            }\n        );\n\n        let body = res.take_body().trapping_unwrap();\n\n        unsafe {\n            *main_ptr!(body_handle_out) = body.take_handle();\n            // reproduce bugged behavior in old hostcall\n            *main_ptr!(generation_out) = 0;\n        }\n\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_kv_store#lookup_wait_v2\"]\n    pub fn lookup_wait_v2(\n        lookup_handle: KVStoreLookupHandle,\n        body_handle_out: *mut BodyHandle,\n        metadata_out: *mut u8,\n        metadata_len: usize,\n        nwritten_out: *mut usize,\n        generation_out: *mut u64,\n        kv_error_out: *mut KvError,\n    ) -> FastlyStatus {\n        let lookup_handle = unsafe { kv_store::PendingLookup::from_handle(lookup_handle) };\n        let res = match kv_store::await_lookup(lookup_handle) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Ok;\n                }\n\n                res\n            }\n            Ok(None) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::NotFound;\n                }\n\n                return FastlyStatus::OK;\n            }\n            Err(kv_store::KvError::GenericError) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Uninitialized;\n                }\n\n                return FastlyStatus::UNKNOWN_ERROR;\n            }\n            Err(e) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = e.into();\n                }\n\n                return FastlyStatus::OK;\n            }\n        };\n\n        with_buffer!(\n            unsafe_main_ptr!(metadata_out),\n            metadata_len,\n            { res.metadata(u64::try_from(metadata_len).trapping_unwrap()) },\n            |res| {\n                let buf = handle_buffer_len!(res, main_ptr!(nwritten_out));\n\n                unsafe {\n                    *main_ptr!(nwritten_out) = buf.as_ref().map(Vec::len).unwrap_or(0);\n                }\n\n                std::mem::forget(buf);\n            }\n        );\n\n        let body = res.take_body().trapping_unwrap();\n        let generation = res.generation();\n\n        unsafe {\n            *main_ptr!(body_handle_out) = body.take_handle();\n            *main_ptr!(generation_out) = generation;\n        }\n\n        FastlyStatus::OK\n    }\n\n    #[export_name = \"fastly_kv_store#insert\"]\n    pub fn insert(\n        kv_store_handle: KVStoreHandle,\n        key_ptr: *const u8,\n        key_len: usize,\n        body_handle: BodyHandle,\n        insert_config_mask: InsertConfigOptions,\n        insert_config: *const InsertConfig,\n        body_handle_out: *mut KVStoreInsertHandle,\n    ) -> FastlyStatus {\n        let key = unsafe { slice::from_raw_parts(main_ptr!(key_ptr), key_len) };\n        let kv_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(kv_store_handle) });\n        let body_handle =\n            unsafe { crate::bindings::fastly::compute::http_body::Body::from_handle(body_handle) };\n\n        let insert_config = unsafe_main_ptr!(insert_config);\n        let metadata = if insert_config_mask.contains(InsertConfigOptions::METADATA) {\n            unsafe {\n                Some(ManuallyDrop::into_inner(crate::make_string!(\n                    main_ptr!((*insert_config).metadata),\n                    (*insert_config).metadata_len\n                )))\n            }\n        } else {\n            None\n        };\n        let options = unsafe {\n            kv_store::InsertOptions {\n                mode: (*insert_config).mode.into(),\n                if_generation_match: insert_config_mask\n                    .contains(InsertConfigOptions::IF_GENERATION_MATCH)\n                    .then_some((*insert_config).if_generation_match),\n                metadata,\n                time_to_live_sec: insert_config_mask\n                    .contains(InsertConfigOptions::TIME_TO_LIVE_SEC)\n                    .then_some((*insert_config).time_to_live_sec),\n                background_fetch: insert_config_mask\n                    .contains(InsertConfigOptions::BACKGROUND_FETCH),\n                extra: None,\n            }\n        };\n\n        let res = kv_store_handle.insert_async(key, body_handle, &options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_kv_store#insert_wait\"]\n    pub fn insert_wait(\n        insert_handle: KVStoreInsertHandle,\n        kv_error_out: *mut KvError,\n    ) -> FastlyStatus {\n        let insert_handle = unsafe { kv_store::PendingInsert::from_handle(insert_handle) };\n        match kv_store::await_insert(insert_handle) {\n            Ok(()) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Ok;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(kv_store::KvError::GenericError) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Uninitialized;\n                }\n\n                FastlyStatus::UNKNOWN_ERROR\n            }\n\n            Err(e) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = e.into();\n                }\n\n                FastlyStatus::OK\n            }\n        }\n    }\n\n    #[export_name = \"fastly_kv_store#delete\"]\n    pub fn delete(\n        kv_store_handle: KVStoreHandle,\n        key_ptr: *const u8,\n        key_len: usize,\n        // These are ignored in the wit interface for the time being, as they don't pass any\n        // meaningful values.\n        _delete_config_mask: DeleteConfigOptions,\n        _delete_config: *const DeleteConfig,\n        body_handle_out: *mut KVStoreDeleteHandle,\n    ) -> FastlyStatus {\n        let key = unsafe { slice::from_raw_parts(main_ptr!(key_ptr), key_len) };\n        let kv_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(kv_store_handle) });\n        match kv_store_handle.delete_async(key) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_kv_store#delete_wait\"]\n    pub fn delete_wait(\n        delete_handle: KVStoreDeleteHandle,\n        kv_error_out: *mut KvError,\n    ) -> FastlyStatus {\n        let delete_handle = unsafe { kv_store::PendingDelete::from_handle(delete_handle) };\n        match kv_store::await_delete(delete_handle) {\n            Ok(true) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Ok;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Ok(false) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::NotFound;\n                }\n\n                return FastlyStatus::OK;\n            }\n            Err(kv_store::KvError::GenericError) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Uninitialized;\n                }\n\n                return FastlyStatus::UNKNOWN_ERROR;\n            }\n\n            Err(e) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Uninitialized;\n                }\n\n                e.into()\n            }\n        }\n    }\n\n    #[export_name = \"fastly_kv_store#list\"]\n    pub fn list(\n        kv_store_handle: KVStoreHandle,\n        list_config_mask: ListConfigOptions,\n        list_config: *const ListConfig,\n        body_handle_out: *mut KVStoreListHandle,\n    ) -> FastlyStatus {\n        let kv_store_handle =\n            ManuallyDrop::new(unsafe { kv_store::Store::from_handle(kv_store_handle) });\n\n        let list_config = unsafe_main_ptr!(list_config);\n        let cursor = if list_config_mask.contains(ListConfigOptions::CURSOR) {\n            Some(unsafe {\n                ManuallyDrop::into_inner(crate::make_string!(\n                    main_ptr!((*list_config).cursor),\n                    (*list_config).cursor_len\n                ))\n            })\n        } else {\n            None\n        };\n        let prefix = if list_config_mask.contains(ListConfigOptions::PREFIX) {\n            Some(unsafe {\n                ManuallyDrop::into_inner(crate::make_string!(\n                    main_ptr!((*list_config).prefix),\n                    (*list_config).prefix_len\n                ))\n            })\n        } else {\n            None\n        };\n        let options = unsafe {\n            kv_store::ListOptions {\n                mode: (*list_config).mode.into(),\n                cursor,\n                limit: list_config_mask\n                    .contains(ListConfigOptions::LIMIT)\n                    .then_some((*list_config).limit),\n                prefix,\n                extra: None,\n            }\n        };\n\n        let res = kv_store_handle.list_async(&options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_kv_store#list_wait\"]\n    pub fn list_wait(\n        list_handle: KVStoreListHandle,\n        body_handle_out: *mut BodyHandle,\n        kv_error_out: *mut KvError,\n    ) -> FastlyStatus {\n        let list_handle = unsafe { kv_store::PendingList::from_handle(list_handle) };\n        match kv_store::await_list(list_handle) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Ok;\n                    *main_ptr!(body_handle_out) = res.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(kv_store::KvError::GenericError) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = KvError::Uninitialized;\n                    *main_ptr!(body_handle_out) = INVALID_HANDLE;\n                }\n\n                FastlyStatus::UNKNOWN_ERROR\n            }\n\n            Err(e) => {\n                unsafe {\n                    *main_ptr!(kv_error_out) = e.into();\n                    *main_ptr!(body_handle_out) = INVALID_HANDLE;\n                }\n\n                FastlyStatus::OK\n            }\n        }\n    }\n}\n\npub mod fastly_secret_store {\n    use super::*;\n    use crate::bindings::fastly::compute::secret_store;\n    use core::slice;\n\n    #[export_name = \"fastly_secret_store#open\"]\n    pub fn open(\n        secret_store_name_ptr: *const u8,\n        secret_store_name_len: usize,\n        secret_store_handle_out: *mut SecretStoreHandle,\n    ) -> FastlyStatus {\n        let secret_store_name = crate::make_str!(\n            unsafe_main_ptr!(secret_store_name_ptr),\n            secret_store_name_len\n        );\n        match secret_store::Store::open(secret_store_name) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(secret_store_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_secret_store#get\"]\n    pub fn get(\n        secret_store_handle: SecretStoreHandle,\n        secret_name_ptr: *const u8,\n        secret_name_len: usize,\n        secret_handle_out: *mut SecretHandle,\n    ) -> FastlyStatus {\n        let secret_name = crate::make_str!(unsafe_main_ptr!(secret_name_ptr), secret_name_len);\n        let secret_store_handle =\n            ManuallyDrop::new(unsafe { secret_store::Store::from_handle(secret_store_handle) });\n        match secret_store_handle.get(secret_name) {\n            Ok(Some(res)) => {\n                unsafe {\n                    *main_ptr!(secret_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n\n            Ok(None) => FastlyStatus::NONE,\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_secret_store#plaintext\"]\n    pub fn plaintext(\n        secret_handle: SecretHandle,\n        plaintext_buf: *mut u8,\n        plaintext_max_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let secret_handle =\n            ManuallyDrop::new(unsafe { secret_store::Secret::from_handle(secret_handle) });\n        alloc_result!(\n            unsafe_main_ptr!(plaintext_buf),\n            plaintext_max_len,\n            main_ptr!(nwritten_out),\n            { secret_handle.plaintext(u64::try_from(plaintext_max_len).trapping_unwrap()) }\n        )\n    }\n\n    #[export_name = \"fastly_secret_store#from_bytes\"]\n    pub fn from_bytes(\n        plaintext_buf: *const u8,\n        plaintext_len: usize,\n        secret_handle_out: *mut SecretHandle,\n    ) -> FastlyStatus {\n        let plaintext = unsafe { slice::from_raw_parts(main_ptr!(plaintext_buf), plaintext_len) };\n        match secret_store::Secret::from_bytes(plaintext) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(secret_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n}\n\npub mod fastly_backend {\n    use super::*;\n    use crate::bindings::fastly::compute::{backend, http_types};\n\n    #[derive(Clone, Copy, Debug, PartialEq, Eq)]\n    #[repr(u32)]\n    pub enum BackendHealth {\n        Unknown,\n        Healthy,\n        Unhealthy,\n    }\n\n    impl From<backend::BackendHealth> for BackendHealth {\n        fn from(value: backend::BackendHealth) -> Self {\n            match value {\n                backend::BackendHealth::Unknown => BackendHealth::Unknown,\n                backend::BackendHealth::Healthy => BackendHealth::Healthy,\n                backend::BackendHealth::Unhealthy => BackendHealth::Unhealthy,\n            }\n        }\n    }\n\n    fn decode_tls_version(val: http_types::TlsVersion) -> Result<u32, ()> {\n        match val {\n            0x0301 => Ok(0), // TLS 1.0\n            0x0302 => Ok(1), // TLS 1.1\n            0x0303 => Ok(2), // TLS 1.2\n            0x0304 => Ok(3), // TLS 1.3\n            _ => Err(()),\n        }\n    }\n\n    #[export_name = \"fastly_backend#exists\"]\n    pub fn exists(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        backend_exists_out: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        match backend::Backend::open(backend) {\n            Ok(_res) => {\n                unsafe {\n                    *main_ptr!(backend_exists_out) = 1;\n                }\n                FastlyStatus::OK\n            }\n            Err(backend::OpenError::NotFound) => {\n                unsafe {\n                    *main_ptr!(backend_exists_out) = 0;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#is_healthy\"]\n    pub fn is_healthy(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        backend_health_out: *mut BackendHealth,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.is_healthy() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(backend_health_out) = BackendHealth::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#is_dynamic\"]\n    pub fn is_dynamic(backend_ptr: *const u8, backend_len: usize, value: *mut u32) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.is_dynamic() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_host\"]\n    pub fn get_host(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u8,\n        value_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        alloc_result!(\n            unsafe_main_ptr!(value),\n            value_max_len,\n            main_ptr!(nwritten),\n            { backend.get_host(u64::try_from(value_max_len).trapping_unwrap()) }\n        )\n    }\n\n    #[export_name = \"fastly_backend#get_override_host\"]\n    pub fn get_override_host(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u8,\n        value_max_len: usize,\n        nwritten: *mut usize,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        alloc_result_opt!(\n            unsafe_main_ptr!(value),\n            value_max_len,\n            main_ptr!(nwritten),\n            { backend.get_override_host(u64::try_from(value_max_len).trapping_unwrap(),) }\n        )\n    }\n\n    #[export_name = \"fastly_backend#get_port\"]\n    pub fn get_port(backend_ptr: *const u8, backend_len: usize, value: *mut u16) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_port() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_connect_timeout_ms\"]\n    pub fn get_connect_timeout_ms(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_connect_timeout_ms() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_first_byte_timeout_ms\"]\n    pub fn get_first_byte_timeout_ms(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_first_byte_timeout_ms() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_between_bytes_timeout_ms\"]\n    pub fn get_between_bytes_timeout_ms(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_between_bytes_timeout_ms() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#is_ssl\"]\n    pub fn is_ssl(backend_ptr: *const u8, backend_len: usize, value: *mut u32) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.is_tls() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = u32::from(res);\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_ssl_min_version\"]\n    pub fn get_ssl_min_version(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_tls_min_version() {\n            Ok(Some(res)) => match decode_tls_version(res) {\n                Ok(decoded) => {\n                    unsafe {\n                        *main_ptr!(value) = decoded;\n                    }\n                    FastlyStatus::OK\n                }\n                Err(()) => FastlyStatus::UNSUPPORTED,\n            },\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_ssl_max_version\"]\n    pub fn get_ssl_max_version(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_tls_max_version() {\n            Ok(Some(res)) => match decode_tls_version(res) {\n                Ok(decoded) => {\n                    unsafe {\n                        *main_ptr!(value) = decoded;\n                    }\n                    FastlyStatus::OK\n                }\n                Err(()) => FastlyStatus::UNSUPPORTED,\n            },\n            Ok(None) => FastlyStatus::NONE,\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_http_keepalive_time\"]\n    pub fn get_http_keepalive_time(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_http_keepalive_time() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_tcp_keepalive_enable\"]\n    pub fn get_tcp_keepalive_enable(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_tcp_keepalive_enable() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = if res { 1 } else { 0 };\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_tcp_keepalive_interval\"]\n    pub fn get_tcp_keepalive_interval(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_tcp_keepalive_interval() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_tcp_keepalive_probes\"]\n    pub fn get_tcp_keepalive_probes(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_tcp_keepalive_probes() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_backend#get_tcp_keepalive_time\"]\n    pub fn get_tcp_keepalive_time(\n        backend_ptr: *const u8,\n        backend_len: usize,\n        value: *mut u32,\n    ) -> FastlyStatus {\n        let backend = crate::make_str!(unsafe_main_ptr!(backend_ptr), backend_len);\n        let backend = match backend::Backend::open(backend) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        match backend.get_tcp_keepalive_time() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(value) = res;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n}\n\npub mod fastly_acl {\n    use super::*;\n    use crate::bindings::fastly::compute::acl;\n    use crate::fastly::decode_ip_address;\n\n    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\n    #[repr(u32)]\n    pub enum AclError {\n        Uninitialized = 0,\n        Ok = 1,\n        NoContent = 2,\n        TooManyRequests = 3,\n    }\n\n    #[export_name = \"fastly_acl#open\"]\n    pub fn open(\n        acl_name_ptr: *const u8,\n        acl_name_len: usize,\n        acl_handle_out: *mut AclHandle,\n    ) -> FastlyStatus {\n        let acl_name = crate::make_str!(unsafe_main_ptr!(acl_name_ptr), acl_name_len);\n        match acl::Acl::open(acl_name) {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(acl_handle_out) = res.take_handle();\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_acl#lookup\"]\n    pub fn lookup(\n        acl_handle: AclHandle,\n        ip_octets: *const u8,\n        ip_len: usize,\n        body_handle_out: *mut BodyHandle,\n        acl_error_out: *mut AclError,\n    ) -> FastlyStatus {\n        let ip = match unsafe { decode_ip_address(main_ptr!(ip_octets), ip_len) } {\n            Some(ip) => ip,\n            None => return FastlyStatus::INVALID_ARGUMENT,\n        };\n        let acl_handle = ManuallyDrop::new(unsafe { acl::Acl::from_handle(acl_handle) });\n        match acl_handle.lookup(ip) {\n            Ok(Some(body_handle)) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = body_handle.take_handle();\n                    *main_ptr!(acl_error_out) = AclError::Ok;\n                }\n                FastlyStatus::OK\n            }\n            Ok(None) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = INVALID_HANDLE;\n                    *main_ptr!(acl_error_out) = AclError::NoContent;\n                }\n                FastlyStatus::OK\n            }\n            Err(acl::AclError::TooManyRequests) => {\n                unsafe { *main_ptr!(acl_error_out) = AclError::TooManyRequests }\n\n                FastlyStatus::OK\n            }\n            Err(acl::AclError::GenericError) => {\n                unsafe { *main_ptr!(acl_error_out) = AclError::Uninitialized }\n\n                FastlyStatus::UNKNOWN_ERROR\n            }\n        }\n    }\n}\n\npub mod fastly_async_io {\n    use super::*;\n    use crate::bindings::fastly::compute::async_io;\n    use core::slice;\n\n    #[export_name = \"fastly_async_io#select\"]\n    pub fn select(\n        async_item_handles: *const AsyncItemHandle,\n        async_item_handles_len: usize,\n        timeout_ms: u32,\n        done_index_out: *mut u32,\n    ) -> FastlyStatus {\n        unsafe {\n            let refs = slice::from_raw_parts(main_ptr!(async_item_handles), async_item_handles_len);\n\n            // In the witx ABI, a `timeout_ms` value of 0 means no timeout.\n            *main_ptr!(done_index_out) = if timeout_ms == 0 {\n                select_wrapper(refs)\n            } else {\n                select_with_timeout_wrapper(refs, timeout_ms).unwrap_or(u32::MAX)\n            };\n\n            FastlyStatus::OK\n        }\n    }\n\n    #[export_name = \"fastly_async_io#is_ready\"]\n    pub fn is_ready(async_item_handle: AsyncItemHandle, ready_out: *mut u32) -> FastlyStatus {\n        unsafe {\n            let async_item_handle =\n                ManuallyDrop::new(async_io::Pollable::from_handle(async_item_handle));\n            *main_ptr!(ready_out) = async_item_handle.is_ready().into();\n            FastlyStatus::OK\n        }\n    }\n}\n\npub mod fastly_purge {\n    use super::*;\n    use crate::bindings::fastly::compute::purge;\n\n    bitflags::bitflags! {\n        #[derive(Default)]\n        #[repr(transparent)]\n        pub struct PurgeOptionsMask: u32 {\n            const SOFT_PURGE = 1 << 0;\n            const RET_BUF = 1 << 1;\n        }\n    }\n\n    impl From<PurgeOptionsMask> for purge::PurgeOptions<'_> {\n        fn from(value: PurgeOptionsMask) -> Self {\n            Self {\n                soft_purge: value.contains(PurgeOptionsMask::SOFT_PURGE),\n                extra: None,\n            }\n        }\n    }\n\n    #[derive(Debug)]\n    #[repr(C)]\n    pub struct PurgeOptions {\n        pub ret_buf_ptr: *mut u8,\n        pub ret_buf_len: usize,\n        pub ret_buf_nwritten_out: *mut usize,\n    }\n\n    #[export_name = \"fastly_purge#purge_surrogate_key\"]\n    pub fn purge_surrogate_key(\n        surrogate_key_ptr: *const u8,\n        surrogate_key_len: usize,\n        options_mask: PurgeOptionsMask,\n        options: *mut PurgeOptions,\n    ) -> FastlyStatus {\n        let options = unsafe_main_ptr!(options);\n        let surrogate_key =\n            crate::make_str!(unsafe_main_ptr!(surrogate_key_ptr), surrogate_key_len);\n        let ret_buf = options_mask.contains(PurgeOptionsMask::RET_BUF);\n        let wit_options = ManuallyDrop::new(options_mask.into());\n\n        if ret_buf {\n            // The `RET_BUF` flag means the user wants the string, so call the\n            // verbose version.\n            let len = unsafe { (*options).ret_buf_len };\n            with_buffer!(\n                unsafe { main_ptr!((*options).ret_buf_ptr) },\n                len,\n                {\n                    match purge::purge_surrogate_key_verbose(\n                        surrogate_key,\n                        &wit_options,\n                        u64::try_from(len).trapping_unwrap(),\n                    ) {\n                        Ok(res) => Ok(res),\n                        Err(err) => Err(err),\n                    }\n                },\n                |res| {\n                    let res = handle_buffer_len!(res, main_ptr!((*options).ret_buf_nwritten_out));\n                    unsafe {\n                        *main_ptr!((*options).ret_buf_nwritten_out) = res.len();\n                    }\n                    std::mem::forget(res);\n                }\n            )\n        } else {\n            // The user doesn't want the string, so call the regular version.\n            match purge::purge_surrogate_key(surrogate_key, &wit_options) {\n                Ok(()) => FastlyStatus::OK,\n                Err(err) => err.into(),\n            }\n        }\n    }\n}\n\npub mod fastly_shielding {\n    use super::*;\n    use crate::bindings::fastly::compute::{shielding as host, types};\n\n    bitflags::bitflags! {\n        #[derive(Default)]\n        #[repr(transparent)]\n        pub struct ShieldBackendOptions: u32 {\n            const RESERVED = 1 << 0;\n            const CACHE_KEY = 1 << 1;\n            const FIRST_BYTE_TIMEOUT = 1 << 2;\n        }\n    }\n\n    #[repr(C)]\n    pub struct ShieldBackendConfig {\n        pub cache_key: *const u8,\n        pub cache_key_len: u32,\n        pub first_byte_timeout_ms: u32,\n    }\n\n    impl Default for ShieldBackendConfig {\n        fn default() -> Self {\n            ShieldBackendConfig {\n                cache_key: std::ptr::null(),\n                cache_key_len: 0,\n                first_byte_timeout_ms: 0,\n            }\n        }\n    }\n\n    //   (@interface func (export \"shield_info\")\n    //     (param $name string)\n    //     (param $info_block (@witx pointer (@witx char8)))\n    //     (param $info_block_max_len (@witx usize))\n    //     (result $err (expected $num_bytes (error $fastly_status)))\n    //   )\n\n    /// Get information about the given shield in the Fastly network\n    #[export_name = \"fastly_shielding#shield_info\"]\n    pub fn shield_info(\n        name: *const u8,\n        name_len: usize,\n        info_block: *mut u8,\n        info_block_len: usize,\n        nwritten_out: *mut u32,\n    ) -> FastlyStatus {\n        let name = crate::make_str!(unsafe_main_ptr!(name), name_len);\n        with_buffer!(\n            unsafe_main_ptr!(info_block),\n            info_block_len,\n            { host::shield_info(name, u64::try_from(info_block_len).trapping_unwrap()) },\n            |res| {\n                match res {\n                    Ok(res) => {\n                        unsafe {\n                            *main_ptr!(nwritten_out) = u32::try_from(res.len()).unwrap_or(0);\n                        }\n                        std::mem::forget(res);\n                    }\n\n                    Err(e) => {\n                        if let types::Error::BufferLen(needed) = e {\n                            unsafe {\n                                *main_ptr!(nwritten_out) = u32::try_from(needed).unwrap_or(0);\n                            }\n                        }\n\n                        return Err(e.into());\n                    }\n                }\n            }\n        )\n    }\n\n    /// Turn a pop name into a backend that we can send requests to.\n    #[export_name = \"fastly_shielding#backend_for_shield\"]\n    pub fn backend_for_shield(\n        name: *const u8,\n        name_len: usize,\n        options_mask: ShieldBackendOptions,\n        options: *const ShieldBackendConfig,\n        backend_name: *mut u8,\n        backend_name_len: usize,\n        nwritten_out: *mut u32,\n    ) -> FastlyStatus {\n        // Backend names may be up to 255 bytes long, so require a buffer at\n        // least that big.\n        if backend_name_len < 255 {\n            return FastlyStatus::BUFFER_LEN;\n        }\n\n        let name = crate::make_str!(unsafe_main_ptr!(name), name_len);\n        let options = unsafe_main_ptr!(options);\n\n        let adapted_options = if options_mask.is_empty() {\n            // Skip the hostcalls to create the options resource.\n            None\n        } else {\n            let adapted_options = host::ShieldBackendOptions::new();\n            if options_mask.contains(ShieldBackendOptions::FIRST_BYTE_TIMEOUT) {\n                adapted_options.set_first_byte_timeout(unsafe { (*options).first_byte_timeout_ms });\n            }\n            if options_mask.contains(ShieldBackendOptions::CACHE_KEY) {\n                let s = unsafe {\n                    crate::make_slice!(main_ptr!((*options).cache_key), (*options).cache_key_len)\n                };\n                adapted_options.set_cache_key(&s);\n            }\n\n            Some(adapted_options)\n        };\n        // We allow the ShieldBackendOptions Resource to drop at the end of this call;\n        // we don't need it anymore, and it has no heap allocations.\n\n        let res = host::backend_for_shield(name, adapted_options.as_ref());\n\n        let Ok(backend) = &res else {\n            return convert_result(res.map(|_backend| ()));\n        };\n\n        with_buffer!(\n            unsafe_main_ptr!(backend_name),\n            backend_name_len,\n            {\n                let res = backend.get_name();\n                unsafe {\n                    *main_ptr!(nwritten_out) = u32::try_from(res.len()).unwrap_or(0);\n                }\n\n                res\n            },\n            |res| {\n                std::mem::forget(res);\n            }\n        )\n    }\n}\n\nmod fastly {\n    use super::*;\n\n    #[export_name = \"fastly#init\"]\n    pub fn init(abi_version: u64) -> FastlyStatus {\n        fastly_abi::init(abi_version)\n    }\n}\n\nmod fastly_secrets {\n    use super::*;\n\n    #[export_name = \"fastly_secrets#get_secret\"]\n    pub fn get_secret(\n        _name_ptr: *const u8,\n        _name_len: usize,\n        _secret_buf: *mut u8,\n        _secret_max_len: usize,\n        _nwritten: *mut usize,\n    ) -> FastlyStatus {\n        FastlyStatus::UNSUPPORTED\n    }\n}\n\n#[repr(u32)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum ImageOptimizerErrorTag {\n    Uninitialized = 0,\n    Ok = 1,\n    Error = 2,\n    Warning = 3,\n}\n\nbitflags::bitflags! {\n    /// `ImageOptimizerTransformConfigOptions` codings.\n    #[derive(Default)]\n    #[repr(transparent)]\n    pub struct ImageOptimizerTransformConfigOptions: u32 {\n        const RESERVED = 1 << 0;\n        const SDK_CLAIMS_OPTS = 1 << 1;\n    }\n}\n\nmod fastly_image_optimizer {\n    use super::*;\n    use crate::bindings::fastly::compute::{backend, image_optimizer};\n\n    #[repr(C)]\n    #[derive(Clone, Debug, PartialEq, Eq)]\n    pub struct ImageOptimizerErrorDetail {\n        pub tag: ImageOptimizerErrorTag,\n        pub message: *const u8,\n        pub message_len: usize,\n    }\n\n    #[repr(C)]\n    #[derive(Clone, Debug, PartialEq, Eq)]\n    pub struct ImageOptimizerTransformConfig {\n        pub sdk_claims_opts: *const u8,\n        pub sdk_claims_opts_len: usize,\n    }\n\n    #[export_name = \"fastly_image_optimizer#transform_image_optimizer_request\"]\n    pub fn transform_image_optimizer_request(\n        req_handle: RequestHandle,\n        body_handle: BodyHandle,\n        origin_image_backend: *const u8,\n        origin_image_backend_len: usize,\n        io_transform_config_options: ImageOptimizerTransformConfigOptions,\n        io_transform_config: *const ImageOptimizerTransformConfig,\n        io_error_detail: *mut ImageOptimizerErrorDetail,\n        resp_handle_out: *mut ResponseHandle,\n        resp_body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let backend_name = crate::make_str!(\n            unsafe_main_ptr!(origin_image_backend),\n            origin_image_backend_len\n        );\n        let backend = match backend::Backend::open(backend_name) {\n            Ok(backend) => backend,\n            Err(err) => return convert_result(Err(err)),\n        };\n        let body_handle = if body_handle == INVALID_HANDLE {\n            None\n        } else {\n            unsafe {\n                Some(crate::bindings::fastly::compute::http_body::Body::from_handle(body_handle))\n            }\n        };\n        let req_handle = ManuallyDrop::new(unsafe {\n            crate::bindings::fastly::compute::http_req::Request::from_handle(req_handle)\n        });\n\n        let io_transform_config = unsafe_main_ptr!(io_transform_config);\n        macro_rules! make_string {\n            ($ptr_field:ident, $len_field:ident) => {\n                unsafe {\n                    crate::make_string!(\n                        main_ptr!((*io_transform_config).$ptr_field),\n                        (*io_transform_config).$len_field\n                    )\n                }\n            };\n        }\n\n        let options = image_optimizer::ImageOptimizerTransformOptions {\n            sdk_claims_opts: if io_transform_config_options\n                .contains(ImageOptimizerTransformConfigOptions::SDK_CLAIMS_OPTS)\n            {\n                Some(ManuallyDrop::into_inner(make_string!(\n                    sdk_claims_opts,\n                    sdk_claims_opts_len\n                )))\n            } else {\n                None\n            },\n            extra: None,\n        };\n\n        let res = image_optimizer::transform_image_optimizer_request(\n            &req_handle,\n            body_handle,\n            &backend,\n            &options,\n        );\n\n        std::mem::forget(options);\n\n        unsafe {\n            (*main_ptr!(io_error_detail)).tag = ImageOptimizerErrorTag::Uninitialized;\n        }\n        match res {\n            Ok((resp, body)) => {\n                unsafe {\n                    *main_ptr!(resp_handle_out) = resp.take_handle();\n                    *main_ptr!(resp_body_handle_out) = body.take_handle();\n                    (*main_ptr!(io_error_detail)).tag = ImageOptimizerErrorTag::Ok;\n                }\n                FastlyStatus::OK\n            }\n            Err(e) => FastlyStatus::from(e),\n        }\n    }\n}\n\n/// Bindings for `async_io::select`.\n///\n/// We can't use the bindings generated by the macro because they use an\n/// allocation to convert a `&[Resource]` to a `&[u32]`.\nfn select_wrapper(hs: &[u32]) -> u32 {\n    unsafe {\n        #[link(wasm_import_module = \"fastly:compute/async-io@0.1.0\")]\n        extern \"C\" {\n            #[link_name = \"select\"]\n            fn wit_import(_: *const u32, _: usize) -> u32;\n        }\n        wit_import(hs.as_ptr(), hs.len())\n    }\n}\n\n/// Bindings for `async_io::select-with-timeout`.\n///\n/// We can't use the bindings generated by the macro because they use an\n/// allocation to convert a `&[Resource]` to a `&[u32]`.\nfn select_with_timeout_wrapper(hs: &[u32], timeout_ms: u32) -> Option<u32> {\n    unsafe {\n        #[repr(align(4))]\n        struct RetArea([::core::mem::MaybeUninit<u8>; 8]);\n        let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 8]);\n        let ptr1 = ret_area.0.as_mut_ptr().cast::<u8>();\n        #[link(wasm_import_module = \"fastly:compute/async-io@0.1.0\")]\n        extern \"C\" {\n            #[link_name = \"select-with-timeout\"]\n            fn wit_import(_: *const u32, _: usize, _: u32, _: *mut u8);\n        }\n        wit_import(hs.as_ptr(), hs.len(), timeout_ms, ptr1);\n        match *ptr1.add(0).cast::<u8>() {\n            0 => None,\n            1 => Some(*ptr1.add(4).cast::<u32>()),\n            _ => {\n                // Invalid enum discriminant.\n                unreachable!()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/fastly/error.rs",
    "content": "use crate::StateError;\n\n#[derive(PartialEq, Eq)]\n#[repr(C)]\npub struct FastlyStatus(i32);\n\nimpl StateError for FastlyStatus {\n    const SUCCESS: Self = Self::OK;\n}\n\nimpl FastlyStatus {\n    pub const OK: Self = FastlyStatus(0);\n    pub const UNKNOWN_ERROR: Self = FastlyStatus(1);\n    pub const INVALID_ARGUMENT: Self = Self(2);\n    pub const BADF: Self = Self(3);\n    pub const BUFFER_LEN: Self = Self(4);\n    pub const UNSUPPORTED: Self = FastlyStatus(5);\n    pub const HTTPINVALID: Self = FastlyStatus(7);\n    pub const HTTPUSER: Self = FastlyStatus(8);\n    pub const HTTPINCOMPLETE: Self = FastlyStatus(9);\n    pub const NONE: Self = FastlyStatus(10);\n    pub const HTTPHEADTOOLARGE: Self = FastlyStatus(11);\n    pub const HTTPINVALIDSTATUS: Self = FastlyStatus(12);\n    pub const LIMITEXCEEDED: Self = FastlyStatus(13);\n    pub const AGAIN: Self = FastlyStatus(14);\n}\n\nimpl From<crate::bindings::fastly::compute::types::Error> for FastlyStatus {\n    fn from(err: crate::bindings::fastly::compute::types::Error) -> Self {\n        use crate::bindings::fastly::compute::types::Error;\n        FastlyStatus(match err {\n            // use black_box here to prevent rustc/llvm from generating a switch table\n            Error::GenericError => std::hint::black_box(1),\n            Error::InvalidArgument => Self::INVALID_ARGUMENT.0,\n            Error::AuxiliaryError => 3,\n            Error::BufferLen(_) => 4,\n            Error::Unsupported => 5,\n            Error::HttpInvalid => 7,\n            Error::HttpUser => 8,\n            Error::HttpIncomplete => 9,\n            Error::CannotRead => 10,\n            Error::HttpHeadTooLarge => 11,\n            Error::HttpInvalidStatus => 12,\n            Error::LimitExceeded => 13,\n        })\n    }\n}\n\nimpl From<crate::bindings::fastly::compute::types::OpenError> for FastlyStatus {\n    fn from(err: crate::bindings::fastly::compute::types::OpenError) -> Self {\n        use crate::bindings::fastly::compute::types::OpenError;\n        // Map from `OpenError` to `FastlyStatus`. Not all functions use these\n        // mappings, individual functions sometimes special-case these to\n        // translate them differently.\n        match err {\n            OpenError::NotFound => FastlyStatus::NONE,\n            OpenError::Reserved | OpenError::NameTooLong | OpenError::InvalidSyntax => {\n                FastlyStatus::INVALID_ARGUMENT\n            }\n            // use black_box here to prevent rustc/llvm from generating a switch table\n            OpenError::Unsupported => std::hint::black_box(FastlyStatus::UNSUPPORTED),\n            OpenError::LimitExceeded => FastlyStatus::LIMITEXCEEDED,\n            OpenError::GenericError => FastlyStatus::UNKNOWN_ERROR,\n        }\n    }\n}\n\nimpl From<crate::bindings::fastly::compute::kv_store::KvError> for FastlyStatus {\n    fn from(err: crate::bindings::fastly::compute::kv_store::KvError) -> Self {\n        use crate::bindings::fastly::compute::kv_store::KvError;\n        match err {\n            // use black_box here to prevent rustc/llvm from generating a switch table\n            KvError::BadRequest | KvError::PreconditionFailed | KvError::PayloadTooLarge => {\n                std::hint::black_box(FastlyStatus::INVALID_ARGUMENT)\n            }\n            KvError::GenericError | KvError::InternalError | KvError::Extra(_) => {\n                FastlyStatus::UNKNOWN_ERROR\n            }\n            KvError::TooManyRequests => FastlyStatus::LIMITEXCEEDED,\n        }\n    }\n}\n\npub(crate) fn convert_result<T: Into<FastlyStatus>>(res: Result<(), T>) -> FastlyStatus {\n    match res {\n        Ok(()) => FastlyStatus::OK,\n        Err(e) => e.into(),\n    }\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/fastly/http_cache.rs",
    "content": "use super::{\n    convert_result, BodyHandle, CacheDurationNs, CacheHitCount, CacheLookupState,\n    CacheObjectLength, FastlyStatus, RequestHandle, ResponseHandle,\n};\n\nuse crate::{\n    alloc_result, alloc_result_opt, with_buffer, write_bool_result, write_bool_result_opt,\n    write_handle_result, write_result_opt, TrappingUnwrap,\n};\nuse core::mem::ManuallyDrop;\n\npub type HttpCacheHandle = u32;\npub type IsCacheable = u32;\npub type IsSensitive = u32;\n\npub const INVALID_HTTP_CACHE_HANDLE: HttpCacheHandle = HttpCacheHandle::MAX - 1;\n\n#[repr(u32)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n#[allow(unused)]\npub enum HttpStorageAction {\n    Insert,\n    Update,\n    DoNotStore,\n    RecordUncacheable,\n}\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct HttpCacheLookupOptions {\n    pub override_key_ptr: *const u8,\n    pub override_key_len: usize,\n    pub backend_name_ptr: *const u8,\n    pub backend_name_len: usize,\n}\n\nbitflags::bitflags! {\n    #[repr(transparent)]\n    pub struct HttpCacheLookupOptionsMask: u32 {\n        const _RESERVED = 1 << 0;\n        const OVERRIDE_KEY = 1 << 1;\n        const BACKEND_NAME = 1 << 2;\n    }\n}\n\n#[repr(C)]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub struct HttpCacheWriteOptions {\n    pub max_age_ns: CacheDurationNs,\n    pub vary_rule_ptr: *const u8,\n    pub vary_rule_len: usize,\n    pub initial_age_ns: CacheDurationNs,\n    pub stale_while_revalidate_ns: CacheDurationNs,\n    pub surrogate_keys_ptr: *const u8,\n    pub surrogate_keys_len: usize,\n    pub length: CacheObjectLength,\n    pub stale_if_error_ns: CacheDurationNs,\n}\n\nbitflags::bitflags! {\n    #[repr(transparent)]\n    pub struct HttpCacheWriteOptionsMask: u32 {\n        const _RESERVED = 1 << 0;\n        const VARY_RULE = 1 << 1;\n        const INITIAL_AGE_NS = 1 << 2;\n        const STALE_WHILE_REVALIDATE_NS = 1 << 3;\n        const SURROGATE_KEYS = 1 << 4;\n        const LENGTH = 1 << 5;\n        const SENSITIVE_DATA = 1 << 6;\n        const STALE_IF_ERROR_NS = 1 << 7;\n    }\n}\n\nmod http_cache {\n    use super::*;\n    use crate::bindings::fastly::compute::backend;\n    use crate::bindings::fastly::compute::http_cache as host;\n    use crate::bindings::fastly::compute::http_req as host_http_req;\n    use crate::bindings::fastly::compute::http_resp as host_http_resp;\n\n    fn cache_lookup_options(\n        mask: HttpCacheLookupOptionsMask,\n        options: *const HttpCacheLookupOptions,\n    ) -> Result<(Option<Vec<u8>>, Option<Vec<u8>>), FastlyStatus> {\n        macro_rules! make_vec {\n            ($ptr_field:ident, $len_field:ident) => {\n                unsafe { crate::make_vec!(main_ptr!((*options).$ptr_field), (*options).$len_field) }\n            };\n        }\n\n        let override_key = if mask.contains(HttpCacheLookupOptionsMask::OVERRIDE_KEY) {\n            Some(ManuallyDrop::into_inner(make_vec!(\n                override_key_ptr,\n                override_key_len\n            )))\n        } else {\n            None\n        };\n        let backend_name = if mask.contains(HttpCacheLookupOptionsMask::BACKEND_NAME) {\n            Some(ManuallyDrop::into_inner(make_vec!(\n                backend_name_ptr,\n                backend_name_len\n            )))\n        } else {\n            None\n        };\n\n        Ok((override_key, backend_name))\n    }\n\n    fn cache_write_options(\n        mask: HttpCacheWriteOptionsMask,\n        options: *const HttpCacheWriteOptions,\n    ) -> Result<host::WriteOptions<'static>, FastlyStatus> {\n        macro_rules! when_enabled {\n            ($flag:ident, $value:expr) => {\n                if mask.contains(HttpCacheWriteOptionsMask::$flag) {\n                    #[allow(unused_unsafe)]\n                    unsafe {\n                        Some($value)\n                    }\n                } else {\n                    None\n                }\n            };\n        }\n\n        macro_rules! make_string {\n            ($ptr_field:ident, $len_field:ident) => {\n                crate::make_string!(main_ptr!((*options).$ptr_field), (*options).$len_field)\n            };\n        }\n\n        let vary_rule = when_enabled!(\n            VARY_RULE,\n            ManuallyDrop::into_inner(make_string!(vary_rule_ptr, vary_rule_len))\n        );\n        let surrogate_keys = when_enabled!(\n            SURROGATE_KEYS,\n            ManuallyDrop::into_inner(make_string!(surrogate_keys_ptr, surrogate_keys_len))\n        );\n        let options = host::WriteOptions {\n            max_age_ns: unsafe { (*options).max_age_ns },\n            vary_rule,\n            initial_age_ns: when_enabled!(INITIAL_AGE_NS, (*options).initial_age_ns),\n            stale_while_revalidate_ns: when_enabled!(\n                STALE_WHILE_REVALIDATE_NS,\n                (*options).stale_while_revalidate_ns\n            ),\n            stale_if_error_ns: when_enabled!(STALE_IF_ERROR_NS, (*options).stale_if_error_ns),\n            surrogate_keys,\n            length: when_enabled!(LENGTH, (*options).length),\n            sensitive_data: mask.contains(HttpCacheWriteOptionsMask::SENSITIVE_DATA),\n            extra: None,\n        };\n\n        Ok(options)\n    }\n\n    impl From<host::StorageAction> for HttpStorageAction {\n        fn from(value: host::StorageAction) -> Self {\n            match value {\n                host::StorageAction::Insert => Self::Insert,\n                host::StorageAction::Update => Self::Update,\n                host::StorageAction::DoNotStore => Self::DoNotStore,\n                host::StorageAction::RecordUncacheable => Self::RecordUncacheable,\n            }\n        }\n    }\n\n    #[export_name = \"fastly_http_cache#is_request_cacheable\"]\n    pub fn is_request_cacheable(\n        req_handle: RequestHandle,\n        is_cacheable_out: *mut IsCacheable,\n    ) -> FastlyStatus {\n        let req_handle =\n            ManuallyDrop::new(unsafe { host_http_req::Request::from_handle(req_handle) });\n        let is_cacheable_out = unsafe_main_ptr!(is_cacheable_out);\n        write_bool_result!(host::is_request_cacheable(&req_handle), is_cacheable_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_suggested_cache_key\"]\n    pub fn get_suggested_cache_key(\n        req_handle: RequestHandle,\n        key_out_ptr: *mut u8,\n        key_out_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let req_handle =\n            ManuallyDrop::new(unsafe { host_http_req::Request::from_handle(req_handle) });\n        alloc_result!(\n            unsafe_main_ptr!(key_out_ptr),\n            key_out_len,\n            main_ptr!(nwritten_out),\n            {\n                host::get_suggested_cache_key(&req_handle, key_out_len.try_into().trapping_unwrap())\n            }\n        )\n    }\n\n    #[export_name = \"fastly_http_cache#lookup\"]\n    pub fn lookup(\n        req_handle: RequestHandle,\n        options_mask: HttpCacheLookupOptionsMask,\n        options: *const HttpCacheLookupOptions,\n        cache_handle_out: *mut HttpCacheHandle,\n    ) -> FastlyStatus {\n        let req_handle =\n            ManuallyDrop::new(unsafe { host_http_req::Request::from_handle(req_handle) });\n        let (override_key, backend_name) =\n            match cache_lookup_options(options_mask, unsafe_main_ptr!(options)) {\n                Ok(options) => options,\n                Err(err) => return convert_result(Err(err)),\n            };\n\n        let backend = if let Some(backend_name) = backend_name {\n            let res = backend::Backend::open(&backend_name);\n            std::mem::forget(backend_name);\n            let backend = match res {\n                Ok(backend) => backend,\n                Err(err) => {\n                    std::mem::forget(override_key);\n                    return convert_result(Err(err));\n                }\n            };\n            Some(backend)\n        } else {\n            None\n        };\n        let options = host::LookupOptions {\n            override_key,\n            backend: backend.as_ref(),\n            extra: None,\n        };\n\n        let res = host::Entry::transaction_lookup(&req_handle, &options);\n\n        std::mem::forget(options);\n        let cache_handle_out = unsafe_main_ptr!(cache_handle_out);\n        write_handle_result!(res, cache_handle_out)\n    }\n\n    #[export_name = \"fastly_http_cache#transaction_lookup\"]\n    pub fn transaction_lookup(\n        req_handle: RequestHandle,\n        options_mask: HttpCacheLookupOptionsMask,\n        options: *const HttpCacheLookupOptions,\n        cache_handle_out: *mut HttpCacheHandle,\n    ) -> FastlyStatus {\n        let req_handle =\n            ManuallyDrop::new(unsafe { host_http_req::Request::from_handle(req_handle) });\n        let (override_key, backend_name) =\n            match cache_lookup_options(options_mask, unsafe_main_ptr!(options)) {\n                Ok(options) => options,\n                Err(err) => return convert_result(Err(err)),\n            };\n\n        let backend = if let Some(backend_name) = backend_name {\n            let res = backend::Backend::open(&backend_name);\n            std::mem::forget(backend_name);\n            let backend = match res {\n                Ok(backend) => backend,\n                Err(err) => {\n                    std::mem::forget(override_key);\n                    return convert_result(Err(err));\n                }\n            };\n            Some(backend)\n        } else {\n            None\n        };\n        let options = host::LookupOptions {\n            override_key,\n            backend: backend.as_ref(),\n            extra: None,\n        };\n\n        let res = host::Entry::transaction_lookup(&req_handle, &options);\n\n        std::mem::forget(options);\n        let cache_handle_out = unsafe_main_ptr!(cache_handle_out);\n        write_handle_result!(res, cache_handle_out)\n    }\n\n    #[export_name = \"fastly_http_cache#transaction_insert\"]\n    pub fn transaction_insert(\n        handle: HttpCacheHandle,\n        resp_handle: ResponseHandle,\n        options_mask: HttpCacheWriteOptionsMask,\n        options: *const HttpCacheWriteOptions,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let resp_handle = unsafe { host_http_resp::Response::from_handle(resp_handle) };\n        let options = match cache_write_options(options_mask, unsafe_main_ptr!(options)) {\n            Ok(tuple) => tuple,\n            Err(err) => return err,\n        };\n\n        let res = handle.transaction_insert(resp_handle, &options);\n\n        std::mem::forget(options);\n        let body_handle_out = unsafe_main_ptr!(body_handle_out);\n        write_handle_result!(res, body_handle_out)\n    }\n\n    #[export_name = \"fastly_http_cache#transaction_insert_and_stream_back\"]\n    pub fn transaction_insert_and_stream_back(\n        handle: HttpCacheHandle,\n        resp_handle: ResponseHandle,\n        options_mask: HttpCacheWriteOptionsMask,\n        options: *const HttpCacheWriteOptions,\n        body_handle_out: *mut BodyHandle,\n        cache_handle_out: *mut HttpCacheHandle,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let resp_handle = unsafe { host_http_resp::Response::from_handle(resp_handle) };\n        let options = match cache_write_options(options_mask, unsafe_main_ptr!(options)) {\n            Ok(tuple) => tuple,\n            Err(err) => return err,\n        };\n\n        let res = handle.transaction_insert_and_stream_back(resp_handle, &options);\n\n        std::mem::forget(options);\n\n        match res {\n            Ok((body_handle, cache_handle)) => {\n                unsafe {\n                    *main_ptr!(body_handle_out) = body_handle.take_handle();\n                    *main_ptr!(cache_handle_out) = cache_handle.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_cache#transaction_update\"]\n    pub fn transaction_update(\n        handle: HttpCacheHandle,\n        resp_handle: ResponseHandle,\n        options_mask: HttpCacheWriteOptionsMask,\n        options: *const HttpCacheWriteOptions,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let resp_handle = unsafe { host_http_resp::Response::from_handle(resp_handle) };\n        let options = match cache_write_options(options_mask, unsafe_main_ptr!(options)) {\n            Ok(tuple) => tuple,\n            Err(err) => return err,\n        };\n\n        let res = handle.transaction_update(resp_handle, &options);\n\n        std::mem::forget(options);\n\n        convert_result(res)\n    }\n\n    #[export_name = \"fastly_http_cache#transaction_update_and_return_fresh\"]\n    pub fn transaction_update_and_return_fresh(\n        handle: HttpCacheHandle,\n        resp_handle: ResponseHandle,\n        options_mask: HttpCacheWriteOptionsMask,\n        options: *const HttpCacheWriteOptions,\n        cache_handle_out: *mut HttpCacheHandle,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let resp_handle = unsafe { host_http_resp::Response::from_handle(resp_handle) };\n\n        let options = match cache_write_options(options_mask, unsafe_main_ptr!(options)) {\n            Ok(tuple) => tuple,\n            Err(err) => return err,\n        };\n\n        let res = handle.transaction_update_and_return_fresh(resp_handle, &options);\n\n        std::mem::forget(options);\n        let cache_handle_out = unsafe_main_ptr!(cache_handle_out);\n        write_handle_result!(res, cache_handle_out)\n    }\n\n    #[export_name = \"fastly_http_cache#transaction_record_not_cacheable\"]\n    pub fn transaction_record_not_cacheable(\n        handle: HttpCacheHandle,\n        options_mask: HttpCacheWriteOptionsMask,\n        options: *const HttpCacheWriteOptions,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let options = match cache_write_options(options_mask, unsafe_main_ptr!(options)) {\n            Ok(tuple) => tuple,\n            Err(err) => return err,\n        };\n\n        let res = handle.transaction_record_not_cacheable(&options);\n\n        std::mem::forget(options);\n\n        convert_result(res)\n    }\n\n    #[export_name = \"fastly_http_cache#transaction_choose_stale\"]\n    pub fn transaction_choose_stale(handle: HttpCacheHandle) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        convert_result(handle.transaction_choose_stale())\n    }\n\n    #[export_name = \"fastly_http_cache#transaction_abandon\"]\n    pub fn transaction_abandon(handle: HttpCacheHandle) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        convert_result(handle.transaction_abandon())\n    }\n\n    #[export_name = \"fastly_http_cache#close\"]\n    pub fn close(handle: HttpCacheHandle) -> FastlyStatus {\n        let handle = unsafe { host::Entry::from_handle(handle) };\n        convert_result(host::close_entry(handle))\n    }\n\n    #[export_name = \"fastly_http_cache#get_suggested_backend_request\"]\n    pub fn get_suggested_backend_request(\n        handle: HttpCacheHandle,\n        req_handle_out: *mut RequestHandle,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let req_handle_out = unsafe_main_ptr!(req_handle_out);\n        write_handle_result!(handle.get_suggested_backend_request(), req_handle_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_suggested_cache_options\"]\n    pub fn get_suggested_cache_options(\n        handle: HttpCacheHandle,\n        resp_handle: ResponseHandle,\n        requested: HttpCacheWriteOptionsMask,\n        options: *const HttpCacheWriteOptions,\n        options_mask_out: *mut HttpCacheWriteOptionsMask,\n        options_out: *mut HttpCacheWriteOptions,\n    ) -> FastlyStatus {\n        let options_mask_out = unsafe {\n            match main_ptr!(options_mask_out).as_mut() {\n                Some(mask) => mask,\n                None => return FastlyStatus::INVALID_ARGUMENT,\n            }\n        };\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let resp_handle =\n            ManuallyDrop::new(unsafe { host_http_resp::Response::from_handle(resp_handle) });\n\n        let res = match handle.get_suggested_write_options(&resp_handle) {\n            Ok(res) => res,\n            Err(e) => return e.into(),\n        };\n\n        let options = unsafe_main_ptr!(options);\n        let options_out = unsafe_main_ptr!(options_out);\n        // max_age_ns is not optional\n        unsafe {\n            (*options_out).max_age_ns = res.get_max_age_ns();\n        }\n\n        let mut buffer_len_error = false;\n\n        if requested.contains(HttpCacheWriteOptionsMask::VARY_RULE) {\n            options_mask_out.insert(HttpCacheWriteOptionsMask::VARY_RULE);\n\n            let vary_len = unsafe { (*options).vary_rule_len };\n            let vary_nwritten = unsafe { &mut (*options_out).vary_rule_len as *mut _ };\n\n            with_buffer!(\n                unsafe { main_ptr!((*options).vary_rule_ptr) as *mut _ },\n                vary_len,\n                { res.get_vary_rule(vary_len.try_into().trapping_unwrap()) },\n                |res| {\n                    match res {\n                        Ok(res) => {\n                            unsafe { *vary_nwritten = res.len() };\n                            std::mem::forget(res);\n                        }\n\n                        Err(host::Error::BufferLen(len)) => {\n                            unsafe { *vary_nwritten = usize::try_from(len).unwrap_or(0) };\n                            buffer_len_error = true;\n                        }\n\n                        Err(e) => return Err(e.into()),\n                    }\n                }\n            );\n        }\n\n        if requested.contains(HttpCacheWriteOptionsMask::SURROGATE_KEYS) {\n            options_mask_out.insert(HttpCacheWriteOptionsMask::SURROGATE_KEYS);\n\n            let surrogate_keys_len = unsafe { (*options).surrogate_keys_len };\n            let surrogate_keys_nwritten =\n                unsafe { &mut (*options_out).surrogate_keys_len as *mut _ };\n\n            with_buffer!(\n                unsafe { main_ptr!((*options).surrogate_keys_ptr) as *mut _ },\n                surrogate_keys_len,\n                { res.get_surrogate_keys(surrogate_keys_len.try_into().trapping_unwrap()) },\n                |res| {\n                    match res {\n                        Ok(res) => {\n                            unsafe { *surrogate_keys_nwritten = res.len() };\n                            std::mem::forget(res);\n                        }\n\n                        Err(host::Error::BufferLen(len)) => {\n                            unsafe { *surrogate_keys_nwritten = usize::try_from(len).unwrap_or(0) };\n                            buffer_len_error = true;\n                        }\n\n                        Err(e) => return Err(e.into()),\n                    }\n                }\n            );\n        }\n\n        if requested.contains(HttpCacheWriteOptionsMask::INITIAL_AGE_NS) {\n            options_mask_out.insert(HttpCacheWriteOptionsMask::INITIAL_AGE_NS);\n            unsafe {\n                (*options_out).initial_age_ns = res.get_initial_age_ns();\n            }\n        }\n\n        if requested.contains(HttpCacheWriteOptionsMask::STALE_WHILE_REVALIDATE_NS) {\n            options_mask_out.insert(HttpCacheWriteOptionsMask::STALE_WHILE_REVALIDATE_NS);\n            unsafe {\n                (*options_out).stale_while_revalidate_ns = res.get_stale_while_revalidate_ns();\n            }\n        }\n        if requested.contains(HttpCacheWriteOptionsMask::STALE_IF_ERROR_NS) {\n            options_mask_out.insert(HttpCacheWriteOptionsMask::STALE_IF_ERROR_NS);\n            unsafe {\n                (*options_out).stale_if_error_ns = res.get_stale_if_error_ns();\n            }\n        }\n\n        if requested.contains(HttpCacheWriteOptionsMask::LENGTH) {\n            if let Some(len) = res.get_length() {\n                options_mask_out.insert(HttpCacheWriteOptionsMask::LENGTH);\n                unsafe {\n                    (*options_out).length = len;\n                }\n            }\n        }\n\n        if requested.contains(HttpCacheWriteOptionsMask::SENSITIVE_DATA) && res.get_sensitive_data()\n        {\n            options_mask_out.insert(HttpCacheWriteOptionsMask::SENSITIVE_DATA);\n        }\n\n        if buffer_len_error {\n            FastlyStatus::BUFFER_LEN\n        } else {\n            FastlyStatus::OK\n        }\n    }\n\n    #[export_name = \"fastly_http_cache#prepare_response_for_storage\"]\n    pub fn prepare_response_for_storage(\n        handle: HttpCacheHandle,\n        resp_handle: ResponseHandle,\n        http_storage_action_out: *mut HttpStorageAction,\n        resp_handle_out: *mut ResponseHandle,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let resp_handle =\n            ManuallyDrop::new(unsafe { host_http_resp::Response::from_handle(resp_handle) });\n        match handle.prepare_response_for_storage(&resp_handle) {\n            Ok((action, resp_handle)) => {\n                unsafe {\n                    *main_ptr!(http_storage_action_out) = action.into();\n                    *main_ptr!(resp_handle_out) = resp_handle.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_cache#get_found_response\"]\n    pub fn get_found_response(\n        handle: HttpCacheHandle,\n        transform_for_client: u32,\n        resp_handle_out: *mut ResponseHandle,\n        body_handle_out: *mut BodyHandle,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        match handle.get_found_response(transform_for_client) {\n            Ok(Some((resp_handle, body_handle))) => {\n                unsafe {\n                    *main_ptr!(resp_handle_out) = resp_handle.take_handle();\n                    *main_ptr!(body_handle_out) = body_handle.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n\n            Ok(None) => FastlyStatus::NONE,\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_cache#get_state\"]\n    pub fn get_state(\n        handle: HttpCacheHandle,\n        cache_lookup_state_out: *mut CacheLookupState,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        match handle.get_state() {\n            Ok(res) => {\n                unsafe {\n                    *main_ptr!(cache_lookup_state_out) = res.into();\n                }\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    }\n\n    #[export_name = \"fastly_http_cache#get_length\"]\n    pub fn get_length(handle: HttpCacheHandle, length_out: *mut CacheObjectLength) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let length_out = unsafe_main_ptr!(length_out);\n        write_result_opt!(handle.get_length(), length_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_max_age_ns\"]\n    pub fn get_max_age_ns(\n        handle: HttpCacheHandle,\n        duration_out: *mut CacheDurationNs,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let duration_out = unsafe_main_ptr!(duration_out);\n        write_result_opt!(handle.get_max_age_ns(), duration_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_stale_while_revalidate_ns\"]\n    pub fn get_stale_while_revalidate_ns(\n        handle: HttpCacheHandle,\n        duration_out: *mut CacheDurationNs,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let duration_out = unsafe_main_ptr!(duration_out);\n        write_result_opt!(handle.get_stale_while_revalidate_ns(), duration_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_stale_if_error_ns\"]\n    pub fn get_stale_if_error_ns(\n        handle: HttpCacheHandle,\n        duration_out: *mut CacheDurationNs,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let duration_out = unsafe_main_ptr!(duration_out);\n        write_result_opt!(handle.get_stale_if_error_ns(), duration_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_age_ns\"]\n    pub fn get_age_ns(handle: HttpCacheHandle, duration_out: *mut CacheDurationNs) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let duration_out = unsafe_main_ptr!(duration_out);\n        write_result_opt!(handle.get_age_ns(), duration_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_hits\"]\n    pub fn get_hits(handle: HttpCacheHandle, hits_out: *mut CacheHitCount) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let hits_out = unsafe_main_ptr!(hits_out);\n        write_result_opt!(handle.get_hits(), hits_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_sensitive_data\"]\n    pub fn get_sensitive_data(\n        handle: HttpCacheHandle,\n        sensitive_data_out: *mut IsSensitive,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        let sensitive_data_out = unsafe_main_ptr!(sensitive_data_out);\n        write_bool_result_opt!(handle.get_sensitive_data(), sensitive_data_out)\n    }\n\n    #[export_name = \"fastly_http_cache#get_surrogate_keys\"]\n    pub fn get_surrogate_keys(\n        handle: HttpCacheHandle,\n        surrogate_keys_out_ptr: *mut u8,\n        surrogate_keys_out_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(surrogate_keys_out_ptr),\n            surrogate_keys_out_len,\n            main_ptr!(nwritten_out),\n            { handle.get_surrogate_keys(surrogate_keys_out_len.try_into().trapping_unwrap(),) }\n        )\n    }\n\n    #[export_name = \"fastly_http_cache#get_vary_rule\"]\n    pub fn get_vary_rule(\n        handle: HttpCacheHandle,\n        vary_rule_out_ptr: *mut u8,\n        vary_rule_out_len: usize,\n        nwritten_out: *mut usize,\n    ) -> FastlyStatus {\n        let handle = ManuallyDrop::new(unsafe { host::Entry::from_handle(handle) });\n        alloc_result_opt!(\n            unsafe_main_ptr!(vary_rule_out_ptr),\n            vary_rule_out_len,\n            main_ptr!(nwritten_out),\n            { handle.get_vary_rule(vary_rule_out_len.try_into().trapping_unwrap()) }\n        )\n    }\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/fastly/macros.rs",
    "content": "#[macro_export]\nmacro_rules! with_buffer {\n    ($buf:expr, $len:expr, $alloc:block, |$res:ident| $free:block) => {\n        crate::State::with::<FastlyStatus>(|state| {\n            let $res = state.with_one_import_alloc($buf, $len, || $alloc);\n            $free;\n            Ok(())\n        })\n    };\n}\n\n#[macro_export]\nmacro_rules! alloc_result {\n    ($buf:expr, $len:expr, $nwritten:expr, $block:block) => {\n        crate::with_buffer!($buf, $len, $block, |res| {\n            let res = crate::handle_buffer_len!(res, $nwritten);\n            unsafe {\n                *$nwritten = res.len();\n            }\n\n            std::mem::forget(res);\n        })\n    };\n}\n\n#[macro_export]\nmacro_rules! alloc_result_opt {\n    ($buf:expr, $len:expr, $nwritten:expr, $block:block) => {\n        crate::with_buffer!($buf, $len, $block, |res| {\n            let res = crate::handle_buffer_len!(res, $nwritten).ok_or(FastlyStatus::NONE)?;\n            unsafe {\n                *$nwritten = res.len();\n            }\n\n            std::mem::forget(res);\n        })\n    };\n}\n\n#[macro_export]\nmacro_rules! handle_buffer_len {\n    ($res:ident, $nwritten:expr) => {\n        match $res {\n            Ok(res) => res,\n            Err(err) => {\n                if let crate::bindings::fastly::compute::types::Error::BufferLen(needed) = err {\n                    unsafe {\n                        *$nwritten =\n                            crate::TrappingUnwrap::trapping_unwrap(usize::try_from(needed));\n                    }\n                }\n\n                return Err(err.into());\n            }\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! write_result {\n    ($res:expr, $out:ident) => {\n        match $res {\n            Ok(val) => {\n                unsafe {\n                    *$out = val;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! write_result_opt {\n    ($res:expr, $out:ident) => {\n        match $res {\n            Ok(Some(val)) => {\n                unsafe {\n                    *$out = val;\n                }\n\n                FastlyStatus::OK\n            }\n\n            Ok(None) => FastlyStatus::NONE,\n\n            Err(e) => e.into(),\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! write_bool_result {\n    ($res:expr, $out:ident) => {\n        match $res {\n            Ok(val) => {\n                unsafe {\n                    *$out = if val { 1 } else { 0 };\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! write_bool_result_opt {\n    ($res:expr, $out:ident) => {\n        match $res {\n            Ok(Some(val)) => {\n                unsafe {\n                    *$out = if val { 1 } else { 0 };\n                }\n\n                FastlyStatus::OK\n            }\n\n            Ok(None) => FastlyStatus::NONE,\n\n            Err(e) => e.into(),\n        }\n    };\n}\n\n/// Construct a temporary `&[T]` containing the given pointer and length.\n#[macro_export]\nmacro_rules! make_slice {\n    ($ptr:expr, $len:expr) => {{\n        let ptr = $ptr as *mut _;\n        let len = $crate::TrappingUnwrap::trapping_unwrap(usize::try_from($len));\n        #[allow(unused_unsafe)]\n        unsafe {\n            core::slice::from_raw_parts(ptr, len)\n        }\n    }};\n}\n\n/// Construct a `ManuallyDrop<Vec>` containing the given pointer and length.\n#[macro_export]\nmacro_rules! make_vec {\n    ($ptr:expr, $len:expr) => {{\n        let ptr = $ptr as *mut _;\n        let len = $crate::TrappingUnwrap::trapping_unwrap(usize::try_from($len));\n        #[allow(unused_unsafe)]\n        core::mem::ManuallyDrop::new(unsafe { Vec::from_raw_parts(ptr, len, len) })\n    }};\n}\n\n/// Construct a `&str` containing the given pointer and length.\n#[macro_export]\nmacro_rules! make_str {\n    ($ptr:expr, $len:expr) => {{\n        $crate::make_slice!($ptr, $len)\n    }};\n}\n\n/// Construct a `ManuallyDrop<Vec<u8>>` containing the given pointer and length.\n///\n/// This would use `String`, except that we use `raw_strings` for better\n/// compatibility with the witx ABI. The canonical ABI will check for UTF-8\n/// validity.\n#[macro_export]\nmacro_rules! make_string {\n    ($ptr:expr, $len:expr) => {{\n        $crate::make_vec!($ptr, $len)\n    }};\n}\n\n#[macro_export]\nmacro_rules! write_handle_result {\n    ($res:expr, $out:ident) => {\n        match $res {\n            Ok(val) => {\n                unsafe {\n                    *$out = val.take_handle();\n                }\n\n                FastlyStatus::OK\n            }\n\n            Err(e) => e.into(),\n        }\n    };\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/fastly/mod.rs",
    "content": "mod cache;\nmod config_store;\nmod core;\nmod error;\nmod http_cache;\nmod macros;\n\npub(crate) use error::*;\n\npub use cache::*;\npub use config_store::*;\npub use core::*;\npub use http_cache::*;\n\n/// Decode an IP address from `ip_len` bytes pointed to by `ip_octets` into a Wit `IpAddress`.\npub(crate) unsafe fn decode_ip_address(\n    ip_octets: *const u8,\n    ip_len: usize,\n) -> Option<crate::bindings::fastly::compute::types::IpAddress> {\n    let ip = std::slice::from_raw_parts(ip_octets, ip_len);\n    if let Ok(bytes) = <[u8; 4]>::try_from(ip) {\n        Some(crate::bindings::fastly::compute::types::IpAddress::Ipv4(\n            bytes.into(),\n        ))\n    } else if let Ok(bytes) = <[u8; 16]>::try_from(ip) {\n        Some(crate::bindings::fastly::compute::types::IpAddress::Ipv6(\n            std::net::Ipv6Addr::from(bytes).segments().into(),\n        ))\n    } else {\n        None\n    }\n}\n\n/// Encode a Wit `IpAddress` into the `ip_octets` buffer, and return the number of bytes written.\npub(crate) unsafe fn encode_ip_address(\n    ip_addr: crate::bindings::fastly::compute::types::IpAddress,\n    ip_octets: *mut u8,\n) -> usize {\n    let bytes = match ip_addr {\n        crate::bindings::fastly::compute::types::IpAddress::Ipv4(bytes) => {\n            &<[u8; 4]>::from(bytes)[..]\n        }\n        crate::bindings::fastly::compute::types::IpAddress::Ipv6(segments) => {\n            &std::net::Ipv6Addr::from(<[u16; 8]>::from(segments)).octets()[..]\n        }\n    };\n    std::ptr::copy_nonoverlapping(bytes.as_ptr(), ip_octets, bytes.len());\n    bytes.len()\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/lib.rs",
    "content": "// Promote warnings into errors, when building in release mode.\n#![cfg_attr(not(debug_assertions), deny(warnings))]\n\n/// After performing the instrumentation from `crates/xqd-codegen/src/shift_mem.rs`,\n/// the adapter is now free to take the first two pages of the Wasm memory.\n/// This means the adapter code deviates from the upstream repo in the following ways:\n///  1) In `build.rs`, set the `__stack_pointer` global to 64k.\n///     That is, the adapter stack takes the first page of memory.\n///     Set the `allocation_state` global to `StackAllocated`, so that we don't\n///     trigger the [`allocate_stack_via_realloc` function](https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wit-component/src/gc.rs#L122) in wit-component.\n///  2) In `src/lib.rs`, the `State::new()` function uses the hard-coded address\n///     `65536 as *mut State` to write the state, removing the imported `cabi_realloc`\n///     function. This means, the state takes the second page of the memory.\n///  3) The rest of the adapter code uses the `main_ptr!` macro to indicate which\n///     pointers come from the main module, and adds the offset before dereferencing\n///     the value throughout the whole adapter code.\n///  4) When the adapter returns a pointer back to the main module, we need to keep\n///     the lie by subtracting offset. This is done via the `unshift_ptr` macro.\n///     This only happens in the WASI `args_get` and `environ_get` API.\n///     Fastly APIs never return pointers. They use resources to store pointers,\n///     which lives on the host side, so we don't need to change any code related to resources.\nuse crate::bindings::wasi::clocks::{monotonic_clock, wall_clock};\nuse crate::bindings::wasi::io::poll;\nuse crate::bindings::wasi::io::streams;\nuse crate::bindings::wasi::random::random;\nuse crate::fastly::INVALID_HANDLE;\nuse core::cell::{Cell, RefCell, RefMut, UnsafeCell};\nuse core::ffi::c_void;\nuse core::mem::{self, align_of, forget, size_of, MaybeUninit};\nuse core::ops::{Deref, DerefMut};\nuse core::ptr::null_mut;\nuse core::slice;\nuse poll::Pollable;\nuse wasi::*;\n\n// test\n\n#[macro_use]\nmod macros;\n\npub mod fastly;\n\nmod descriptors;\n\nuse crate::descriptors::{Descriptor, Descriptors, StreamType};\n\n// This const should always equal to the OFFSET defined in crates/xqd-codegen/src/shift_mem.rs\n#[cfg(not(feature = \"noshift\"))]\npub const OFFSET: usize = 2 * PAGE_SIZE;\n\npub mod bindings {\n    // When building the normal adapter, use the adapter-service\n    // world which includes the imports and exports.\n    #[cfg(feature = \"exports\")]\n    wit_bindgen_rust_macro::generate!({\n        path: \"../wit\",\n        world: \"fastly:adapter/adapter-service\",\n        std_feature,\n        raw_strings,\n        runtime_path: \"crate::bindings::wit_bindgen_rt_shim\",\n        disable_run_ctors_once_workaround: true,\n        skip: [\"poll\", \"select\", \"select-with-timeout\"],\n        generate_all,\n        disable_custom_section_link_helpers: true,\n    });\n\n    // When not building the adapter with \"exports\", use the\n    // adapter-service-imports world which just has the imports.\n    #[cfg(not(feature = \"exports\"))]\n    wit_bindgen_rust_macro::generate!({\n        path: \"../wit\",\n        world: \"fastly:adapter/adapter-service-imports\",\n        std_feature,\n        raw_strings,\n        runtime_path: \"crate::bindings::wit_bindgen_rt_shim\",\n        disable_run_ctors_once_workaround: true,\n        skip: [\"poll\", \"select\", \"select-with-timeout\"],\n        generate_all,\n        disable_custom_section_link_helpers: true,\n    });\n\n    pub mod wit_bindgen_rt_shim {\n        pub use bitflags;\n\n        pub fn maybe_link_cabi_realloc() {}\n    }\n\n    #[cfg(feature = \"exports\")]\n    pub struct ComponentAdapter;\n\n    #[cfg(feature = \"exports\")]\n    impl exports::fastly::compute::http_incoming::Guest for ComponentAdapter {\n        fn handle(\n            req: fastly::compute::http_req::Request,\n            body: fastly::compute::http_body::Body,\n        ) -> Result<(), ()> {\n            #[link(wasm_import_module = \"__main_module__\")]\n            extern \"C\" {\n                fn _start();\n            }\n\n            let res = crate::State::with::<crate::fastly::FastlyStatus>(|state| {\n                let old = state.request.replace(Some(req.take_handle()));\n                assert!(old.is_none());\n                let old = state.request_body.replace(Some(body.take_handle()));\n                assert!(old.is_none());\n                Ok(())\n            });\n\n            unsafe {\n                _start();\n            }\n\n            // Don't bother making hostcalls to drop these, as the host\n            // will clean them up for us automatically. This also avoids\n            // trouble if the user has already dropped them, after obtaining\n            // them via `body_downstream_get`.\n            std::mem::forget(req);\n            std::mem::forget(body);\n\n            if res == crate::fastly::FastlyStatus::OK {\n                Ok(())\n            } else {\n                Err(())\n            }\n        }\n    }\n\n    #[cfg(feature = \"exports\")]\n    export!(ComponentAdapter);\n}\n\n// The unwrap/expect methods in std pull panic when they fail, which pulls\n// in unwinding machinery that we can't use in the adapter. Instead, use this\n// extension trait to get postfixed upwrap on Option and Result.\ntrait TrappingUnwrap<T> {\n    fn trapping_unwrap(self) -> T;\n}\n\nimpl<T> TrappingUnwrap<T> for Option<T> {\n    fn trapping_unwrap(self) -> T {\n        match self {\n            Some(t) => t,\n            None => unreachable!(),\n        }\n    }\n}\n\nimpl<T, E> TrappingUnwrap<T> for Result<T, E> {\n    fn trapping_unwrap(self) -> T {\n        match self {\n            Ok(t) => t,\n            Err(_) => unreachable!(),\n        }\n    }\n}\n\n/// Allocate a file descriptor which will generate an `ERRNO_BADF` if passed to\n/// any WASI Preview 1 function implemented by this adapter.\n///\n/// This is intended for use by `wasi-libc` during its incremental transition\n/// from WASI Preview 1 to Preview 2.  It will use this function to reserve\n/// descriptors for its own use, valid only for use with libc functions.\n#[no_mangle]\npub unsafe extern \"C\" fn adapter_open_badfd(fd: *mut u32) -> Errno {\n    State::with::<Errno>(|state| {\n        *main_ptr!(fd) = state.descriptors_mut().open(Descriptor::Bad)?;\n        Ok(())\n    })\n}\n\n/// Close a descriptor previously opened using `adapter_open_badfd`.\n#[no_mangle]\npub unsafe extern \"C\" fn adapter_close_badfd(fd: u32) -> Errno {\n    State::with::<Errno>(|state| state.descriptors_mut().close(fd))\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn reset_adapter_state() {\n    let state = get_state_ptr();\n    if !state.is_null() {\n        State::init(state)\n    }\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn cabi_import_realloc(\n    old_ptr: *mut u8,\n    old_size: usize,\n    align: usize,\n    new_size: usize,\n) -> *mut u8 {\n    let mut ptr = null_mut::<u8>();\n    State::with::<Errno>(|state| {\n        let mut alloc = state.import_alloc.replace(ImportAlloc::None);\n        ptr = alloc.alloc(old_ptr, old_size, align, new_size);\n        state.import_alloc.set(alloc);\n        Ok(())\n    });\n    ptr\n}\n\n/// Different ways that calling imports can allocate memory.\n///\n/// This behavior is used to customize the behavior of `cabi_import_realloc`.\n/// This is configured within `State` whenever an import is called that may\n/// invoke `cabi_import_realloc`.\n///\n/// The general idea behind these various behaviors of import allocation is\n/// that we're limited for space in the adapter here to 1 page of memory but\n/// that may not fit the total sum of arguments, environment variables, and\n/// preopens. WASIp1 APIs all provide user-provided buffers as well for these\n/// allocations so we technically don't need to store them in the adapter\n/// itself. Instead what this does is it tries to copy strings and such directly\n/// into their destination pointers where possible.\n///\n/// The types requiring allocation in the WASIp2 APIs that the WASIp1 APIs call\n/// are relatively simple. They all look like `list<T>` where `T` only has\n/// indirections in the form of `String`. This means that we can apply a\n/// \"clever\" hack where the alignment of an allocation is used to disambiguate\n/// whether we're allocating a string or allocating the `list<T>` allocation.\n/// This signal with alignment means that we can configure where everything\n/// goes.\n///\n/// For example consider `args_sizes_get` and `args_get`. When `args_sizes_get`\n/// is called the `list<T>` allocation happens first with alignment 4. This\n/// must be valid for the rest of the strings since the canonical ABI will fill\n/// it in, so it's allocated from `State::temporary_data`. Next all other\n/// arguments will be `string` type with alignment 1. These are also allocated\n/// within `State::temporary_data` but they're \"allocated on top of one\n/// another\" meaning that internal allocator state isn't updated after a string\n/// is allocated. While these strings are discarded their sizes are all summed\n/// up and returned from `args_sizes_get`.\n///\n/// Later though when `args_get` is called it's a similar allocation strategy\n/// except that strings are instead redirected to the allocation provided to\n/// `args_get` itself. This enables strings to be directly allocated into their\n/// destinations.\n///\n/// Overall this means that we're limiting the maximum number of arguments plus\n/// the size of the largest string, but otherwise we're not limiting the total\n/// size of all arguments (or env vars, preopens, etc).\nenum ImportAlloc {\n    /// A single allocation from the provided `BumpAlloc` is supported. After\n    /// the single allocation is performed all future allocations will fail.\n    OneAlloc(BumpAlloc),\n\n    /// An allocator intended for `list<T>` where `T` has string types but no\n    /// other indirections. String allocations are discarded but counted for\n    /// size.\n    ///\n    /// This allocator will use `alloc` for all allocations. Any string-related\n    /// allocation, detected via an alignment of 1, is considered \"temporary\"\n    /// and doesn't affect the internal state of the allocator. The allocation\n    /// is assumed to not be used after the import call returns.\n    ///\n    /// The total sum of all string sizes, however, is accumulated within\n    /// `strings_size`.\n    CountAndDiscardStrings {\n        strings_size: usize,\n        alloc: BumpAlloc,\n    },\n\n    /// An allocator intended for `list<T>` where `T` has string types but no\n    /// other indirections. String allocations go into `strings` and the\n    /// `list<..>` allocation goes into `pointers`.\n    ///\n    /// This allocator enables placing strings within a caller-supplied buffer\n    /// configured with `strings`. The `pointers` allocation is\n    /// `State::temporary_data`.\n    ///\n    /// This will additionally over-allocate strings with one extra byte to be\n    /// nul-terminated or `=`-terminated in the case of env vars.\n    SeparateStringsAndPointers {\n        strings: BumpAlloc,\n        pointers: BumpAlloc,\n    },\n\n    /// No import allocator is configured and if an allocation happens then\n    /// this will abort.\n    None,\n}\n\nimpl ImportAlloc {\n    /// To be used by cabi_import_realloc only!\n    unsafe fn alloc(\n        &mut self,\n        old_ptr: *mut u8,\n        old_size: usize,\n        align: usize,\n        size: usize,\n    ) -> *mut u8 {\n        // This is ... a hack. This is a hack in subtle ways that is quite\n        // brittle and may break over time. There's only one case for the\n        // `realloc`-like-behavior in the canonical ABI and that's when the host\n        // is transferring a string to the guest and the host has a different\n        // string encoding. For example JS uses utf-16 (ish) and Rust/WASIp1 use\n        // utf-8. That means that when this adapter is used with a JS host\n        // realloc behavior may be triggered in which case `old_ptr` may not be\n        // null.\n        //\n        // In the case that `old_ptr` may not be null we come to the first\n        // brittle assumption: it's assumed that this is shrinking memory. In\n        // the canonical ABI overlarge allocations are made originally and then\n        // shrunk afterwards once encoding is finished. This means that the\n        // first allocation is too big and the `realloc` call is shrinking\n        // memory. This assumption may be violated in the future if the\n        // canonical ABI is updated to handle growing strings in addition to\n        // shrinking strings. (e.g. starting with an assume-ascii path and then\n        // falling back to an ok-needs-more-space path for larger unicode code\n        // points).\n        //\n        // This comes to the second brittle assumption, nothing happens here\n        // when a shrink happens. This is brittle for each of the cases below,\n        // enumerated here:\n        //\n        // * For `OneAlloc` this isn't the end of the world. That's already\n        //   asserting that only a single string is allocated. Returning the\n        //   original pointer keeps the pointer the same and the host will keep\n        //   track of the appropriate length. In this case the final length is\n        //   read out of the return value of a function, meaning that everything\n        //   actually works out here.\n        //\n        // * For `CountAndDiscardStrings` we're relying on the fact that\n        //   this is only used for `environ_sizes_get` and `args_sizes_get`. In\n        //   both situations we're actually going to return an \"overlarge\"\n        //   return value for the size of arguments and return values. By\n        //   assuming memory shrinks after the first allocation the return value\n        //   of `environ_sizes_get` and `args_sizes_get` will be the overlong\n        //   approximation for all strings. That means that the final exact size\n        //   won't be what's returned. This ends up being ok because technically\n        //   nothing about WASI says that those blocks have to be exact-sized.\n        //   In our case we're (ab)using that to force the caller to make an\n        //   overlarge return area which we'll allocate into. All-in-all we\n        //   don't track the shrink request and ignore the size.\n        //\n        // * For `SeparateStringsAndPointers` it's similar to the previous case\n        //   except the weird part is that the caller is providing the\n        //   argument/env space buffer to write into. It's over-large because of\n        //   the case of `CountAndDiscardStrings` above, but we'll exploit that\n        //   here and end up having space between all the arguments. Technically\n        //   WASI doesn't say all the strings have to be adjacent, so this\n        //   should work out in practice.\n        //\n        // Basically it's a case-by-case basis here that enables ignoring\n        // shrinking return calls here. Not robust.\n        if !old_ptr.is_null() {\n            assert!(old_size > size);\n            assert_eq!(align, 1);\n            return old_ptr;\n        }\n        match self {\n            ImportAlloc::OneAlloc(alloc) => {\n                let ret = alloc.alloc(align, size);\n                *self = ImportAlloc::None;\n                ret\n            }\n            ImportAlloc::SeparateStringsAndPointers { strings, pointers } => {\n                if align == 1 {\n                    strings.alloc(align, size + 1)\n                } else {\n                    pointers.alloc(align, size)\n                }\n            }\n            ImportAlloc::CountAndDiscardStrings {\n                strings_size,\n                alloc,\n            } => {\n                if align == 1 {\n                    *strings_size += size;\n                    alloc.clone().alloc(align, size)\n                } else {\n                    alloc.alloc(align, size)\n                }\n            }\n            ImportAlloc::None => {\n                unreachable!(\"no allocator configured\")\n            }\n        }\n    }\n}\n\n/// Helper type to manage allocations from a `base`/`len` combo.\n///\n/// This isn't really used much in an arena-style per se but it's used in\n/// combination with the `ImportAlloc` flavors above.\n#[derive(Clone)]\nstruct BumpAlloc {\n    base: *mut u8,\n    len: usize,\n}\n\nimpl BumpAlloc {\n    unsafe fn alloc(&mut self, align: usize, size: usize) -> *mut u8 {\n        self.align_to(align);\n        if size > self.len {\n            unreachable!(\"allocation size is too large\")\n        }\n        self.len -= size;\n        let ret = self.base;\n        self.base = ret.add(size);\n        ret\n    }\n\n    unsafe fn align_to(&mut self, align: usize) {\n        if !align.is_power_of_two() {\n            unreachable!(\"invalid alignment\");\n        }\n        let align_offset = self.base.align_offset(align);\n        if align_offset > self.len {\n            unreachable!(\"failed to allocate\")\n        }\n        self.len -= align_offset;\n        self.base = self.base.add(align_offset);\n    }\n}\n\n#[link(wasm_import_module = \"wasi:cli/environment@0.2.6\")]\nextern \"C\" {\n    #[link_name = \"get-arguments\"]\n    fn wasi_cli_get_arguments(rval: *mut WasmStrList);\n    #[link_name = \"get-environment\"]\n    fn wasi_cli_get_environment(rval: *mut StrTupleList);\n}\n\n/// Read command-line argument data.\n/// The size of the array should match that returned by `args_sizes_get`\n#[no_mangle]\npub unsafe extern \"C\" fn args_get(argv: *mut *mut u8, argv_buf: *mut u8) -> Errno {\n    State::with(|state| {\n        let alloc = ImportAlloc::SeparateStringsAndPointers {\n            strings: BumpAlloc {\n                base: main_ptr!(argv_buf),\n                len: usize::MAX,\n            },\n            pointers: state.temporary_alloc(),\n        };\n        let (list, _) = state.with_import_alloc(alloc, || unsafe {\n            let mut list = WasmStrList {\n                base: std::ptr::null(),\n                len: 0,\n            };\n            wasi_cli_get_arguments(&mut list);\n            list\n        });\n\n        // Fill in `argv` by walking over the returned `list` and then\n        // additionally apply the nul-termination for each argument itself\n        // here.\n        for i in 0..list.len {\n            let s = list.base.add(i).read();\n            // When sending string pointer back to the main module, we need to subtract\n            // the offset, since the main module doesn't know it's been shifted.\n            *main_ptr!(argv.add(i)) = unshift_ptr!(s.ptr).cast_mut();\n            *s.ptr.add(s.len).cast_mut() = 0;\n        }\n        Ok(())\n    })\n}\n\n/// Return command-line argument data sizes.\n#[no_mangle]\npub unsafe extern \"C\" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno {\n    State::with::<Errno>(|state| {\n        let alloc = ImportAlloc::CountAndDiscardStrings {\n            strings_size: 0,\n            alloc: state.temporary_alloc(),\n        };\n        let (len, alloc) = state.with_import_alloc(alloc, || unsafe {\n            let mut list = WasmStrList {\n                base: std::ptr::null(),\n                len: 0,\n            };\n            wasi_cli_get_arguments(&mut list);\n            list.len\n        });\n        match alloc {\n            ImportAlloc::CountAndDiscardStrings {\n                strings_size,\n                alloc: _,\n            } => {\n                *main_ptr!(argc) = len;\n                // add in bytes needed for a 0-byte at the end of each\n                // argument.\n                *main_ptr!(argv_buf_size) = strings_size + len;\n            }\n            _ => unreachable!(),\n        }\n        Ok(())\n    })\n}\n\n/// Read environment variable data.\n/// The sizes of the buffers should match that returned by `environ_sizes_get`.\n#[no_mangle]\npub unsafe extern \"C\" fn environ_get(environ: *mut *const u8, environ_buf: *mut u8) -> Errno {\n    State::with(|state| {\n        let alloc = ImportAlloc::SeparateStringsAndPointers {\n            strings: BumpAlloc {\n                base: main_ptr!(environ_buf),\n                len: usize::MAX,\n            },\n            pointers: state.temporary_alloc(),\n        };\n        let (list, _) = state.with_import_alloc(alloc, || unsafe {\n            let mut list = StrTupleList {\n                base: std::ptr::null(),\n                len: 0,\n            };\n            wasi_cli_get_environment(&mut list);\n            list\n        });\n\n        // Fill in `environ` by walking over the returned `list`. Strings\n        // are guaranteed to be allocated next to each other with one\n        // extra byte at the end, so also insert the `=` between keys and\n        // the `\\0` at the end of the env var.\n        for i in 0..list.len {\n            let s = list.base.add(i).read();\n            *main_ptr!(environ.add(i)) = unshift_ptr!(s.key.ptr);\n            *s.key.ptr.add(s.key.len).cast_mut() = b'=';\n            *s.value.ptr.add(s.value.len).cast_mut() = 0;\n        }\n\n        Ok(())\n    })\n}\n\n/// Return environment variable data sizes.\n#[no_mangle]\npub unsafe extern \"C\" fn environ_sizes_get(\n    environc: *mut Size,\n    environ_buf_size: *mut Size,\n) -> Errno {\n    if !matches!(\n        get_allocation_state(),\n        AllocationState::StackAllocated | AllocationState::StateAllocated\n    ) {\n        *main_ptr!(environc) = 0;\n        *main_ptr!(environ_buf_size) = 0;\n        return ERRNO_SUCCESS;\n    }\n\n    State::with(|state| {\n        let alloc = ImportAlloc::CountAndDiscardStrings {\n            strings_size: 0,\n            alloc: state.temporary_alloc(),\n        };\n        let (len, alloc) = state.with_import_alloc(alloc, || unsafe {\n            let mut list = StrTupleList {\n                base: std::ptr::null(),\n                len: 0,\n            };\n            wasi_cli_get_environment(&mut list);\n            list.len\n        });\n        match alloc {\n            ImportAlloc::CountAndDiscardStrings {\n                strings_size,\n                alloc: _,\n            } => {\n                *main_ptr!(environc) = len;\n                // Account for `=` between keys and a 0-byte at the end of\n                // each key.\n                *main_ptr!(environ_buf_size) = strings_size + 2 * len;\n            }\n            _ => unreachable!(),\n        }\n\n        Ok(())\n    })\n}\n\n/// Return the resolution of a clock.\n/// Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks,\n/// return `errno::inval`.\n/// Note: This is similar to `clock_getres` in POSIX.\n#[no_mangle]\npub extern \"C\" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errno {\n    let resolution = unsafe_main_ptr!(resolution as *mut Timestamp);\n    match id {\n        CLOCKID_MONOTONIC => {\n            unsafe { *resolution = monotonic_clock::resolution() };\n            ERRNO_SUCCESS\n        }\n        CLOCKID_REALTIME => {\n            let res = wall_clock::resolution();\n            unsafe {\n                *resolution = match Timestamp::from(res.seconds)\n                    .checked_mul(1_000_000_000)\n                    .and_then(|ns| ns.checked_add(res.nanoseconds.into()))\n                {\n                    Some(ns) => ns,\n                    None => return ERRNO_OVERFLOW,\n                }\n            };\n            ERRNO_SUCCESS\n        }\n        _ => ERRNO_BADF,\n    }\n}\n\n/// Return the time value of a clock.\n/// Note: This is similar to `clock_gettime` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn clock_time_get(\n    id: Clockid,\n    _precision: Timestamp,\n    time: &mut Timestamp,\n) -> Errno {\n    let time = main_ptr!(time as *mut Timestamp);\n    match id {\n        CLOCKID_MONOTONIC => {\n            *time = monotonic_clock::now();\n            ERRNO_SUCCESS\n        }\n        CLOCKID_REALTIME => {\n            let res = wall_clock::now();\n            *time = match Timestamp::from(res.seconds)\n                .checked_mul(1_000_000_000)\n                .and_then(|ns| ns.checked_add(res.nanoseconds.into()))\n            {\n                Some(ns) => ns,\n                None => return ERRNO_OVERFLOW,\n            };\n            ERRNO_SUCCESS\n        }\n        _ => ERRNO_BADF,\n    }\n}\n\n/// Provide file advisory information on a file descriptor.\n/// Note: This is similar to `posix_fadvise` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_advise(\n    _fd: Fd,\n    _offset: Filesize,\n    _len: Filesize,\n    _advice: Advice,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Force the allocation of space in a file.\n/// Note: This is similar to `posix_fallocate` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_allocate(_fd: Fd, _offset: Filesize, _len: Filesize) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Close a file descriptor.\n/// Note: This is similar to `close` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_close(fd: Fd) -> Errno {\n    State::with::<Errno>(|state| {\n        if let Descriptor::Bad = state.descriptors().get(fd)? {\n            return Err(wasi::ERRNO_BADF);\n        }\n\n        state.descriptors_mut().close(fd)?;\n        Ok(())\n    })\n}\n\n/// Synchronize the data of a file to disk.\n/// Note: This is similar to `fdatasync` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_datasync(_fd: Fd) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Get the attributes of a file descriptor.\n/// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_fdstat_get(_fd: Fd, _stat: *mut Fdstat) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Adjust the flags associated with a file descriptor.\n/// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_fdstat_set_flags(_fd: Fd, _flags: Fdflags) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Does not do anything if `fd` corresponds to a valid descriptor and returns [`wasi::ERRNO_BADF`] otherwise.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_fdstat_set_rights(\n    fd: Fd,\n    _fs_rights_base: Rights,\n    _fs_rights_inheriting: Rights,\n) -> Errno {\n    State::with::<Errno>(|state| {\n        let ds = state.descriptors();\n        match ds.get(fd)? {\n            Descriptor::Streams(..) => Ok(()),\n            Descriptor::Closed(..) | Descriptor::Bad => Err(wasi::ERRNO_BADF),\n        }\n    })\n}\n\n/// Return the attributes of an open file.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_filestat_get(_fd: Fd, _buf: *mut Filestat) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros.\n/// Note: This is similar to `ftruncate` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_filestat_set_size(_fd: Fd, _size: Filesize) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Adjust the timestamps of an open file or directory.\n/// Note: This is similar to `futimens` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_filestat_set_times(\n    _fd: Fd,\n    _atim: Timestamp,\n    _mtim: Timestamp,\n    _fst_flags: Fstflags,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Read from a file descriptor, without using and updating the file descriptor's offset.\n/// Note: This is similar to `preadv` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_pread(\n    _fd: Fd,\n    _iovs_ptr: *const Iovec,\n    _iovs_len: usize,\n    _offset: Filesize,\n    _nread: *mut Size,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Return a description of the given preopened file descriptor.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_prestat_get(_fd: Fd, _buf: *mut Prestat) -> Errno {\n    return ERRNO_BADF;\n}\n\n/// Return a description of the given preopened file descriptor.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_prestat_dir_name(\n    _fd: Fd,\n    _path: *mut u8,\n    _path_max_len: Size,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Write to a file descriptor, without using and updating the file descriptor's offset.\n/// Note: This is similar to `pwritev` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_pwrite(\n    _fd: Fd,\n    _iovs_ptr: *const Ciovec,\n    _iovs_len: usize,\n    _offset: Filesize,\n    _nwritten: *mut Size,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Read from a file descriptor.\n/// Note: This is similar to `readv` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_read(\n    fd: Fd,\n    mut iovs_ptr: *const Iovec,\n    mut iovs_len: usize,\n    nread: *mut Size,\n) -> Errno {\n    // Advance to the first non-empty buffer.\n    while iovs_len != 0 && (*main_ptr!(iovs_ptr)).buf_len == 0 {\n        iovs_ptr = iovs_ptr.add(1);\n        iovs_len -= 1;\n    }\n    if iovs_len == 0 {\n        *main_ptr!(nread) = 0;\n        return ERRNO_SUCCESS;\n    }\n\n    let ptr = (*main_ptr!(iovs_ptr)).buf;\n    let len = (*main_ptr!(iovs_ptr)).buf_len;\n\n    State::with::<Errno>(|state| {\n        let ds = state.descriptors();\n        match ds.get(fd)? {\n            Descriptor::Streams(streams) => {\n                let blocking_mode = BlockingMode::Blocking;\n\n                let read_len = u64::try_from(len).trapping_unwrap();\n                let wasi_stream = streams.get_read_stream()?;\n                let data = match state.with_one_import_alloc(main_ptr!(ptr), len, || {\n                    blocking_mode.read(wasi_stream, read_len)\n                }) {\n                    Ok(data) => data,\n                    Err(streams::StreamError::Closed) => {\n                        *main_ptr!(nread) = 0;\n                        return Ok(());\n                    }\n                    Err(streams::StreamError::LastOperationFailed(e)) => {\n                        Err(stream_error_to_errno(e))?\n                    }\n                };\n\n                assert_eq!(data.as_ptr(), main_ptr!(ptr));\n                assert!(data.len() <= len);\n\n                let len = data.len();\n                *main_ptr!(nread) = len;\n                forget(data);\n                Ok(())\n            }\n            Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),\n        }\n    })\n}\n\nfn stream_error_to_errno(_err: streams::Error) -> Errno {\n    return ERRNO_IO;\n}\n\n/// Read directory entries from a directory.\n/// When successful, the contents of the output buffer consist of a sequence of\n/// directory entries. Each directory entry consists of a `dirent` object,\n/// followed by `dirent::d_namlen` bytes holding the name of the directory\n/// entry.\n/// This function fills the output buffer as much as possible, potentially\n/// truncating the last directory entry. This allows the caller to grow its\n/// read buffer size in case it's too small to fit a single large directory\n/// entry, or skip the oversized directory entry.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_readdir(\n    _fd: Fd,\n    _buf: *mut u8,\n    _buf_len: Size,\n    _cookie: Dircookie,\n    _bufused: *mut Size,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Atomically replace a file descriptor by renumbering another file descriptor.\n/// Due to the strong focus on thread safety, this environment does not provide\n/// a mechanism to duplicate or renumber a file descriptor to an arbitrary\n/// number, like `dup2()`. This would be prone to race conditions, as an actual\n/// file descriptor with the same number could be allocated by a different\n/// thread at the same time.\n/// This function provides a way to atomically renumber file descriptors, which\n/// would disappear if `dup2()` were to be removed entirely.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_renumber(fd: Fd, to: Fd) -> Errno {\n    State::with::<Errno>(|state| state.descriptors_mut().renumber(fd, to))\n}\n\n/// Move the offset of a file descriptor.\n/// Note: This is similar to `lseek` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_seek(\n    _fd: Fd,\n    _offset: Filedelta,\n    _whence: Whence,\n    _newoffset: *mut Filesize,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Synchronize the data and metadata of a file to disk.\n/// Note: This is similar to `fsync` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_sync(_fd: Fd) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Return the current offset of a file descriptor.\n/// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_tell(_fd: Fd, _offset: *mut Filesize) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Write to a file descriptor.\n/// Note: This is similar to `writev` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn fd_write(\n    fd: Fd,\n    mut iovs_ptr: *const Ciovec,\n    mut iovs_len: usize,\n    nwritten: *mut Size,\n) -> Errno {\n    if !matches!(\n        get_allocation_state(),\n        AllocationState::StackAllocated | AllocationState::StateAllocated\n    ) {\n        *main_ptr!(nwritten) = 0;\n        return ERRNO_IO;\n    }\n\n    // Advance to the first non-empty buffer.\n    while iovs_len != 0 && (*main_ptr!(iovs_ptr)).buf_len == 0 {\n        iovs_ptr = iovs_ptr.add(1);\n        iovs_len -= 1;\n    }\n    if iovs_len == 0 {\n        *main_ptr!(nwritten) = 0;\n        return ERRNO_SUCCESS;\n    }\n\n    let ptr = (*main_ptr!(iovs_ptr)).buf;\n    let len = (*main_ptr!(iovs_ptr)).buf_len;\n    let bytes = slice::from_raw_parts(main_ptr!(ptr), len);\n\n    State::with::<Errno>(|state| {\n        let ds = state.descriptors();\n        match ds.get(fd)? {\n            Descriptor::Streams(streams) => {\n                let wasi_stream = streams.get_write_stream()?;\n\n                let nbytes = BlockingMode::Blocking.write(wasi_stream, bytes)?;\n\n                *main_ptr!(nwritten) = nbytes;\n                Ok(())\n            }\n            Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF),\n        }\n    })\n}\n\n/// Create a directory.\n/// Note: This is similar to `mkdirat` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_create_directory(\n    _fd: Fd,\n    _path_ptr: *const u8,\n    _path_len: usize,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Return the attributes of a file or directory.\n/// Note: This is similar to `stat` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_filestat_get(\n    _fd: Fd,\n    _flags: Lookupflags,\n    _path_ptr: *const u8,\n    _path_len: usize,\n    _buf: *mut Filestat,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Adjust the timestamps of a file or directory.\n/// Note: This is similar to `utimensat` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_filestat_set_times(\n    _fd: Fd,\n    _flags: Lookupflags,\n    _path_ptr: *const u8,\n    _path_len: usize,\n    _atim: Timestamp,\n    _mtim: Timestamp,\n    _fst_flags: Fstflags,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Create a hard link.\n/// Note: This is similar to `linkat` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_link(\n    _old_fd: Fd,\n    _old_flags: Lookupflags,\n    _old_path_ptr: *const u8,\n    _old_path_len: usize,\n    _new_fd: Fd,\n    _new_path_ptr: *const u8,\n    _new_path_len: usize,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Open a file or directory.\n/// The returned file descriptor is not guaranteed to be the lowest-numbered\n/// file descriptor not currently open; it is randomized to prevent\n/// applications from depending on making assumptions about indexes, since this\n/// is error-prone in multi-threaded contexts. The returned file descriptor is\n/// guaranteed to be less than 2**31.\n/// Note: This is similar to `openat` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_open(\n    _fd: Fd,\n    _dirflags: Lookupflags,\n    _path_ptr: *const u8,\n    _path_len: usize,\n    _oflags: Oflags,\n    _fs_rights_base: Rights,\n    _fs_rights_inheriting: Rights,\n    _fdflags: Fdflags,\n    _opened_fd: *mut Fd,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Read the contents of a symbolic link.\n/// Note: This is similar to `readlinkat` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_readlink(\n    _fd: Fd,\n    _path_ptr: *const u8,\n    _path_len: usize,\n    _buf: *mut u8,\n    _buf_len: Size,\n    _bufused: *mut Size,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Remove a directory.\n/// Return `errno::notempty` if the directory is not empty.\n/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_remove_directory(\n    _fd: Fd,\n    _path_ptr: *const u8,\n    _path_len: usize,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Rename a file or directory.\n/// Note: This is similar to `renameat` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_rename(\n    _old_fd: Fd,\n    _old_path_ptr: *const u8,\n    _old_path_len: usize,\n    _new_fd: Fd,\n    _new_path_ptr: *const u8,\n    _new_path_len: usize,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Create a symbolic link.\n/// Note: This is similar to `symlinkat` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_symlink(\n    _old_path_ptr: *const u8,\n    _old_path_len: usize,\n    _fd: Fd,\n    _new_path_ptr: *const u8,\n    _new_path_len: usize,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\n/// Unlink a file.\n/// Return `errno::isdir` if the path refers to a directory.\n/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn path_unlink_file(\n    _fd: Fd,\n    _path_ptr: *const u8,\n    _path_len: usize,\n) -> Errno {\n    wasi::ERRNO_NOTSUP\n}\n\nstruct Pollables {\n    pointer: *mut Pollable,\n    index: usize,\n    length: usize,\n}\n\nimpl Pollables {\n    unsafe fn push(&mut self, pollable: Pollable) {\n        assert!(self.index < self.length);\n        // Use `ptr::write` instead of `*... = pollable` because `ptr::write`\n        // doesn't call drop on the old memory.\n        self.pointer.add(self.index).write(pollable);\n        self.index += 1;\n    }\n}\n\n// We create new pollable handles for each `poll_oneoff` call, so drop them all\n// after the call.\nimpl Drop for Pollables {\n    fn drop(&mut self) {\n        while self.index != 0 {\n            self.index -= 1;\n            unsafe {\n                core::ptr::drop_in_place(self.pointer.add(self.index));\n            }\n        }\n    }\n}\n\n/// Concurrently poll for the occurrence of a set of events.\n#[no_mangle]\npub unsafe extern \"C\" fn poll_oneoff(\n    r#in: *const Subscription,\n    out: *mut Event,\n    nsubscriptions: Size,\n    nevents: *mut Size,\n) -> Errno {\n    *main_ptr!(nevents) = 0;\n\n    let subscriptions = slice::from_raw_parts(main_ptr!(r#in), nsubscriptions);\n\n    // We're going to split the `nevents` buffer into two non-overlapping\n    // buffers: one to store the pollable handles, and the other to store\n    // the bool results.\n    //\n    // First, we assert that this is possible:\n    assert!(align_of::<Event>() >= align_of::<Pollable>());\n    assert!(align_of::<Pollable>() >= align_of::<u32>());\n    assert!(\n        nsubscriptions\n            .checked_mul(size_of::<Event>())\n            .trapping_unwrap()\n            >= nsubscriptions\n                .checked_mul(size_of::<Pollable>())\n                .trapping_unwrap()\n                .checked_add(\n                    nsubscriptions\n                        .checked_mul(size_of::<u32>())\n                        .trapping_unwrap()\n                )\n                .trapping_unwrap()\n    );\n    // Store the pollable handles at the beginning, and the bool results at the\n    // end, so that we don't clobber the bool results when writing the events.\n    let pollables = main_ptr!(out as *mut c_void as *mut Pollable);\n    let results = main_ptr!(out.add(nsubscriptions).cast::<u32>().sub(nsubscriptions));\n\n    // Indefinite sleeping is not supported in preview1.\n    if nsubscriptions == 0 {\n        return ERRNO_INVAL;\n    }\n\n    State::with::<Errno>(|state| {\n        const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw();\n        const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw();\n        const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw();\n\n        let mut pollables = Pollables {\n            pointer: pollables,\n            index: 0,\n            length: nsubscriptions,\n        };\n\n        for subscription in subscriptions {\n            pollables.push(match subscription.u.tag {\n                EVENTTYPE_CLOCK => {\n                    let clock = &subscription.u.u.clock;\n                    let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME)\n                        == SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME;\n                    match clock.id {\n                        CLOCKID_REALTIME => {\n                            let timeout = if absolute {\n                                // Convert `clock.timeout` to `Datetime`.\n                                let mut datetime = wall_clock::Datetime {\n                                    seconds: clock.timeout / 1_000_000_000,\n                                    nanoseconds: (clock.timeout % 1_000_000_000) as _,\n                                };\n\n                                // Subtract `now`.\n                                let now = wall_clock::now();\n                                datetime.seconds -= now.seconds;\n                                if datetime.nanoseconds < now.nanoseconds {\n                                    datetime.seconds -= 1;\n                                    datetime.nanoseconds += 1_000_000_000;\n                                }\n                                datetime.nanoseconds -= now.nanoseconds;\n\n                                // Convert to nanoseconds.\n                                let nanos = datetime\n                                    .seconds\n                                    .checked_mul(1_000_000_000)\n                                    .ok_or(ERRNO_OVERFLOW)?;\n                                nanos\n                                    .checked_add(datetime.nanoseconds.into())\n                                    .ok_or(ERRNO_OVERFLOW)?\n                            } else {\n                                clock.timeout\n                            };\n\n                            monotonic_clock::subscribe_duration(timeout)\n                        }\n\n                        CLOCKID_MONOTONIC => {\n                            if absolute {\n                                monotonic_clock::subscribe_instant(clock.timeout)\n                            } else {\n                                monotonic_clock::subscribe_duration(clock.timeout)\n                            }\n                        }\n\n                        _ => return Err(ERRNO_INVAL),\n                    }\n                }\n\n                EVENTTYPE_FD_READ => state\n                    .descriptors()\n                    .get_read_stream(subscription.u.u.fd_read.file_descriptor)\n                    .map(|stream| stream.subscribe())?,\n\n                EVENTTYPE_FD_WRITE => state\n                    .descriptors()\n                    .get_write_stream(subscription.u.u.fd_write.file_descriptor)\n                    .map(|stream| stream.subscribe())?,\n\n                _ => return Err(ERRNO_INVAL),\n            });\n        }\n\n        #[link(wasm_import_module = \"wasi:io/poll@0.2.6\")]\n        #[allow(improper_ctypes)] // FIXME(bytecodealliance/wit-bindgen#684)\n        extern \"C\" {\n            #[link_name = \"poll\"]\n            fn poll_import(pollables: *const Pollable, len: usize, rval: *mut ReadyList);\n        }\n        let mut ready_list = ReadyList {\n            base: std::ptr::null(),\n            len: 0,\n        };\n\n        state.with_one_import_alloc(\n            results.cast(),\n            nsubscriptions\n                .checked_mul(size_of::<u32>())\n                .trapping_unwrap(),\n            || {\n                poll_import(\n                    pollables.pointer,\n                    pollables.length,\n                    &mut ready_list as *mut _,\n                );\n            },\n        );\n\n        assert!(ready_list.len <= nsubscriptions);\n        assert_eq!(ready_list.base, results as *const u32);\n\n        drop(pollables);\n\n        let ready = std::slice::from_raw_parts(ready_list.base, ready_list.len);\n\n        let mut count = 0;\n\n        for subscription in ready {\n            let subscription = *subscriptions.as_ptr().add(*subscription as usize);\n\n            let type_;\n\n            let (error, nbytes, flags) = match subscription.u.tag {\n                EVENTTYPE_CLOCK => {\n                    type_ = wasi::EVENTTYPE_CLOCK;\n                    (ERRNO_SUCCESS, 0, 0)\n                }\n\n                EVENTTYPE_FD_READ => {\n                    type_ = wasi::EVENTTYPE_FD_READ;\n                    let ds = state.descriptors();\n                    let desc = ds\n                        .get(subscription.u.u.fd_read.file_descriptor)\n                        .trapping_unwrap();\n                    match desc {\n                        Descriptor::Streams(streams) => match &streams.type_ {\n                            StreamType::Stdio => (ERRNO_SUCCESS, 1, 0),\n                        },\n                        _ => unreachable!(),\n                    }\n                }\n                EVENTTYPE_FD_WRITE => {\n                    type_ = wasi::EVENTTYPE_FD_WRITE;\n                    let ds = state.descriptors();\n                    let desc = ds\n                        .get(subscription.u.u.fd_write.file_descriptor)\n                        .trapping_unwrap();\n                    match desc {\n                        Descriptor::Streams(streams) => match &streams.type_ {\n                            StreamType::Stdio => (ERRNO_SUCCESS, 1, 0),\n                        },\n                        _ => unreachable!(),\n                    }\n                }\n\n                _ => unreachable!(),\n            };\n\n            *main_ptr!(out.add(count)) = Event {\n                userdata: subscription.userdata,\n                error,\n                type_,\n                fd_readwrite: EventFdReadwrite { nbytes, flags },\n            };\n\n            count += 1;\n        }\n\n        *main_ptr!(nevents) = count;\n\n        Ok(())\n    })\n}\n\n/// Terminate the process normally. An exit code of 0 indicates successful\n/// termination of the program. The meanings of other values is dependent on\n/// the environment.\n#[no_mangle]\npub unsafe extern \"C\" fn proc_exit(rval: Exitcode) -> ! {\n    let status = if rval == 0 { Ok(()) } else { Err(()) };\n    crate::bindings::wasi::cli::exit::exit(status); // does not return\n    unreachable!(\"host exit implementation didn't exit!\") // actually unreachable\n}\n\n/// Send a signal to the process of the calling thread.\n/// Note: This is similar to `raise` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn proc_raise(_sig: Signal) -> Errno {\n    unreachable!()\n}\n\n/// Temporarily yield execution of the calling thread.\n/// Note: This is similar to `sched_yield` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn sched_yield() -> Errno {\n    // TODO: This is not yet covered in Preview2.\n\n    ERRNO_SUCCESS\n}\n\n/// Write high-quality random data into a buffer.\n/// This function blocks when the implementation is unable to immediately\n/// provide sufficient high-quality random data.\n/// This function may execute slowly, so when large mounts of random data are\n/// required, it's advisable to use this function to seed a pseudo-random\n/// number generator, rather than to provide the random data directly.\n#[no_mangle]\npub unsafe extern \"C\" fn random_get(buf: *mut u8, buf_len: Size) -> Errno {\n    if matches!(\n        get_allocation_state(),\n        AllocationState::StackAllocated | AllocationState::StateAllocated\n    ) {\n        State::with::<Errno>(|state| {\n            assert_eq!(buf_len as u32 as Size, buf_len);\n            let result = state.with_one_import_alloc(main_ptr!(buf), buf_len, || {\n                random::get_random_bytes(buf_len as u64)\n            });\n            assert_eq!(result.as_ptr(), main_ptr!(buf));\n\n            // The returned buffer's memory was allocated in `buf`, so don't separately\n            // free it.\n            forget(result);\n\n            Ok(())\n        })\n    } else {\n        ERRNO_SUCCESS\n    }\n}\n\n/// Accept a new incoming connection.\n/// Note: This is similar to `accept` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn sock_accept(_fd: Fd, _flags: Fdflags, _connection: *mut Fd) -> Errno {\n    unreachable!()\n}\n\n/// Receive a message from a socket.\n/// Note: This is similar to `recv` in POSIX, though it also supports reading\n/// the data into multiple buffers in the manner of `readv`.\n#[no_mangle]\npub unsafe extern \"C\" fn sock_recv(\n    _fd: Fd,\n    _ri_data_ptr: *const Iovec,\n    _ri_data_len: usize,\n    _ri_flags: Riflags,\n    _ro_datalen: *mut Size,\n    _ro_flags: *mut Roflags,\n) -> Errno {\n    unreachable!()\n}\n\n/// Send a message on a socket.\n/// Note: This is similar to `send` in POSIX, though it also supports writing\n/// the data from multiple buffers in the manner of `writev`.\n#[no_mangle]\npub unsafe extern \"C\" fn sock_send(\n    _fd: Fd,\n    _si_data_ptr: *const Ciovec,\n    _si_data_len: usize,\n    _si_flags: Siflags,\n    _so_datalen: *mut Size,\n) -> Errno {\n    unreachable!()\n}\n\n/// Shut down socket send and receive channels.\n/// Note: This is similar to `shutdown` in POSIX.\n#[no_mangle]\npub unsafe extern \"C\" fn sock_shutdown(_fd: Fd, _how: Sdflags) -> Errno {\n    unreachable!()\n}\n\n#[derive(Clone, Copy)]\npub enum BlockingMode {\n    NonBlocking,\n    Blocking,\n}\n\nimpl BlockingMode {\n    // note: these methods must take self, not &self, to avoid rustc creating a constant\n    // out of a BlockingMode literal that it places in .romem, creating a data section and\n    // breaking our fragile linking scheme\n    fn read(\n        self,\n        input_stream: &streams::InputStream,\n        read_len: u64,\n    ) -> Result<Vec<u8>, streams::StreamError> {\n        match self {\n            BlockingMode::NonBlocking => input_stream.read(read_len),\n            BlockingMode::Blocking => input_stream.blocking_read(read_len),\n        }\n    }\n    fn write(\n        self,\n        output_stream: &streams::OutputStream,\n        mut bytes: &[u8],\n    ) -> Result<usize, Errno> {\n        match self {\n            BlockingMode::Blocking => {\n                let total = bytes.len();\n                while !bytes.is_empty() {\n                    let len = bytes.len().min(4096);\n                    let (chunk, rest) = bytes.split_at(len);\n                    bytes = rest;\n                    match output_stream.blocking_write_and_flush(chunk) {\n                        Ok(()) => {}\n                        Err(streams::StreamError::Closed) => return Err(ERRNO_IO),\n                        Err(streams::StreamError::LastOperationFailed(e)) => {\n                            return Err(stream_error_to_errno(e))\n                        }\n                    }\n                }\n                Ok(total)\n            }\n\n            BlockingMode::NonBlocking => {\n                let permit = match output_stream.check_write() {\n                    Ok(n) => n,\n                    Err(streams::StreamError::Closed) => 0,\n                    Err(streams::StreamError::LastOperationFailed(e)) => {\n                        return Err(stream_error_to_errno(e))\n                    }\n                };\n\n                let len = bytes.len().min(permit as usize);\n                if len == 0 {\n                    return Ok(0);\n                }\n\n                match output_stream.write(&bytes[..len]) {\n                    Ok(_) => {}\n                    Err(streams::StreamError::Closed) => return Ok(0),\n                    Err(streams::StreamError::LastOperationFailed(e)) => {\n                        return Err(stream_error_to_errno(e))\n                    }\n                }\n\n                match output_stream.blocking_flush() {\n                    Ok(_) => {}\n                    Err(streams::StreamError::Closed) => return Ok(0),\n                    Err(streams::StreamError::LastOperationFailed(e)) => {\n                        return Err(stream_error_to_errno(e))\n                    }\n                }\n\n                Ok(len)\n            }\n        }\n    }\n}\n\nconst PAGE_SIZE: usize = 65536;\n\n/// A canary value to detect memory corruption within `State`.\nconst MAGIC: u32 = u32::from_le_bytes(*b\"ugh!\");\n\n#[repr(C)] // used for now to keep magic1 and magic2 at the start and end\npub(crate) struct State {\n    /// A canary constant value located at the beginning of this structure to\n    /// try to catch memory corruption coming from the bottom.\n    magic1: u32,\n\n    /// Used to coordinate allocations of `cabi_import_realloc`\n    import_alloc: Cell<ImportAlloc>,\n\n    /// Storage of mapping from preview1 file descriptors to preview2 file\n    /// descriptors.\n    ///\n    /// Do not use this member directly - use State::descriptors() to ensure\n    /// lazy initialization happens.\n    descriptors: RefCell<Option<Descriptors>>,\n\n    /// Temporary data\n    temporary_data: UnsafeCell<MaybeUninit<[u8; temporary_data_size()]>>,\n\n    /// The incoming request `request`, if the entry-point was through the `http-incoming`.\n    pub(crate) request: Cell<Option<u32>>,\n\n    /// The incoming request `body` index, if the entry-point was through the `http-incoming`.\n    pub(crate) request_body: Cell<Option<u32>>,\n\n    /// Work around `CacheBusyHandle::wait` in the Rust SDK not consuming `self`.\n    ///\n    /// `cache_busy_handle_wait` consumes its handle, however because the\n    /// SDK's `CacheBusyHandle::wait` doesn't consume `self`, it does a\n    /// separate drop and calls `close_busy`. To avoid this use of a dangling\n    /// handle, `cache_busy_handle_wait` records the handle it consumed so that\n    /// `close_busy` can check if it's closing a handle that has just been\n    /// consumed.\n    pub(crate) recently_consumed_cache_busy_handle: Cell<u32>,\n\n    /// Work around `CacheReplaceHandle::replace_insert` in the Rust SDK not\n    /// consuming `self`.\n    ///\n    /// `replace_insert` consumes its handle, however because the\n    /// SDK's `CacheReplaceHandle::replace_insert` doesn't consume `self`, it does a\n    /// separate drop and calls `close`. To avoid this use of a dangling\n    /// handle, `replace_insert` records the handle it consumed so that\n    /// `close` can check if it's closing a handle that has just been\n    /// consumed.\n    pub(crate) recently_consumed_cache_replace_handle: Cell<u32>,\n\n    /// Another canary constant located at the end of the structure to catch\n    /// memory corruption coming from the bottom.\n    magic2: u32,\n}\n\n#[repr(C)]\npub struct WasmStr {\n    ptr: *const u8,\n    len: usize,\n}\n\n#[repr(C)]\npub struct WasmStrList {\n    base: *const WasmStr,\n    len: usize,\n}\n\n#[repr(C)]\npub struct StrTuple {\n    key: WasmStr,\n    value: WasmStr,\n}\n\n#[derive(Copy, Clone)]\n#[repr(C)]\npub struct StrTupleList {\n    base: *const StrTuple,\n    len: usize,\n}\n\n#[derive(Copy, Clone)]\n#[repr(C)]\npub struct ReadyList {\n    base: *const u32,\n    len: usize,\n}\n\nconst fn temporary_data_size() -> usize {\n    // The total size of the struct should be a page, so start there\n    let mut start = PAGE_SIZE;\n\n    // Remove big chunks of the struct for its various fields.\n    start -= size_of::<Descriptors>();\n\n    // Remove miscellaneous metadata also stored in state.\n    let misc = 14;\n    start -= misc * size_of::<usize>();\n\n    // Everything else is the `command_data` allocation.\n    start\n}\n\n// Statically assert that the `State` structure is the size of a wasm page. This\n// mostly guarantees that it's not larger than one page which is relied upon\n// below.\n#[cfg(target_arch = \"wasm32\")]\nconst _: () = {\n    let _size_assert: [(); PAGE_SIZE] = [(); size_of::<State>()];\n};\n\n#[allow(unused)]\n#[repr(i32)]\nenum AllocationState {\n    StackUnallocated,\n    StackAllocating,\n    StackAllocated,\n    StateAllocating,\n    StateAllocated,\n}\n\n#[allow(improper_ctypes)]\nextern \"C\" {\n    fn get_state_ptr() -> *mut State;\n    fn set_state_ptr(state: *mut State);\n    fn get_allocation_state() -> AllocationState;\n    fn set_allocation_state(state: AllocationState);\n}\n\npub(crate) trait StateError {\n    const SUCCESS: Self;\n}\n\nimpl StateError for Errno {\n    const SUCCESS: Self = ERRNO_SUCCESS;\n}\n\nimpl State {\n    pub(crate) fn with<E: StateError>(f: impl FnOnce(&State) -> Result<(), E>) -> E {\n        let state_ref = State::ptr();\n        assert_eq!(state_ref.magic1, MAGIC);\n        assert_eq!(state_ref.magic2, MAGIC);\n        let ret = f(state_ref);\n        match ret {\n            Ok(()) => E::SUCCESS,\n            Err(err) => err,\n        }\n    }\n\n    fn ptr() -> &'static State {\n        unsafe {\n            let mut ptr = get_state_ptr();\n            if ptr.is_null() {\n                ptr = State::new();\n                set_state_ptr(ptr);\n            }\n            &*ptr\n        }\n    }\n\n    #[cold]\n    fn new() -> *mut State {\n        #[cfg(feature = \"noshift\")]\n        #[link(wasm_import_module = \"__main_module__\")]\n        extern \"C\" {\n            fn cabi_realloc(\n                old_ptr: *mut u8,\n                old_len: usize,\n                align: usize,\n                new_len: usize,\n            ) -> *mut u8;\n        }\n\n        assert!(matches!(\n            unsafe { get_allocation_state() },\n            AllocationState::StackAllocated\n        ));\n\n        unsafe { set_allocation_state(AllocationState::StateAllocating) };\n\n        #[cfg(feature = \"noshift\")]\n        let ret = unsafe {\n            cabi_realloc(\n                std::ptr::null_mut(),\n                0,\n                mem::align_of::<UnsafeCell<State>>(),\n                mem::size_of::<UnsafeCell<State>>(),\n            ) as *mut State\n        };\n\n        // Use the second page of memory to store state\n        #[cfg(not(feature = \"noshift\"))]\n        let ret = (OFFSET - PAGE_SIZE) as *mut State;\n\n        unsafe { set_allocation_state(AllocationState::StateAllocated) };\n\n        unsafe {\n            Self::init(ret);\n        }\n\n        ret\n    }\n\n    #[cold]\n    unsafe fn init(state: *mut State) {\n        state.write(State {\n            magic1: MAGIC,\n            magic2: MAGIC,\n            import_alloc: Cell::new(ImportAlloc::None),\n            descriptors: RefCell::new(None),\n            temporary_data: UnsafeCell::new(MaybeUninit::uninit()),\n            request: Cell::new(None),\n            request_body: Cell::new(None),\n            recently_consumed_cache_busy_handle: Cell::new(INVALID_HANDLE),\n            recently_consumed_cache_replace_handle: Cell::new(INVALID_HANDLE),\n        });\n    }\n\n    /// Accessor for the descriptors member that ensures it is properly initialized\n    fn descriptors<'a>(&'a self) -> impl Deref<Target = Descriptors> + 'a {\n        let mut d = self\n            .descriptors\n            .try_borrow_mut()\n            .unwrap_or_else(|_| unreachable!());\n        if d.is_none() {\n            *d = Some(Descriptors::new(self));\n        }\n        RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))\n    }\n\n    /// Mut accessor for the descriptors member that ensures it is properly initialized\n    fn descriptors_mut<'a>(&'a self) -> impl DerefMut + Deref<Target = Descriptors> + 'a {\n        let mut d = self\n            .descriptors\n            .try_borrow_mut()\n            .unwrap_or_else(|_| unreachable!());\n        if d.is_none() {\n            *d = Some(Descriptors::new(self));\n        }\n        RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!()))\n    }\n\n    unsafe fn temporary_alloc(&self) -> BumpAlloc {\n        BumpAlloc {\n            base: self.temporary_data.get().cast(),\n            len: mem::size_of_val(&self.temporary_data),\n        }\n    }\n\n    /// Configure that `cabi_import_realloc` will allocate once from\n    /// `base` with at most `len` bytes for the duration of `f`.\n    ///\n    /// Panics if the import allocator is already configured.\n    fn with_one_import_alloc<T>(&self, base: *mut u8, len: usize, f: impl FnOnce() -> T) -> T {\n        let alloc = BumpAlloc { base, len };\n        self.with_import_alloc(ImportAlloc::OneAlloc(alloc), f).0\n    }\n\n    /// Configures the `alloc` specified to be the allocator for\n    /// `cabi_import_realloc` for the duration of `f`.\n    ///\n    /// Panics if the import allocator is already configured.\n    fn with_import_alloc<T>(&self, alloc: ImportAlloc, f: impl FnOnce() -> T) -> (T, ImportAlloc) {\n        match self.import_alloc.replace(alloc) {\n            ImportAlloc::None => {}\n            _ => unreachable!(\"import allocator already set\"),\n        }\n        let r = f();\n        let alloc = self.import_alloc.replace(ImportAlloc::None);\n        (r, alloc)\n    }\n}\n"
  },
  {
    "path": "wasm_abi/adapter/src/macros.rs",
    "content": "//! Minimal versions of standard-library panicking and printing macros.\n//!\n//! We're avoiding static initializers, so we can't have things like string\n//! literals. Replace the standard assert macros with simpler implementations.\n\nuse crate::bindings::wasi::cli::stderr::get_stderr;\n\n/// Used to annotate the address that comes from the main module.\n/// The annotation is needed because the main module has a different view\n/// of the memory address. With the current implementation, the main module\n/// thinks the memory address starts at 0, but in reality, the memory address\n/// starts two Wasm pages later. When accessing the main module memory from\n/// the adapter, we need this annotation to map the pointer to the correct\n/// memory address.\n#[cfg(not(feature = \"noshift\"))]\nmacro_rules! main_ptr {\n    ($ptr:expr) => {{\n        $ptr.byte_add(crate::OFFSET)\n    }};\n}\n#[cfg(not(feature = \"noshift\"))]\nmacro_rules! unsafe_main_ptr {\n    ($ptr:expr) => {{\n        unsafe { main_ptr!($ptr) }\n    }};\n}\n/// Used to annotate the address sending back to the main module.\n/// This macro does exactly the opposite of `main_ptr`.\n#[cfg(not(feature = \"noshift\"))]\nmacro_rules! unshift_ptr {\n    ($ptr:expr) => {{\n        $ptr.byte_sub(crate::OFFSET)\n    }};\n}\n\n#[cfg(feature = \"noshift\")]\nmacro_rules! main_ptr {\n    ($ptr:expr) => {{\n        $ptr\n    }};\n}\n#[cfg(feature = \"noshift\")]\nmacro_rules! unsafe_main_ptr {\n    ($ptr:expr) => {{\n        $ptr\n    }};\n}\n#[cfg(feature = \"noshift\")]\nmacro_rules! unshift_ptr {\n    ($ptr:expr) => {{\n        $ptr\n    }};\n}\n\n#[allow(dead_code)]\n#[cold]\npub(crate) fn print(message: &[u8]) {\n    let _ = get_stderr().blocking_write_and_flush(message);\n}\n\n/// A minimal `eprint` for debugging.\n#[allow(unused_macros)]\nmacro_rules! eprint {\n    ($arg:tt) => {{\n        // We have to expand string literals into byte arrays to prevent them\n        // from getting statically initialized.\n        let message = byte_array_literals::str!($arg);\n        $crate::macros::print(&message);\n    }};\n}\n\n/// A minimal `eprintln` for debugging.\n#[allow(unused_macros)]\nmacro_rules! eprintln {\n    ($arg:tt) => {{\n        // We have to expand string literals into byte arrays to prevent them\n        // from getting statically initialized.\n        let message = byte_array_literals::str_nl!($arg);\n        $crate::macros::print(&message);\n    }};\n}\n\n#[cold]\nfn eprint_u32(x: u32) {\n    if x == 0 {\n        eprint!(\"0\");\n    } else {\n        eprint_u32_impl(x)\n    }\n\n    #[cold]\n    fn eprint_u32_impl(x: u32) {\n        if x != 0 {\n            eprint_u32_impl(x / 10);\n\n            let digit = [b'0' + ((x % 10) as u8)];\n            crate::macros::print(&digit);\n        }\n    }\n}\n\n#[allow(dead_code)]\n#[cold]\npub(crate) fn unreachable(line: u32, message: &[u8]) -> ! {\n    eprint!(\"unreachable executed at adapter line \");\n    crate::macros::eprint_u32(line);\n    if !message.is_empty() {\n        eprint!(\": \");\n        print(message);\n    }\n    eprint!(\"\\n\");\n    #[cfg(target_arch = \"wasm32\")]\n    core::arch::wasm32::unreachable();\n    // This is here to keep rust-analyzer happy when building for native:\n    #[cfg(not(target_arch = \"wasm32\"))]\n    std::process::abort();\n}\n\n/// A minimal `unreachable`.\nmacro_rules! unreachable {\n    () => {{\n        crate::macros::unreachable(line!(), b\"\");\n    }};\n\n    ($arg:tt) => {{\n        let message = byte_array_literals::str!($arg);\n        crate::macros::unreachable(line!(), &message);\n    }};\n}\n\n/// A minimal `assert`.\nmacro_rules! assert {\n    ($cond:expr $(,)?) => {\n        if !$cond {\n            unreachable!(\"assertion failed\")\n        }\n    };\n}\n\n/// A minimal `assert_eq`.\nmacro_rules! assert_eq {\n    ($left:expr, $right:expr $(,)?) => {\n        assert!($left == $right);\n    };\n}\n"
  },
  {
    "path": "wasm_abi/compute-at-edge-abi/README.md",
    "content": "# 🔗 compute-at-edge-abi\n\nThis directory contains the canonical `witx` definitions for the Compute\nplatform ABI.\n\n### About `witx`\n\n> The `witx` file format is an experimental format which is based on the\n> [module linking] text format (`wit`), (which is in turn based on the\n> [wat format], which is based on [S-expressions]). It adds some features\n> using the same syntax as [interface types], some features with syntax\n> similar to [gc types], as well as a few special features of its own.\n>\n> `witx` is actively evolving. Expect backwards-incompatible changes,\n> particularly in the areas where `witx` differs from `wit`.\n>\n> The initial goal for `witx` is just to have a language suitable for\n> expressing [WASI] APIs in, to serve as the vocabulary for proposing changes\n> to existing APIs and proposing new APIs. Initially, while it uses some of\n> the syntax and concepts from interface types, it doesn't currently imply the\n> full interface types specification, or the use of the interface types custom\n> sections.\n>\n> We expect that eventually we will transition to using the full interface\n> types specification, with `witx` having minimal additional features. Until\n> then, the goals here are to remain aligned with interface types and other\n> relevant WebAssembly standards and proposals wherever practical, and to be an\n> input into the design process of interface types.\n\n- [source][witx]\n\n[interface types]: https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md\n[gc types]: https://github.com/WebAssembly/gc\n[module linking]: https://github.com/WebAssembly/module-linking/blob/master/proposals/module-linking/Explainer.md\n[S-expressions]: https://en.wikipedia.org/wiki/S-expression\n[WASI]: https://github.com/WebAssembly/WASI\n[wat format]: https://webassembly.github.io/spec/core/bikeshed/index.html#text-format%E2%91%A0\n[witx]: https://github.com/WebAssembly/WASI/blob/main/docs/witx.md\n"
  },
  {
    "path": "wasm_abi/compute-at-edge-abi/cache.witx",
    "content": ";;; The outcome of a cache lookup (either bare or as part of a cache transaction)\n(typename $cache_handle (handle))\n;;; Handle that can be used to check whether or not a cache lookup is waiting on another client.\n(typename $cache_busy_handle (handle))\n;;; Handle for an in-progress Replace operation\n(typename $cache_replace_handle (handle))\n\n(typename $cache_object_length u64)\n(typename $cache_duration_ns u64)\n(typename $cache_hit_count u64)\n(typename $cache_replace_strategy u32)\n\n;;; Extensible options for cache lookup operations; currently used for both `lookup` and `transaction_lookup`.\n(typename $cache_lookup_options\n    (record\n        (field $request_headers $request_handle) ;; a full request handle, but used only for its headers\n        (field $service_id (@witx pointer (@witx char8)))\n        (field $service_id_len u32)\n    )\n)\n\n(typename $cache_lookup_options_mask\n    (flags (@witx repr u32)\n        $reserved\n        $request_headers\n        $service_id\n        $always_use_requested_range\n    )\n)\n\n;;; Extensible options for cache replace operations\n(typename $cache_replace_options\n    (record\n        (field $request_headers $request_handle) ;; a full request handle, but used only for its headers\n        (field $replace_strategy $cache_replace_strategy)\n        (field $service_id (@witx pointer (@witx char8)))\n        (field $service_id_len u32)\n    )\n)\n\n(typename $cache_replace_options_mask\n    (flags (@witx repr u32)\n        $reserved\n        $request_headers\n        $replace_strategy\n        $service_id\n        $always_use_requested_range\n    )\n)\n\n;;; Configuration for several hostcalls that write to the cache:\n;;; - `insert`\n;;; - `transaction_insert`\n;;; - `transaction_insert_and_stream_back`\n;;; - `transaction_update`\n;;;\n;;; Some options are only allowed for certain of these hostcalls; see `cache_write_options_mask`.\n(typename $cache_write_options\n    (record\n        (field $max_age_ns $cache_duration_ns) ;; this is a required field; there's no flag for it\n        (field $request_headers $request_handle) ;; a full request handle, but used only for its headers\n        (field $vary_rule_ptr (@witx pointer (@witx char8))) ;; a list of header names separated by spaces\n        (field $vary_rule_len (@witx usize))\n        ;; The initial age of the object in nanoseconds (default: 0).\n        ;;\n        ;; This age is used to determine the freshness lifetime of the object as well as to\n        ;; prioritize which variant to return if a subsequent lookup matches more than one vary rule\n        (field $initial_age_ns $cache_duration_ns)\n        (field $stale_while_revalidate_ns $cache_duration_ns)\n        (field $surrogate_keys_ptr (@witx pointer (@witx char8))) ;; a list of surrogate keys separated by spaces\n        (field $surrogate_keys_len (@witx usize))\n        (field $length $cache_object_length)\n        (field $user_metadata_ptr (@witx pointer u8))\n        (field $user_metadata_len (@witx usize))\n        (field $edge_max_age_ns $cache_duration_ns)\n        (field $service_id (@witx pointer (@witx char8)))\n        (field $service_id_len u32)\n    )\n)\n\n(typename $cache_write_options_mask\n    (flags (@witx repr u32)\n        $reserved\n        $request_headers ;;; Only allowed for non-transactional `insert`\n        $vary_rule\n        $initial_age_ns\n        $stale_while_revalidate_ns\n        $surrogate_keys\n        $length\n        $user_metadata\n        $sensitive_data\n        $edge_max_age_ns\n        $service_id\n    )\n)\n\n(typename $cache_get_body_options\n    (record\n        (field $from u64)\n        (field $to u64)\n    )\n)\n\n(typename $cache_get_body_options_mask\n    (flags (@witx repr u32)\n        $reserved\n        $from\n        $to\n    )\n)\n\n;;; The status of this lookup (and potential transaction)\n(typename $cache_lookup_state\n    (flags (@witx repr u32)\n        ;; A cached object was found.\n        $found\n        ;; The cached object is valid to use (implies $found)\n        $usable\n        ;; The cached object is stale (but may or may not be valid to use)\n        $stale\n        ;; This client is requested to insert or revalidate an object\n        $must_insert_or_update\n        ;; The cached object is only usable if revalidation has failed.\n        ;; If $usable_if_error is set, either $must_insert_or_update will be set\n        ;; (in which case the client must revalidate before use)\n        ;; or $usable will be set (in which case revalidation has already failed).\n        $usable_if_error\n    )\n)\n\n(module $fastly_cache\n    ;;; Performs a non-request-collapsing cache lookup.\n    ;;;\n    ;;; Returns a result without waiting for any request collapsing that may be ongoing.\n    (@interface func (export \"lookup\")\n        (param $cache_key (list u8))\n        (param $options_mask $cache_lookup_options_mask)\n        (param $options (@witx pointer $cache_lookup_options))\n        (result $err (expected $cache_handle (error $fastly_status)))\n    )\n\n    ;;; Performs a non-request-collapsing cache insertion (or update).\n    ;;;\n    ;;; The returned handle is to a streaming body that is used for writing the object into\n    ;;; the cache.\n    (@interface func (export \"insert\")\n        (param $cache_key (list u8))\n        (param $options_mask $cache_write_options_mask)\n        (param $options (@witx pointer $cache_write_options))\n        (result $err (expected $body_handle (error $fastly_status)))\n    )\n\n    ;;; The entrypoint to the replace API.\n    ;;;\n    ;;; This operation always participates in request collapsing and may return stale objects.\n    (@interface func (export \"replace\")\n        (param $cache_key (list u8))\n        (param $options_mask $cache_replace_options_mask)\n        (param $options (@witx pointer $cache_replace_options))\n        (result $err (expected $cache_replace_handle (error $fastly_status)))\n    )\n\n    ;;; Replace an object in the cache with the given metadata\n    ;;;\n    ;;; The returned handle is to a streaming body that is used for writing the object into\n    ;;; the cache.\n    (@interface func (export \"replace_insert\")\n        (param $handle $cache_replace_handle)\n        (param $options_mask $cache_write_options_mask)\n        (param $options (@witx pointer $cache_write_options))\n        (result $err (expected $body_handle (error $fastly_status)))\n    )\n\n    ;;; Gets the age of the existing object during replace, returning the\n    ;;; `$none` error if there was no object.\n    (@interface func (export \"replace_get_age_ns\")\n        (param $handle $cache_replace_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Gets a range of the existing object body, returning the `$none` error if there\n    ;;; was no existing object.\n    ;;;\n    ;;; The returned `body_handle` must be closed before calling this function\n    ;;; again on the same `cache_replace_handle`.\n    (@interface func (export \"replace_get_body\")\n        (param $handle $cache_replace_handle)\n        (param $options_mask $cache_get_body_options_mask)\n        (param $options $cache_get_body_options)\n        (result $err (expected $body_handle (error $fastly_status)))\n    )\n\n    ;;; Gets the number of cache hits for the existing object during replace,\n    ;;; returning the `$none` error if there was no object.\n    (@interface func (export \"replace_get_hits\")\n        (param $handle $cache_replace_handle)\n        (result $err (expected $cache_hit_count (error $fastly_status)))\n    )\n\n    ;;; Gets the content length of the existing object during replace,\n    ;;; returning the `$none` error if there was no object, or no content\n    ;;; length was provided.\n    (@interface func (export \"replace_get_length\")\n        (param $handle $cache_replace_handle)\n        (result $err (expected $cache_object_length (error $fastly_status)))\n    )\n\n    ;;; Gets the configured max age of the existing object during replace,\n    ;;; returning the `$none` error if there was no object.\n    (@interface func (export \"replace_get_max_age_ns\")\n        (param $handle $cache_replace_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Gets the configured stale-while-revalidate period of the existing\n    ;;; object during replace, returning the `$none` error if there was no\n    ;;; object.\n    (@interface func (export \"replace_get_stale_while_revalidate_ns\")\n        (param $handle $cache_replace_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Gets the lookup state of the existing object during replace, returning\n    ;;; the `$none` error if there was no object.\n    ;;;\n    ;;; Note that FOUND == USABLE, and means \"usable\" (fresh or stale-while-revalidate).\n    ;;; Some SDKs were released that checked only FOUND to infer \"usable\";\n    ;;; we preserve the equivalence for backwards compatibility.\n    (@interface func (export \"replace_get_state\")\n        (param $handle $cache_replace_handle)\n        (result $err (expected $cache_lookup_state (error $fastly_status)))\n    )\n\n    ;;; Gets the user metadata of the existing object during replace, returning\n    ;;; the `$none` error if there was no object.\n    (@interface func (export \"replace_get_user_metadata\")\n        (param $handle $cache_replace_handle)\n        (param $user_metadata_out_ptr (@witx pointer u8))\n        (param $user_metadata_out_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; The entrypoint to the request-collapsing cache transaction API.\n    ;;;\n    ;;; This operation always participates in request collapsing and may return stale objects. To bypass\n    ;;; request collapsing, use `lookup` and `insert` instead.\n    (@interface func (export \"transaction_lookup\")\n        (param $cache_key (list u8))\n        (param $options_mask $cache_lookup_options_mask)\n        (param $options (@witx pointer $cache_lookup_options))\n        (result $err (expected $cache_handle (error $fastly_status)))\n    )\n\n    ;;; The entrypoint to the request-collapsing cache transaction API, returning instead of waiting on busy.\n    ;;;\n    ;;; This operation always participates in request collapsing and may return stale objects. To bypass\n    ;;; request collapsing, use `lookup` and `insert` instead.\n    (@interface func (export \"transaction_lookup_async\")\n        (param $cache_key (list u8))\n        (param $options_mask $cache_lookup_options_mask)\n        (param $options (@witx pointer $cache_lookup_options))\n        (result $err (expected $cache_busy_handle (error $fastly_status)))\n    )\n\n    ;;; Continues the lookup transaction from which the given busy handle was returned,\n    ;;; waiting for the leader transaction if request collapsed, and returns a cache handle.\n    (@interface func (export \"cache_busy_handle_wait\")\n        (param $busy_handle $cache_busy_handle)\n        (result $err (expected $cache_handle (error $fastly_status)))\n    )\n\n    ;;; Insert an object into the cache with the given metadata.\n    ;;;\n    ;;; Can only be used in if the cache handle state includes the `$must_insert_or_update` flag.\n    ;;;\n    ;;; The returned handle is to a streaming body that is used for writing the object into\n    ;;; the cache.\n    (@interface func (export \"transaction_insert\")\n        (param $handle $cache_handle)\n        (param $options_mask $cache_write_options_mask)\n        (param $options (@witx pointer $cache_write_options))\n        (result $err (expected $body_handle (error $fastly_status)))\n    )\n\n    ;;; Insert an object into the cache with the given metadata, and return a readable stream of the\n    ;;; bytes as they are stored.\n    ;;;\n    ;;; This helps avoid the \"slow reader\" problem on a teed stream, for example when a program wishes\n    ;;; to store a backend request in the cache while simultaneously streaming to a client in an HTTP\n    ;;; response.\n    ;;;\n    ;;; The returned body handle is to a streaming body that is used for writing the object _into_\n    ;;; the cache. The returned cache handle provides a separate transaction for reading out the\n    ;;; newly cached object to send elsewhere.\n    (@interface func (export \"transaction_insert_and_stream_back\")\n        (param $handle $cache_handle)\n        (param $options_mask $cache_write_options_mask)\n        (param $options (@witx pointer $cache_write_options))\n        (result $err (expected (tuple $body_handle $cache_handle) (error $fastly_status)))\n    )\n\n    ;;; Update the metadata of an object in the cache without changing its data.\n    ;;;\n    ;;; Can only be used in if the cache handle state includes both of the flags:\n    ;;; - `$found`\n    ;;; - `$must_insert_or_update`\n    (@interface func (export \"transaction_update\")\n        (param $handle $cache_handle)\n        (param $options_mask $cache_write_options_mask)\n        (param $options (@witx pointer $cache_write_options))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Cancel an obligation to provide an object to the cache.\n    ;;;\n    ;;; Useful if there is an error before streaming is possible, e.g. if a backend is unreachable.\n    (@interface func (export \"transaction_cancel\")\n        (param $handle $cache_handle)\n        (result $err (expected (error $fastly_status))))\n\n    ;;; Close an interaction with the cache that has not yet finished request collapsing.\n    (@interface func (export \"close_busy\")\n        (param $handle $cache_busy_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Close an ongoing interaction with the cache.\n    ;;;\n    ;;; If the cache handle state includes the `$must_insert_or_update` (and hence no insert or\n    ;;; update has been performed), closing the handle cancels any request collapsing, potentially\n    ;;; choosing a new waiter to perform the insertion/update.\n    (@interface func (export \"close\")\n        (param $handle $cache_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Get the state of a cache lookup, waiting for the lookup to complete if necessary.\n    ;;;\n    ;;; Note that FOUND == USABLE, and means \"usable\" (fresh or stale-while-revalidate).\n    ;;; Some SDKs were released that checked only FOUND to infer \"usable\";\n    ;;; we preserve the equivalence for backwards compatibility.\n    (@interface func (export \"get_state\")\n        (param $handle $cache_handle)\n        (result $err (expected $cache_lookup_state (error $fastly_status)))\n    )\n\n    ;;; Gets the user metadata of the found object, returning the `$none` error if there\n    ;;; was no found object.\n    (@interface func (export \"get_user_metadata\")\n        (param $handle $cache_handle)\n        (param $user_metadata_out_ptr (@witx pointer u8))\n        (param $user_metadata_out_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Gets a range of the found object body, returning the `$none` error if there\n    ;;; was no found object.\n    ;;;\n    ;;; The returned `body_handle` must be closed before calling this function again on the same\n    ;;; `cache_handle`.\n    (@interface func (export \"get_body\")\n        (param $handle $cache_handle)\n        (param $options_mask $cache_get_body_options_mask)\n        (param $options $cache_get_body_options)\n        (result $err (expected $body_handle (error $fastly_status)))\n    )\n\n    ;;; Gets the content length of the found object, returning the `$none` error if there\n    ;;; was no found object, or no content length was provided.\n    (@interface func (export \"get_length\")\n        (param $handle $cache_handle)\n        (result $err (expected $cache_object_length (error $fastly_status)))\n    )\n\n    ;;; Gets the configured max age of the found object, returning the `$none` error if there\n    ;;; was no found object.\n    (@interface func (export \"get_max_age_ns\")\n        (param $handle $cache_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Gets the configured stale-while-revalidate period of the found object, returning the\n    ;;; `$none` error if there was no found object.\n    (@interface func (export \"get_stale_while_revalidate_ns\")\n        (param $handle $cache_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Gets the age of the found object, returning the `$none` error if there\n    ;;; was no found object.\n    (@interface func (export \"get_age_ns\")\n        (param $handle $cache_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Gets the number of cache hits for the found object, returning the `$none` error if there\n    ;;; was no found object.\n    (@interface func (export \"get_hits\")\n        (param $handle $cache_handle)\n        (result $err (expected $cache_hit_count (error $fastly_status)))\n    )\n)\n"
  },
  {
    "path": "wasm_abi/compute-at-edge-abi/compute-at-edge.witx",
    "content": "(use \"typenames.witx\")\n(use \"cache.witx\")\n(use \"config-store.witx\")\n(use \"http-cache.witx\")\n(use \"shielding.witx\")\n\n(module $fastly_abi\n    (@interface func (export \"init\")\n        (param $abi_version u64)\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n;; DEPRECATED\n(module $fastly_uap\n    ;; DEPRECATED\n    (@interface func (export \"parse\")\n        (param $user_agent string)\n\n        (param $family (@witx pointer (@witx char8)))\n        (param $family_len (@witx usize))\n        (param $family_nwritten_out (@witx pointer (@witx usize)))\n\n        (param $major (@witx pointer (@witx char8)))\n        (param $major_len (@witx usize))\n        (param $major_nwritten_out (@witx pointer (@witx usize)))\n\n        (param $minor (@witx pointer (@witx char8)))\n        (param $minor_len (@witx usize))\n        (param $minor_nwritten_out (@witx pointer (@witx usize)))\n\n        (param $patch (@witx pointer (@witx char8)))\n        (param $patch_len (@witx usize))\n        (param $patch_nwritten_out (@witx pointer (@witx usize)))\n\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_http_body\n    (@interface func (export \"append\")\n        (param $dest $body_handle)\n        (param $src $body_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"new\")\n        (result $err (expected $body_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"read\")\n        (param $h $body_handle)\n        (param $buf (@witx pointer u8))\n        (param $buf_len (@witx usize))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    (@interface func (export \"write\")\n        (param $h $body_handle)\n        (param $buf (list u8))\n        (param $end $body_write_end)\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    ;;; Frees the body on the host.\n    ;;;\n    ;;; For streaming bodies, this is a _successful_ stream termination, which will signal\n    ;;; via framing that the body transfer is complete.\n    (@interface func (export \"close\")\n        (param $h $body_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Frees a streaming body on the host _unsuccessfully_, so that framing makes clear that\n    ;;; the body is incomplete.\n    (@interface func (export \"abandon\")\n        (param $h $body_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"trailer_append\")\n        (param $h $body_handle)\n        (param $name (list u8))\n        (param $value (list u8))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"trailer_names_get\")\n        (param $h $body_handle)\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $cursor $multi_value_cursor)\n        (param $ending_cursor_out (@witx pointer $multi_value_cursor_result))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"trailer_value_get\")\n        (param $h $body_handle)\n        (param $name (list u8))\n        (param $value (@witx pointer (@witx char8)))\n        (param $value_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"trailer_values_get\")\n        (param $h $body_handle)\n        (param $name (list u8))\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $cursor $multi_value_cursor)\n        (param $ending_cursor_out (@witx pointer $multi_value_cursor_result))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Returns a u64 body length if the length of a body is known, or `FastlyStatus::None`\n    ;;; otherwise.\n    ;;;\n    ;;; If the length is unknown, it is likely due to the body arising from an HTTP/1.1 message with\n    ;;; chunked encoding, an HTTP/2 or later message with no `content-length`, or being a streaming\n    ;;; body.\n    ;;;\n    ;;; Note that receiving a length from this function does not guarantee that the full number of\n    ;;; bytes can actually be read from the body. For example, when proxying a response from a\n    ;;; backend, this length may reflect the `content-length` promised in the response, but if the\n    ;;; backend connection is closed prematurely, fewer bytes may be delivered before this body\n    ;;; handle can no longer be read.\n    (@interface func (export \"known_length\")\n        (param $h $body_handle)\n        (result $err (expected $body_length (error $fastly_status)))\n    )\n)\n\n(module $fastly_log\n    (@interface func (export \"endpoint_get\")\n        (param $name (list u8))\n        (result $err (expected $endpoint_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"write\")\n        (param $h $endpoint_handle)\n        (param $msg (list u8))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n)\n\n(module $fastly_http_req\n    (@interface func (export \"body_downstream_get\")\n        (result $err (expected\n                (tuple $request_handle $body_handle)\n                (error $fastly_status)))\n    )\n\n    (@interface func (export \"cache_override_set\")\n        (param $h $request_handle)\n        (param $tag $cache_override_tag)\n        (param $ttl u32)\n        (param $stale_while_revalidate u32)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"cache_override_v2_set\")\n        (param $h $request_handle)\n        (param $tag $cache_override_tag)\n        (param $ttl u32)\n        (param $stale_while_revalidate u32)\n        (param $sk (list u8))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_client_ip_addr\n    (@interface func (export \"downstream_client_ip_addr\")\n        ;; must be a 16-byte array\n        (param $addr_octets_out (@witx pointer (@witx char8)))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_server_ip_addr\n    (@interface func (export \"downstream_server_ip_addr\")\n        ;; must be a 16-byte array\n        (param $addr_octets_out (@witx pointer (@witx char8)))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_client_h2_fingerprint\n    (@interface func (export \"downstream_client_h2_fingerprint\")\n        (param $h2fp_out (@witx pointer (@witx char8)))\n        (param $h2fp_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_client_request_id\n    (@interface func (export \"downstream_client_request_id\")\n        (param $reqid_out (@witx pointer (@witx char8)))\n        (param $reqid_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_client_oh_fingerprint\n    (@interface func (export \"downstream_client_oh_fingerprint\")\n        (param $ohfp_out (@witx pointer (@witx char8)))\n        (param $ohfp_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_client_ddos_detected\n    (@interface func (export \"downstream_client_ddos_detected\")\n        (result $err (expected $ddos_detected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_tls_cipher_openssl_name\n    (@interface func (export \"downstream_tls_cipher_openssl_name\")\n        (param $cipher_out (@witx pointer (@witx char8)))\n        (param $cipher_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_tls_protocol\n    (@interface func (export \"downstream_tls_protocol\")\n        (param $protocol_out (@witx pointer (@witx char8)))\n        (param $protocol_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_tls_client_hello\n    (@interface func (export \"downstream_tls_client_hello\")\n        (param $chello_out (@witx pointer (@witx char8)))\n        (param $chello_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_tls_raw_client_certificate\n    (@interface func (export \"downstream_tls_raw_client_certificate\")\n        (param $raw_client_cert_out (@witx pointer (@witx char8)))\n        (param $raw_client_cert_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_tls_client_cert_verify_result\n    (@interface func (export \"downstream_tls_client_cert_verify_result\")\n        (result $err (expected $client_cert_verify_result (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_tls_ja3_md5\n    (@interface func (export \"downstream_tls_ja3_md5\")\n        ;; must be a 16-byte array\n        (param $cja3_md5_out (@witx pointer (@witx char8)))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_tls_ja4\n    (@interface func (export \"downstream_tls_ja4\")\n         (param $ja4_out (@witx pointer (@witx char8)))\n         (param $ja4_max_len (@witx usize))\n         (param $nwritten_out (@witx pointer (@witx usize)))\n         (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_compliance_region\n    (@interface func (export \"downstream_compliance_region\")\n         (param $region_out (@witx pointer (@witx char8)))\n         (param $region_max_len (@witx usize))\n         (param $nwritten_out (@witx pointer (@witx usize)))\n         (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"new\")\n        (result $err (expected $request_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_names_get\")\n        (param $h $request_handle)\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $cursor $multi_value_cursor)\n        (param $ending_cursor_out (@witx pointer $multi_value_cursor_result))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_original_header_names\n    (@interface func (export \"original_header_names_get\")\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $cursor $multi_value_cursor)\n        (param $ending_cursor_out (@witx pointer $multi_value_cursor_result))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use fastly_http_downstream::downstream_original_header_count\n    (@interface func (export \"original_header_count\")\n        (result $err (expected $header_count (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_value_get\")\n        (param $h $request_handle)\n        (param $name (list u8))\n        (param $value (@witx pointer (@witx char8)))\n        (param $value_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_values_get\")\n        (param $h $request_handle)\n        (param $name (list u8))\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $cursor $multi_value_cursor)\n        (param $ending_cursor_out (@witx pointer $multi_value_cursor_result))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_values_set\")\n        (param $h $request_handle)\n        (param $name (list u8))\n        ;;; contains multiple values separated by \\0\n        (param $values (list (@witx char8)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_insert\")\n        (param $h $request_handle)\n        (param $name (list u8))\n        (param $value (list u8))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_append\")\n        (param $h $request_handle)\n        (param $name (list u8))\n        (param $value (list u8))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_remove\")\n        (param $h $request_handle)\n        (param $name (list u8))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"method_get\")\n        (param $h $request_handle)\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"method_set\")\n        (param $h $request_handle)\n        (param $method string)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"uri_get\")\n        (param $h $request_handle)\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"uri_set\")\n        (param $h $request_handle)\n        (param $uri string)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"version_get\")\n        (param $h $request_handle)\n        (result $err (expected $http_version (error $fastly_status)))\n    )\n\n    (@interface func (export \"version_set\")\n        (param $h $request_handle)\n        (param $version $http_version)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"send\")\n        (param $h $request_handle)\n        (param $b $body_handle)\n        (param $backend string)\n        (result $err (expected\n                (tuple $response_handle $body_handle)\n                (error $fastly_status)))\n    )\n\n    ;; The behavior of this method is identical to the original except for the `$error_detail`\n    ;; out-parameter.\n    ;;\n    ;; If the returned `$fastly_status` is OK, `$error_detail` will not be read. Otherwise,\n    ;; the status is returned identically to the original `send`, but `$error_detail` is populated.\n    ;; Since `$send_error_detail` provides much more granular information about failures, it should\n    ;; be used by SDKs as the primary source of error information in favor of `$fastly_status`.\n    ;;\n    ;; Make sure to initialize `$error_detail` with the full complement of mask values that the\n    ;; guest supports. If the corresponding bits in the mask are not set, the host will not populate\n    ;; fields in the `$error_detail` struct even if there are values available for those fields.\n    ;; This allows forward compatibility when new fields are added.\n    (@interface func (export \"send_v2\")\n        (param $h $request_handle)\n        (param $b $body_handle)\n        (param $backend string)\n        (param $error_detail (@witx pointer $send_error_detail))\n        (result $err (expected\n                (tuple $response_handle $body_handle)\n                (error $fastly_status)))\n    )\n\n    ;; Like `send_v2`, but does NOT provide caching of any form, and does not set `X-Cache` or\n    ;; similar.\n    ;;\n    ;; This hostcall is intended to ultimately replace `send_v2` as HTTP caching becomes managed\n    ;; explicitly at the SDK level.\n    ;;\n    ;; Any cache override setting on the request is ignored.\n    ;;\n    ;; Making this a distinct hostcall, rather than a cache override variant, may make it easier\n    ;; to tell when support for old styles of send can be safely dropped.\n    (@interface func (export \"send_v3\")\n        (param $h $request_handle)\n        (param $b $body_handle)\n        (param $backend string)\n        (param $error_detail (@witx pointer $send_error_detail))\n        (result $err (expected\n                (tuple $response_handle $body_handle)\n                (error $fastly_status)))\n    )\n\n    (@interface func (export \"send_async\")\n        (param $h $request_handle)\n        (param $b $body_handle)\n        (param $backend string)\n        (result $err (expected $pending_request_handle\n                (error $fastly_status)))\n    )\n\n    ;; Like `send_async`, but does NOT provide caching of any form, and does not set `X-Cache` or\n    ;; similar.\n    ;;\n    ;; Also encompasses `send_async_streaming` by including a streaming flag.\n    ;;\n    ;; This hostcall is intended to ultimately replace `send_async{_streaming}` as HTTP\n    ;; caching becomes managed explicitly at the SDK level.\n    ;;\n    ;; Any cache override setting on the request is ignored.\n    ;;\n    ;; Making this a distinct hostcall, rather than a cache override variant, may make it easier\n    ;; to tell when support for old styles of send can be safely dropped.\n    (@interface func (export \"send_async_v2\")\n        (param $h $request_handle)\n        (param $b $body_handle)\n        (param $backend string)\n        (param $streaming u32)\n        (result $err (expected $pending_request_handle\n                (error $fastly_status)))\n    )\n\n    (@interface func (export \"send_async_streaming\")\n        (param $h $request_handle)\n        (param $b $body_handle)\n        (param $backend string)\n        (result $err (expected $pending_request_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"pending_req_poll\")\n        (param $h $pending_request_handle)\n        (result $err (expected\n                (tuple $is_done\n                    $response_handle\n                    $body_handle)\n                (error $fastly_status)))\n    )\n\n    ;; See `send_v2` for an explanation of the `$error_detail` out-parameter.\n    (@interface func (export \"pending_req_poll_v2\")\n        (param $h $pending_request_handle)\n        (param $error_detail (@witx pointer $send_error_detail))\n        (result $err (expected\n                (tuple $is_done\n                    $response_handle\n                    $body_handle)\n                (error $fastly_status)))\n    )\n\n    (@interface func (export \"pending_req_wait\")\n        (param $h $pending_request_handle)\n        (result $err (expected\n                (tuple $response_handle $body_handle)\n                (error $fastly_status)))\n    )\n\n    ;; See `send_v2` for an explanation of the `$error_detail` out-parameter.\n    (@interface func (export \"pending_req_wait_v2\")\n        (param $h $pending_request_handle)\n        (param $error_detail (@witx pointer $send_error_detail))\n        (result $err (expected\n                (tuple $response_handle $body_handle)\n                (error $fastly_status)))\n    )\n\n    (@interface func (export \"pending_req_select\")\n        (param $hs (list $pending_request_handle))\n        (result $err (expected\n                (tuple $done_idx $response_handle $body_handle)\n                (error $fastly_status)))\n    )\n\n    ;; See `send_v2` for an explanation of the `$error_detail` out-parameter.\n    (@interface func (export \"pending_req_select_v2\")\n        (param $hs (list $pending_request_handle))\n        (param $error_detail (@witx pointer $send_error_detail))\n        (result $err (expected\n                (tuple $done_idx $response_handle $body_handle)\n                (error $fastly_status)))\n    )\n\n    ;;; DEPRECATED: use fastly_http_downstream::fastly_key_is_valid\n    ;;;\n    ;;; Returns whether or not the original client request arrived with a\n    ;;; Fastly-Key belonging to a user with the rights to purge content on this\n    ;;; service.\n    (@interface func (export \"fastly_key_is_valid\")\n        (result $err (expected $is_valid (error $fastly_status)))\n    )\n\n    (@interface func (export \"close\")\n        (param $h $request_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"auto_decompress_response_set\")\n        (param $h $request_handle)\n        (param $encodings $content_encodings)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"upgrade_websocket\")\n        (param $backend_name string)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use redirect_to_websocket_proxy_v2\n    (@interface func (export \"redirect_to_websocket_proxy\")\n        (param $backend_name string)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; DEPRECATED: use redirect_to_grip_proxy_v2\n    (@interface func (export \"redirect_to_grip_proxy\")\n        (param $backend_name string)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"redirect_to_websocket_proxy_v2\")\n        (param $h $request_handle)\n        (param $backend_name string)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"redirect_to_grip_proxy_v2\")\n        (param $h $request_handle)\n        (param $backend_name string)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Adjust how this requests's framing headers are determined.\n    (@interface func (export \"framing_headers_mode_set\")\n        (param $h $request_handle)\n        (param $mode $framing_headers_mode)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Create a backend for later use\n    (@interface func (export \"register_dynamic_backend\")\n        (param $name_prefix string)\n        (param $target string)\n        (param $backend_config_mask $backend_config_options)\n        (param $backend_configuration (@witx pointer $dynamic_backend_config))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Hostcall for Fastly Compute guests to inspect request HTTP traffic\n    ;;; using the NGWAF lookaside service.\n    (@interface func (export \"inspect\")\n        (param $req $request_handle)\n        (param $body $body_handle)\n        (param $insp_info_mask $inspect_info_mask)\n        (param $insp_info (@witx pointer $inspect_info))\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    ;;; Instead of having this request cache in this service's space, use the\n    ;;; cache of the named service\n    (@interface func (export \"on_behalf_of\")\n        (param $req $request_handle)\n        (param $service string)\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_http_downstream\n    ;;; Indicate to the host that we will accept a new request from a client in the future.\n    (@interface func (export \"next_request\")\n        (param $options_mask $next_request_options_mask)\n        (param $options (@witx pointer $next_request_options))\n        (result $err (expected $request_promise_handle (error $fastly_status)))\n    )\n\n    ;;; Block until an additional request from a client is ready,\n    ;;; and return the request and its associated body.\n    (@interface func (export \"next_request_wait\")\n        (param $handle $request_promise_handle)\n        (result $err (expected (tuple $request_handle $body_handle) (error $fastly_status)))\n    )\n\n    ;;; Abandon a promised future request. Indicate that we are no longer willing to receive\n    ;;; an additional request from a client in the future.\n    (@interface func (export \"next_request_abandon\")\n        (param $handle $request_promise_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_original_header_names\")\n        (param $req $request_handle)\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $cursor $multi_value_cursor)\n        (param $ending_cursor_out (@witx pointer $multi_value_cursor_result))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_original_header_count\")\n        (param $req $request_handle)\n        (result $err (expected $header_count (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_client_ip_addr\")\n        (param $req $request_handle)\n        ;; must be a 16-byte array\n        (param $addr_octets_out (@witx pointer (@witx char8)))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_server_ip_addr\")\n        (param $req $request_handle)\n        ;; must be a 16-byte array\n        (param $addr_octets_out (@witx pointer (@witx char8)))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_client_h2_fingerprint\")\n        (param $req $request_handle)\n        (param $h2fp_out (@witx pointer (@witx char8)))\n        (param $h2fp_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_client_request_id\")\n        (param $req $request_handle)\n        (param $reqid_out (@witx pointer (@witx char8)))\n        (param $reqid_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_client_oh_fingerprint\")\n        (param $req $request_handle)\n        (param $ohfp_out (@witx pointer (@witx char8)))\n        (param $ohfp_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_client_ddos_detected\")\n        (param $req $request_handle)\n        (result $err (expected $ddos_detected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_tls_cipher_openssl_name\")\n        (param $req $request_handle)\n        (param $cipher_out (@witx pointer (@witx char8)))\n        (param $cipher_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_tls_protocol\")\n        (param $req $request_handle)\n        (param $protocol_out (@witx pointer (@witx char8)))\n        (param $protocol_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_tls_client_hello\")\n        (param $req $request_handle)\n        (param $chello_out (@witx pointer (@witx char8)))\n        (param $chello_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_tls_raw_client_certificate\")\n        (param $req $request_handle)\n        (param $raw_client_cert_out (@witx pointer (@witx char8)))\n        (param $raw_client_cert_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_tls_client_cert_verify_result\")\n        (param $req $request_handle)\n        (result $err (expected $client_cert_verify_result (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_tls_client_servername\")\n        (param $req $request_handle)\n        (param $tls_sni_out (@witx pointer (@witx char8)))\n        (param $tls_sni_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_tls_ja3_md5\")\n        (param $req $request_handle)\n        ;; must be a 16-byte array\n        (param $cja3_md5_out (@witx pointer (@witx char8)))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_tls_ja4\")\n        (param $req $request_handle)\n        (param $ja4_out (@witx pointer (@witx char8)))\n        (param $ja4_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_compliance_region\")\n        (param $req $request_handle)\n        (param $region_out (@witx pointer (@witx char8)))\n        (param $region_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"fastly_key_is_valid\")\n        (param $req $request_handle)\n        (result $err (expected $is_valid (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_bot_analyzed\")\n        (param $req $request_handle)\n        (result $err (expected $bot_analyzed (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_bot_detected\")\n        (param $req $request_handle)\n        (result $err (expected $bot_detected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_bot_name\")\n        (param $req $request_handle)\n        (param $bot_name_out (@witx pointer (@witx char8)))\n        (param $bot_name_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_bot_category\")\n        (param $req $request_handle)\n        (param $bot_category_out (@witx pointer (@witx char8)))\n        (param $bot_category_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_bot_category_kind\")\n        (param $req $request_handle)\n        (result $err (expected $bot_category_kind (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_bot_verified\")\n        (param $req $request_handle)\n        (result $err (expected $bot_verified (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_anonymous\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_anonymous (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_anonymous_vpn\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_anonymous_vpn (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_hosting_provider\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_hosting_provider (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_proxy_over_vpn\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_proxy_over_vpn (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_public_proxy\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_public_proxy (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_relay_proxy\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_relay_proxy (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_residential_proxy\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_residential_proxy (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_smart_dns_proxy\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_smart_dns_proxy (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_tor_exit_node\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_tor_exit_node (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_is_vpn_datacenter\")\n        (param $req $request_handle)\n        (result $err (expected $resvpnproxy_is_vpn_datacenter (error $fastly_status)))\n    )\n\n    (@interface func (export \"downstream_resvpnproxy_vpn_service_name\")\n        (param $req $request_handle)\n        (param $vpn_service_name_out (@witx pointer (@witx char8)))\n        (param $vpn_service_name_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_http_resp\n    (@interface func (export \"new\")\n        (result $err (expected $response_handle (error $fastly_status)))\n    )\n\n    ;; The following directly mirror header & version methods on req\n\n    (@interface func (export \"header_names_get\")\n        (param $h $response_handle)\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $cursor $multi_value_cursor)\n        (param $ending_cursor_out (@witx pointer $multi_value_cursor_result))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_value_get\")\n        (param $h $response_handle)\n        (param $name (list u8))\n        (param $value (@witx pointer (@witx char8)))\n        (param $value_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_values_get\")\n        (param $h $response_handle)\n        (param $name (list u8))\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $cursor $multi_value_cursor)\n        (param $ending_cursor_out (@witx pointer $multi_value_cursor_result))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_values_set\")\n        (param $h $response_handle)\n        (param $name (list u8))\n        ;;; contains multiple values separated by \\0\n        (param $values (list (@witx char8)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_insert\")\n        (param $h $response_handle)\n        (param $name (list u8))\n        (param $value (list u8))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_append\")\n        (param $h $response_handle)\n        (param $name (list u8))\n        (param $value (list u8))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"header_remove\")\n        (param $h $response_handle)\n        (param $name (list u8))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"version_get\")\n        (param $h $response_handle)\n        (result $err (expected $http_version (error $fastly_status)))\n    )\n\n    (@interface func (export \"version_set\")\n        (param $h $response_handle)\n        (param $version $http_version)\n        (result $err (expected (error $fastly_status)))\n    )\n    ;; End directly mirror header & version methods on req\n\n    (@interface func (export \"send_downstream\")\n        (param $h $response_handle)\n        (param $b $body_handle)\n        (param $streaming u32)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"status_get\")\n        (param $h $response_handle)\n        (result $err (expected $http_status (error $fastly_status)))\n    )\n\n    (@interface func (export \"status_set\")\n        (param $h $response_handle)\n        (param $status $http_status)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"close\")\n        (param $h $response_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Adjust how this response's framing headers are determined.\n    (@interface func (export \"framing_headers_mode_set\")\n        (param $h $response_handle)\n        (param $mode $framing_headers_mode)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Adjust the response's connection reuse mode.\n    (@interface func (export \"http_keepalive_mode_set\")\n        (param $h $response_handle)\n        (param $mode $http_keepalive_mode)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Hostcall for getting the destination IP used for this request.\n    ;;;\n    ;;; The buffer for the IP address must be 16 bytes. `addr_octets_out`\n    ;;; will be set to 4 for IPv4 addresses, and 16 for IPv6.\n    (@interface func (export \"get_addr_dest_ip\")\n        (param $h $response_handle)\n        ;; must be a 16-byte array\n        (param $addr_octets_out (@witx pointer (@witx char8)))\n        (result $err (expected $num_bytes (error $fastly_status)))\n    )\n\n    ;;; Hostcall for getting the destination port used for this request.\n    (@interface func (export \"get_addr_dest_port\")\n        (param $h $response_handle)\n        (result $err (expected $port (error $fastly_status)))\n    )\n)\n\n(module $fastly_dictionary\n    (@interface func (export \"open\")\n        (param $name string)\n        (result $err (expected $dictionary_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"get\")\n        (param $h $dictionary_handle)\n        (param $key string)\n        (param $value (@witx pointer (@witx char8)))\n        (param $value_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_geo\n    (@interface func (export \"lookup\")\n        (param $addr_octets (@witx const_pointer (@witx char8)))\n        (param $addr_len (@witx usize))\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_device_detection\n    (@interface func (export \"lookup\")\n        (param $user_agent string)\n\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_erl\n    (@interface func (export \"check_rate\")\n        (param $rc string)\n        (param $entry string)\n        (param $delta u32)\n        (param $window u32)\n        (param $limit u32)\n        (param $pb string)\n        (param $ttl u32)\n\n        (result $err (expected $blocked (error $fastly_status)))\n    )\n\n    (@interface func (export \"ratecounter_increment\")\n        (param $rc string)\n        (param $entry string)\n        (param $delta u32)\n\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"ratecounter_lookup_rate\")\n        (param $rc string)\n        (param $entry string)\n        (param $window u32)\n\n        (result $err (expected $rate (error $fastly_status)))\n    )\n\n    (@interface func (export \"ratecounter_lookup_count\")\n        (param $rc string)\n        (param $entry string)\n        (param $duration u32)\n\n        (result $err (expected $count (error $fastly_status)))\n    )\n\n    (@interface func (export \"penaltybox_add\")\n        (param $pb string)\n        (param $entry string)\n        (param $ttl u32)\n\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"penaltybox_has\")\n        (param $pb string)\n        (param $entry string)\n\n        (result $err (expected $has (error $fastly_status)))\n    )\n)\n\n;; NOTE: These are deprecated, use the fastly_kv_store hostcalls\n(module $fastly_object_store\n    (@interface func (export \"open\")\n        (param $name string)\n        (result $err (expected $object_store_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"lookup\")\n        (param $store $object_store_handle)\n        (param $key string)\n        (param $body_handle_out (@witx pointer $body_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"lookup_async\")\n        (param $store $object_store_handle)\n        (param $key string)\n        (param $pending_handle_out (@witx pointer $pending_kv_lookup_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"pending_lookup_wait\")\n        (param $pending_objstr_handle $pending_kv_lookup_handle)\n        (param $body_handle_out (@witx pointer $body_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"insert\")\n        (param $store $object_store_handle)\n        (param $key string)\n        (param $body_handle $body_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"insert_async\")\n        (param $store $object_store_handle)\n        (param $key string)\n        (param $body_handle $body_handle)\n        (param $pending_handle_out (@witx pointer $pending_kv_insert_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"pending_insert_wait\")\n        (param $pending_objstr_handle $pending_kv_insert_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"delete_async\")\n        (param $store $object_store_handle)\n        (param $key string)\n        (param $pending_handle_out (@witx pointer $pending_kv_delete_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"pending_delete_wait\")\n        (param $pending_objstr_handle $pending_kv_delete_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_image_optimizer\n    (@interface func (export \"transform_image_optimizer_request\")\n        (param $origin_image_request $request_handle)\n        (param $origin_image_request_body $body_handle)\n        (param $origin_image_request_backend string)\n        (param $io_transform_config_mask $image_optimizer_transform_config_options)\n        (param $io_transform_configuration (@witx pointer $image_optimizer_transform_config))\n        (param $io_error_detail (@witx pointer $image_optimizer_error_detail))\n        (result $err (expected\n                (tuple $response_handle $body_handle)\n                (error $fastly_status)))\n    )\n)\n\n(module $fastly_kv_store\n    (@interface func (export \"open\")\n        (param $name string)\n        (result $err (expected $kv_store_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"lookup\")\n        (param $store $kv_store_handle)\n        (param $key string)\n        (param $lookup_config_mask $kv_lookup_config_options)\n        (param $lookup_configuration (@witx pointer $kv_lookup_config))\n        (param $handle_out (@witx pointer $kv_store_lookup_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; deprecated, generation always returns 0\n    ;; via a failed u32::parse of a u64\n    (@interface func (export \"lookup_wait\")\n        (param $handle $kv_store_lookup_handle)\n        (param $body_handle_out (@witx pointer $body_handle))\n        (param $metadata_buf (@witx pointer (@witx char8)))\n        (param $metadata_buf_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (param $generation_out (@witx pointer u32))\n        (param $kv_error_out (@witx pointer $kv_error))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"lookup_wait_v2\")\n        (param $handle $kv_store_lookup_handle)\n        (param $body_handle_out (@witx pointer $body_handle))\n        (param $metadata_buf (@witx pointer (@witx char8)))\n        (param $metadata_buf_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (param $generation_out (@witx pointer u64))\n        (param $kv_error_out (@witx pointer $kv_error))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"insert\")\n        (param $store $kv_store_handle)\n        (param $key string)\n        (param $body_handle $body_handle)\n        (param $insert_config_mask $kv_insert_config_options)\n        (param $insert_configuration (@witx pointer $kv_insert_config))\n        (param $handle_out (@witx pointer $kv_store_insert_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"insert_wait\")\n        (param $handle $kv_store_insert_handle)\n        (param $kv_error_out (@witx pointer $kv_error))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"delete\")\n        (param $store $kv_store_handle)\n        (param $key string)\n        (param $delete_config_mask $kv_delete_config_options)\n        (param $delete_configuration (@witx pointer $kv_delete_config))\n        (param $handle_out (@witx pointer $kv_store_delete_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"delete_wait\")\n        (param $handle $kv_store_delete_handle)\n        (param $kv_error_out (@witx pointer $kv_error))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"list\")\n        (param $store $kv_store_handle)\n        (param $list_config_mask $kv_list_config_options)\n        (param $list_configuration (@witx pointer $kv_list_config))\n        (param $handle_out (@witx pointer $kv_store_list_handle))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"list_wait\")\n        (param $handle $kv_store_list_handle)\n        (param $body_handle_out (@witx pointer $body_handle))\n        (param $kv_error_out (@witx pointer $kv_error))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_secret_store\n    (@interface func (export \"open\")\n        (param $name string)\n        (result $err (expected $secret_store_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"get\")\n        (param $store $secret_store_handle)\n        (param $key string)\n        (result $err (expected $secret_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"plaintext\")\n        (param $secret $secret_handle)\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    (@interface func (export \"from_bytes\")\n        (param $buf (@witx pointer (@witx char8)))\n        (param $buf_len (@witx usize))\n        (result $err (expected $secret_handle (error $fastly_status)))\n    )\n)\n\n(module $fastly_backend\n    ;; Returns 1 if a backend with this name exists.\n    (@interface func (export \"exists\")\n        (param $backend string)\n        (result $err (expected\n                $backend_exists\n                (error $fastly_status)))\n    )\n\n    (@interface func (export \"is_healthy\")\n        (param $backend string)\n        (result $err (expected $backend_health (error $fastly_status)))\n    )\n\n    ;; Returns 1 if the backend is a \"dynamic\" backend.\n    (@interface func (export \"is_dynamic\")\n        (param $backend string)\n        (result $err (expected $is_dynamic (error $fastly_status)))\n    )\n\n    ;; Get the host of this backend.\n    (@interface func (export \"get_host\")\n        (param $backend string)\n        (param $value (@witx pointer (@witx char8)))\n        (param $value_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; Get the \"override host\" for this backend.\n    ;;\n    ;; This is used to change the `Host` header sent to the backend. See the Fastly documentation\n    ;; on this topic here: https://docs.fastly.com/en/guides/specifying-an-override-host\n    (@interface func (export \"get_override_host\")\n        (param $backend string)\n        (param $value (@witx pointer (@witx char8)))\n        (param $value_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;; Get the remote TCP port of the backend connection for the request.\n    (@interface func (export \"get_port\")\n        (param $backend string)\n        (result $err (expected\n                $port\n                (error $fastly_status)))\n    )\n\n    ;; Get the connection timeout of the backend.\n    (@interface func (export \"get_connect_timeout_ms\")\n        (param $backend string)\n        (result $err (expected\n                $timeout_ms\n                (error $fastly_status)))\n    )\n\n    ;; Get the first byte timeout of the backend.\n    (@interface func (export \"get_first_byte_timeout_ms\")\n        (param $backend string)\n        (result $err (expected\n                $timeout_ms\n                (error $fastly_status)))\n    )\n\n    ;; Get the between byte timeout of the backend.\n    (@interface func (export \"get_between_bytes_timeout_ms\")\n        (param $backend string)\n        (result $err (expected\n                $timeout_ms\n                (error $fastly_status)))\n    )\n\n    ;; Get the idle timeout for HTTP keepalive connections to the backend.\n    (@interface func (export \"get_http_keepalive_time\")\n        (param $backend string)\n        (result $err (expected\n                $timeout_ms\n                (error $fastly_status)))\n    )\n\n    ;; Get whether TCP keepalives have been enabled on the backend.\n    (@interface func (export \"get_tcp_keepalive_enable\")\n        (param $backend string)\n        (result $err (expected\n                $is_keepalive\n                (error $fastly_status)))\n    )\n\n    ;; Get how long to wait between sending each TCP keepalive probe to the\n    ;; backend.\n    (@interface func (export \"get_tcp_keepalive_interval\")\n        (param $backend string)\n        (result $err (expected\n                $timeout_secs\n                (error $fastly_status)))\n    )\n\n    ;; Get how long to wait after sending the last data before starting to send\n    ;; TCP keepalives to the backend.\n    (@interface func (export \"get_tcp_keepalive_probes\")\n        (param $backend string)\n        (result $err (expected\n                $probe_count\n                (error $fastly_status)))\n    )\n\n    ;; Get how long to wait after sending the last data before starting to send\n    ;; TCP keepalives to the backend.\n    (@interface func (export \"get_tcp_keepalive_time\")\n        (param $backend string)\n        (result $err (expected\n                $timeout_secs\n                (error $fastly_status)))\n    )\n\n    ;; Returns 1 if the backend is configured to use SSL.\n    (@interface func (export \"is_ssl\")\n        (param $backend string)\n        (result $err (expected $is_ssl (error $fastly_status)))\n    )\n\n    ;; Get the minimum SSL version this backend will use.\n    (@interface func (export \"get_ssl_min_version\")\n        (param $backend string)\n        (result $err (expected $tls_version (error $fastly_status)))\n    )\n\n    ;; Get the maximum SSL version this backend will use.\n    (@interface func (export \"get_ssl_max_version\")\n        (param $backend string)\n        (result $err (expected $tls_version (error $fastly_status)))\n    )\n)\n\n(module $fastly_async_io\n    ;;; Blocks until one of the given objects is ready for I/O, or the optional timeout expires.\n    ;;;\n    ;;; Valid object handles includes bodies and pending requests. See the `async_item_handle`\n    ;;; definition for more details, including what I/O actions are associated with each handle\n    ;;; type.\n    ;;;\n    ;;; The timeout is specified in milliseconds, or 0 if no timeout is desired.\n    ;;;\n    ;;; Returns the _index_ (not handle!) of the first object that is ready, or u32::MAX if the\n    ;;; timeout expires before any objects are ready for I/O.\n    (@interface func (export \"select\")\n        (param $hs (list $async_item_handle))\n        (param $timeout_ms u32)\n        (result $err (expected $ready_idx (error $fastly_status)))\n    )\n\n    ;;; Returns 1 if the given async item is \"ready\" for its associated I/O action, 0 otherwise.\n    ;;;\n    ;;; If an object is ready, the I/O action is guaranteed to complete without blocking.\n    ;;;\n    ;;; Valid object handles includes bodies and pending requests. See the `async_item_handle`\n    ;;; definition for more details, including what I/O actions are associated with each handle\n    ;;; type.\n    (@interface func (export \"is_ready\")\n        (param $handle $async_item_handle)\n        (result $err (expected $is_done (error $fastly_status)))\n    )\n)\n\n(module $fastly_purge\n    (@interface func (export \"purge_surrogate_key\")\n        (param $surrogate_key string)\n        (param $options_mask $purge_options_mask)\n        (param $options (@witx pointer $purge_options))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n\n(module $fastly_compute_runtime\n    (@interface func (export \"get_vcpu_ms\")\n        (result $err (expected $vcpu_ms (error $fastly_status)))\n    )\n\n    ;;; Get a snapshot of the current dynamic memory usage, rounded up to the nearest mebibyte (2^20).\n    ;;;\n    ;;; This includes usage from the Wasm linear memory (heap) and usage from host allocations\n    ;;; made on behalf of this sandbox, e.g. buffered bodies of HTTP responses.\n    ;;; The returned value is just a snapshot- it can change without any explicit action\n    ;;; by the sandbox (for instance, additional response data coming in from an HTTP response.)\n    ;;; It can also change over time / across runs, as the Compute platform's memory usage\n    ;;; changes. Consider the returned value with these uncertainties in mind.\n    (@interface func (export \"get_heap_mib\")\n        (result $err (expected $memory_mib (error $fastly_status)))\n    )\n)\n\n(module $fastly_acl\n    (@interface func (export \"open\")\n        (param $name string)\n        (result $err (expected $acl_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"lookup\")\n        (param $acl $acl_handle)\n        (param $ip_octets (@witx const_pointer (@witx char8)))\n        (param $ip_len (@witx usize))\n        (param $body_handle_out (@witx pointer $body_handle))\n        (param $acl_error_out (@witx pointer $acl_error))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n"
  },
  {
    "path": "wasm_abi/compute-at-edge-abi/config-store.witx",
    "content": ";; Config Store ABI\n\n;;; A handle to an Config Store.\n(typename $config_store_handle (handle))\n\n(module $fastly_config_store\n    (@interface func (export \"open\")\n        (param $name string)\n        (result $err (expected $config_store_handle (error $fastly_status)))\n    )\n\n    (@interface func (export \"get\")\n        (param $h $config_store_handle)\n        (param $key string)\n        (param $value (@witx pointer (@witx char8)))\n        (param $value_max_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n"
  },
  {
    "path": "wasm_abi/compute-at-edge-abi/http-cache.witx",
    "content": ";;; Overall, this should look very familiar to users of the Core Cache API. The primary differences\n;;; are:\n;;;\n;;; - HTTP `request_handle`s and `response_handle`s are used rather than relying on the user to\n;;;   encode headers, status codes, etc in `user_metadata`.\n;;;\n;;; - Convenience functions specific to HTTP semantics are provided, such as `is_request_cacheable`,\n;;;   `get_suggested_backend_request`, `get_suggested_cache_options`, and\n;;;   `transaction_record_not_cacheable`.\n;;;\n;;; The HTTP-specific behavior of these functions is intended to support applications that match the\n;;; normative guidance in RFC 9111. For example, `is_request_cacheable` returns `false` for `POST`\n;;; requests. However, this answer along with those of many of these functions explicitly provide\n;;; _suggestions_; they do not necessarily need to be followed if custom behavior is required, such\n;;; as caching `POST` responses when the application author knows that to be safe.\n;;;\n;;; The starting points for this API are `lookup` (no request collapsing) and `transaction_lookup`\n;;; (request collapsing).\n\n;;; A handle to an HTTP Cache transaction.\n(typename $http_cache_handle (handle))\n\n;;; Boolean: 1 == true, 0 == false.\n(typename $is_cacheable u32)\n\n;;; Boolean: 1 == true, 0 == false.\n(typename $is_sensitive u32)\n\n;;; The suggested action to take for spec-recommended behavior following\n;;; `prepare_response_for_storage`.\n(typename $http_storage_action\n    (enum (@witx tag u32)\n        ;; Insert the response into cache (for `transaction-insert` and\n        ;; `transaction-insert-and-stream-back`).\n        $insert\n\n        ;; Update the stale response in cache (for `transaction-update` and\n        ;; `transaction-update-and-return-fresh`).\n        $update\n\n        ;; Do not store this response.\n        $do_not_store\n\n        ;; Do not store this response, and furthermore record its non-cacheability for other pending\n        ;; requests (`transaction_record_not_cacheable`).\n        $record_uncacheable\n    )\n)\n\n;;; Non-required options for cache lookups.\n;;;\n;;; This record is always provided along with an `http_cache_lookup_options_mask` value that\n;;; indicates which of the fields in this record are valid.\n(typename $http_cache_lookup_options\n    (record\n        ;; Cache key to use in lieu of the automatically-generated cache key based on the request's\n        ;; properties.\n        (field $override_key_ptr (@witx pointer (@witx char8)))\n        (field $override_key_len (@witx usize))\n        ;; Name of the backend to which the request will eventually be sent.\n        (field $backend_name_ptr (@witx pointer (@witx char8)))\n        (field $backend_name_len (@witx usize))\n    )\n)\n\n;;; Options mask for `http_cache_lookup_options`.\n(typename $http_cache_lookup_options_mask\n    (flags (@witx repr u32)\n        $reserved\n        $override_key\n        $backend_name\n    )\n)\n\n;;; Options for cache insertions and updates.\n;;;\n;;; This record is always provided along with an `http_cache_write_options_mask` value that\n;;; indicates which of the fields in this record are valid.\n(typename $http_cache_write_options\n    (record\n        ;; The maximum age of the response before it is considered stale, in nanoseconds.\n        ;;\n        ;; This field is required; there is no flag for it in `http_cache_write_options_mask`.\n        (field $max_age_ns $cache_duration_ns)\n\n        ;; A list of header names to use when calculating variants for this response.\n        ;;\n        ;; The format is a string containing header names separated by spaces.\n        (field $vary_rule_ptr (@witx pointer (@witx char8)))\n        (field $vary_rule_len (@witx usize))\n\n        ;; The initial age of the response in nanoseconds.\n        ;;\n        ;; If this field is not set, the default value is zero.\n        ;;\n        ;; This age is used to determine the freshness lifetime of the response as well as to\n        ;; prioritize which variant to return if a subsequent lookup matches more than one vary rule\n        (field $initial_age_ns $cache_duration_ns)\n\n        ;; The maximum duration after `max_age` during which the response may be delivered stale\n        ;; while being revalidated, in nanoseconds.\n        ;;\n        ;; If this field is not set, the default value is zero.\n        (field $stale_while_revalidate_ns $cache_duration_ns)\n\n        ;; A list of surrogate keys that may be used to purge this response.\n        ;;\n        ;; The format is a string containing [valid surrogate\n        ;; keys](https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Key/)\n        ;; separated by spaces.\n        ;;\n        ;; If this field is not set, no surrogate keys will be associated with the response. This\n        ;; means that the response cannot be purged except via a purge-all operation.\n        (field $surrogate_keys_ptr (@witx pointer (@witx char8)))\n        (field $surrogate_keys_len (@witx usize))\n\n        ;; The length of the response body.\n        ;;\n        ;; If this field is not set, the length of the body is treated as unknown.\n        ;;\n        ;; When possible, this field should be set so that other clients waiting to retrieve the\n        ;; body have enough information to synthesize a `content-length` even before the complete\n        ;; body is inserted to the cache.\n        (field $length $cache_object_length)\n\n        ;; The maximum duration after `max_age` during which the response may be delivered stale\n        ;; if synchronous revalidation produces an error.\n        ;;\n        ;; If this field is not set, the default value is zero.\n        (field $stale_if_error_ns $cache_duration_ns)\n    )\n)\n\n;;; Options mask for `http_cache_write_options`.\n(typename $http_cache_write_options_mask\n    (flags (@witx repr u32)\n        $reserved\n        $vary_rule\n        $initial_age_ns\n        $stale_while_revalidate_ns\n        $surrogate_keys\n        $length\n        $sensitive_data\n        $stale_if_error_ns\n    )\n)\n\n(module $fastly_http_cache\n    ;;; Determine whether a request is cacheable per conservative RFC 9111 semantics.\n    ;;;\n    ;;; In particular, this function checks whether the request method is `GET` or `HEAD`, and\n    ;;; considers requests with other methods uncacheable. Applications where it is safe to cache\n    ;;; responses to other methods should consider using their own cacheability check instead of\n    ;;; this function.\n    (@interface func (export \"is_request_cacheable\")\n        (param $req_handle $request_handle)\n        (result $err (expected $is_cacheable (error $fastly_status)))\n    )\n\n    ;;; Retrieves the default cache key for the request.\n    ;;;\n    ;;; The `$key_out` parameter must point to an array of size `key_out_len`.\n    ;;;\n    ;;; If the guest-provided output parameter is not long enough to contain the full key,\n    ;;; the required size is written by the host to `nwritten_out` and the `$buflen`\n    ;;; error is returned.\n    ;;;\n    ;;; At the moment, HTTP cache keys must always be 32 bytes.\n    (@interface func (export \"get_suggested_cache_key\")\n        (param $req_handle $request_handle)\n        (param $key_out_ptr (@witx pointer (@witx char8)))\n        (param $key_out_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; DEPRECATED: use transaction_lookup\n    (@interface func (export \"lookup\")\n        (param $req_handle $request_handle)\n        (param $options_mask $http_cache_lookup_options_mask)\n        (param $options (@witx pointer $http_cache_lookup_options))\n        (result $err (expected $http_cache_handle (error $fastly_status)))\n    )\n\n    ;;; Perform a cache lookup based on the given request.\n    ;;;\n    ;;; This operation always participates in request collapsing and may return an obligation to\n    ;;; insert or update responses, and/or stale responses. To bypass request collapsing, use\n    ;;; `lookup` instead.\n    ;;;\n    ;;; The request is not consumed.\n    (@interface func (export \"transaction_lookup\")\n        (param $req_handle $request_handle)\n        (param $options_mask $http_cache_lookup_options_mask)\n        (param $options (@witx pointer $http_cache_lookup_options))\n        (result $err (expected $http_cache_handle (error $fastly_status)))\n    )\n\n    ;;; Insert a response into the cache with the given options, returning a streaming body handle\n    ;;; that is ready for writing or appending.\n    ;;;\n    ;;; Can only be used if the cache handle state includes the `$must_insert_or_update` flag.\n    ;;;\n    ;;; The response is consumed.\n    (@interface func (export \"transaction_insert\")\n        (param $handle $http_cache_handle)\n        (param $resp_handle $response_handle)\n        (param $options_mask $http_cache_write_options_mask)\n        (param $options (@witx pointer $http_cache_write_options))\n        (result $err (expected $body_handle (error $fastly_status)))\n    )\n\n    ;;; Insert a response into the cache with the given options, and return a fresh cache handle\n    ;;; that can be used to retrieve and stream the response while it's being inserted.\n    ;;;\n    ;;; This helps avoid the \"slow reader\" problem on a teed stream, for example when a program wishes\n    ;;; to store a backend request in the cache while simultaneously streaming to a client in an HTTP\n    ;;; response.\n    ;;;\n    ;;; The response is consumed.\n    (@interface func (export \"transaction_insert_and_stream_back\")\n        (param $handle $http_cache_handle)\n        (param $resp_handle $response_handle)\n        (param $options_mask $http_cache_write_options_mask)\n        (param $options (@witx pointer $http_cache_write_options))\n        (result $err (expected (tuple $body_handle $http_cache_handle) (error $fastly_status)))\n    )\n\n    ;;; Update freshness lifetime, response headers, and caching settings without updating the\n    ;;; response body.\n    ;;;\n    ;;; Can only be used in if the cache handle state includes both of the flags:\n    ;;; - `$found`\n    ;;; - `$must_insert_or_update`\n    ;;;\n    ;;; The response is consumed.\n    (@interface func (export \"transaction_update\")\n        (param $handle $http_cache_handle)\n        (param $resp_handle $response_handle)\n        (param $options_mask $http_cache_write_options_mask)\n        (param $options (@witx pointer $http_cache_write_options))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Update freshness lifetime, response headers, and caching settings without updating the\n    ;;; response body, and return a fresh cache handle that can be used to retrieve and stream the\n    ;;; stored response.\n    ;;;\n    ;;; Can only be used in if the cache handle state includes both of the flags:\n    ;;; - `$found`\n    ;;; - `$must_insert_or_update`\n    ;;;\n    ;;; The response is consumed.\n    (@interface func (export \"transaction_update_and_return_fresh\")\n        (param $handle $http_cache_handle)\n        (param $resp_handle $response_handle)\n        (param $options_mask $http_cache_write_options_mask)\n        (param $options (@witx pointer $http_cache_write_options))\n        (result $err (expected $http_cache_handle (error $fastly_status)))\n    )\n\n    ;;; Fulfill an obligation to provide a response to the cache by selecting a stale-if-error response.\n    ;;;\n    ;;; A guest that is obligated to insert/update the cache may not be able to produce an acceptable\n    ;;; response (e.g. unreachable backend, 5xx response). If the cache contains a response in the\n    ;;; stale-if-error period, the guest may prefer to use that response rather than returning an error.\n    ;;;\n    ;;; `transaction_choose_stale` is an alternative to `transaction_update_and_return_fresh` or \n    ;;; `transaction_insert_and_stream_back`. Like those methods, it completes a request collapse,\n    ;;; providing the stale response to all collapsed transactions; and, after calling\n    ;;; `transaction_choose_stale`, the cache handle provides the (stale) response to send to the client.\n    ;;;\n    ;;; However, `transaction_choose_stale` does not change the cached state. The next lookup will again\n    ;;; collapse and/or get an obligation to revalidate.\n    (@interface func (export \"transaction_choose_stale\")\n        (param $handle $http_cache_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Fulfill an obligation to provide a response to the cache by disabling request collapsing and\n    ;;; response caching for this cache entry.\n    ;;;\n    ;;; In Varnish terms, this function stores a hit-for-pass object.\n    ;;;\n    ;;; Only the max age and, optionally, the vary rule are read from the options mask and struct\n    ;;; for this function.\n    (@interface func (export \"transaction_record_not_cacheable\")\n        (param $handle $http_cache_handle)\n        (param $options_mask $http_cache_write_options_mask)\n        (param $options (@witx pointer $http_cache_write_options))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Abandon an obligation to provide a response to the cache.\n    ;;;\n    ;;; Useful if there is an error before streaming is possible, e.g. if a backend is unreachable.\n    ;;;\n    ;;; If there are other requests collapsed on this transaction, one of those other requests will\n    ;;; be awoken and given the obligation to provide a response. Note that if subsequent requests\n    ;;; are unlikely to yield cacheable responses, this may lead to undesired serialization of\n    ;;; requests. Consider using `transaction_record_not_cacheable` to make lookups for this request\n    ;;; bypass the cache.\n    (@interface func (export \"transaction_abandon\")\n        (param $handle $http_cache_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Close an ongoing interaction with the cache.\n    ;;;\n    ;;; If the cache handle state includes `$must_insert_or_update` (and hence no insert or update\n    ;;; has been performed), closing the handle cancels any request collapsing, potentially choosing\n    ;;; a new waiter to perform the insertion/update.\n    (@interface func (export \"close\")\n        (param $handle $http_cache_handle)\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Prepare a suggested request to make to a backend to satisfy the looked-up request.\n    ;;;\n    ;;; If there is a stored, stale response, this suggested request may be for revalidation. If the\n    ;;; looked-up request is ranged, the suggested request will be unranged in order to try caching\n    ;;; the entire response.\n    (@interface func (export \"get_suggested_backend_request\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $request_handle (error $fastly_status)))\n    )\n\n    ;;; Prepare a suggested set of cache write options for a given request and response pair.\n    ;;;\n    ;;; The ABI of this function includes several unusual types of input and output parameters.\n    ;;;\n    ;;; The bits set in the `options_mask` input parameter describe which cache options the guest is\n    ;;; requesting that the host provide.\n    ;;;\n    ;;; The `options` input parameter allows the guest to provide output parameters for\n    ;;; pointer/length options. When the corresponding bit is set in `options_mask`, the pointer and\n    ;;; length should be set in this record to be used by the host to provide the output.\n    ;;;\n    ;;; The `options_mask_out` output parameter is only used by the host to indicate the status of\n    ;;; pointer/length data in the `options_out` record. The flag for a given pointer/length\n    ;;; parameter is set by the host if the corresponding flag was set in `options_mask`, and the\n    ;;; value is present in the suggested options. If the host returns a status of `$buflen`, the\n    ;;; same set of flags will be set, but the length value of the corresponding fields in\n    ;;; `options_out` are set to the lengths that would be required to read the full value from the\n    ;;; host on a subsequent call.\n    ;;;\n    ;;; The `options_out` output parameter is where the host writes the suggested options that were\n    ;;; requested by the guest in `options_mask`. For pointer/length data, if there was enough room\n    ;;; to write the suggested option, the length field will contain the length of the data actually\n    ;;; written, while the pointer field will match the input pointer.\n    ;;;\n    ;;; The response is not consumed.\n    (@interface func (export \"get_suggested_cache_options\")\n        (param $handle $http_cache_handle)\n        (param $response $response_handle)\n        (param $options_mask $http_cache_write_options_mask)\n        (param $options (@witx pointer $http_cache_write_options))\n        (param $options_mask_out (@witx pointer $http_cache_write_options_mask))\n        (param $options_out (@witx pointer $http_cache_write_options))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Adjust a response into the appropriate form for storage and provides a storage action recommendation.\n    ;;;\n    ;;; For example, if the looked-up request contains conditional headers, this function will\n    ;;; interpret a `304 Not Modified` response for revalidation by updating headers.\n    ;;;\n    ;;; In addition to the updated response, this function returns the recommended storage action.\n    (@interface func (export \"prepare_response_for_storage\")\n        (param $handle $http_cache_handle)\n        (param $response $response_handle)\n        (result $err (expected (tuple $http_storage_action $response_handle) (error $fastly_status)))\n    )\n\n    ;;; Retrieve a stored response from the cache, returning the `$none` error if there was no\n    ;;; response found.\n    ;;;\n    ;;; If `transform_for_client` is set, the response will be adjusted according to the looked-up\n    ;;; request. For example, a response retrieved for a range request may be transformed into a\n    ;;; `206 Partial Content` response with an appropriate `content-range` header.\n    (@interface func (export \"get_found_response\")\n        (param $handle $http_cache_handle)\n        (param $transform_for_client u32)\n        (result $err (expected (tuple $response_handle $body_handle) (error $fastly_status)))\n    )\n\n    ;;; Get the state of a cache transaction.\n    ;;;\n    ;;; Primarily useful after performing the lookup to determine what subsequent operations are\n    ;;; possible and whether any insertion or update obligations exist.\n    (@interface func (export \"get_state\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $cache_lookup_state (error $fastly_status)))\n    )\n\n    ;;; Get the length of the found response, returning the `$none` error if there was no response\n    ;;; found or no length was provided.\n    (@interface func (export \"get_length\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $cache_object_length (error $fastly_status)))\n    )\n\n    ;;; Get the configured max age of the found response in nanoseconds, returning the `$none` error\n    ;;; if there was no response found.\n    (@interface func (export \"get_max_age_ns\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Get the configured stale-while-revalidate period of the found response in nanoseconds,\n    ;;; returning the `$none` error if there was no response found.\n    (@interface func (export \"get_stale_while_revalidate_ns\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Get the configured stale-if-error period of the found response in nanoseconds,\n    ;;; returning the `$none` error if there was no response found.\n    (@interface func (export \"get_stale_if_error_ns\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Get the age of the found response in nanoseconds, returning the `$none` error if there was\n    ;;; no response found.\n    (@interface func (export \"get_age_ns\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $cache_duration_ns (error $fastly_status)))\n    )\n\n    ;;; Get the number of cache hits for the found response, returning the `$none` error if there\n    ;;; was no response found.\n    ;;;\n    ;;; Note that this figure only reflects hits for a stored response in a particular cache server\n    ;;; or cluster, not the entire Fastly network.\n    (@interface func (export \"get_hits\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $cache_hit_count (error $fastly_status)))\n    )\n\n    ;;; Get whether a found response is marked as containing sensitive data, returning the `$none`\n    ;;; error if there was no response found.\n    (@interface func (export \"get_sensitive_data\")\n        (param $handle $http_cache_handle)\n        (result $err (expected $is_sensitive (error $fastly_status)))\n    )\n\n    ;;; Get the surrogate keys of the found response, returning the `$none` error if there was no\n    ;;; found response.\n    ;;;\n    ;;; The output is a list of surrogate keys separated by spaces.\n    ;;;\n    ;;; If the guest-provided output parameter is not long enough to contain the full list of\n    ;;; surrogate keys, the required size is written by the host to `nwritten_out` and the `$buflen`\n    ;;; error is returned.\n    (@interface func (export \"get_surrogate_keys\")\n        (param $handle $http_cache_handle)\n        (param $surrogate_keys_out_ptr (@witx pointer u8))\n        (param $surrogate_keys_out_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n\n    ;;; Get the vary rule of the found response, returning the `$none` error if there was no\n    ;;; response found.\n    ;;;\n    ;;; The output is a list of header names separated by spaces.\n    ;;;\n    ;;; If the guest-provided output parameter is not long enough to contain the full list of\n    ;;; surrogate keys, the required size is written by the host to `nwritten_out` and the `$buflen`\n    ;;; error is returned.\n    (@interface func (export \"get_vary_rule\")\n        (param $handle $http_cache_handle)\n        (param $vary_rule_out_ptr (@witx pointer u8))\n        (param $vary_rule_out_len (@witx usize))\n        (param $nwritten_out (@witx pointer (@witx usize)))\n        (result $err (expected (error $fastly_status)))\n    )\n)\n"
  },
  {
    "path": "wasm_abi/compute-at-edge-abi/shielding.witx",
    "content": "(typename $shield_backend_options\n  (flags (@witx repr u32)\n    $reserved\n    $use_cache_key\n    $first_byte_timeout\n  ))\n\n(typename $shield_backend_config\n  (record\n    (field $cache_key (@witx pointer (@witx char8)))\n    (field $cache_key_len u32)\n    (field $first_byte_timeout_ms u32)\n    ))\n\n(module $fastly_shielding\n\n  (@interface func (export \"shield_info\")\n    (param $name string)\n    (param $info_block (@witx pointer (@witx char8)))\n    (param $info_block_max_len (@witx usize))\n    (result $err (expected $num_bytes (error $fastly_status)))\n  )\n\n  (@interface func (export \"backend_for_shield\")\n    (param $shield_name string)\n    (param $backend_config_mask $shield_backend_options)\n    (param $backend_configuration (@witx pointer $shield_backend_config))\n    (param $backend_name_out (@witx pointer (@witx char8)))\n    (param $backend_name_max_len (@witx usize))\n    (result $err (expected $num_bytes (error $fastly_status)))\n  )\n\n)\n"
  },
  {
    "path": "wasm_abi/compute-at-edge-abi/typenames.witx",
    "content": ";;; Status codes returned from hostcalls.\n(typename $fastly_status\n    (enum (@witx tag u32)\n        ;;; Success value.\n        ;;;\n        ;;; This indicates that a hostcall finished successfully.\n        $ok\n        ;;; Generic error value.\n        ;;;\n        ;;; This means that some unexpected error occurred during a hostcall.\n        $error\n        ;;; Invalid argument.\n        $inval\n        ;;; Invalid handle.\n        ;;;\n        ;;; Returned when a request, response, or body handle is not valid.\n        $badf\n        ;;; Buffer length error.\n        ;;;\n        ;;; Returned when a buffer is too long.\n        $buflen\n        ;;; Unsupported operation error.\n        ;;;\n        ;;; This error is returned when some operation cannot be performed, because it is not supported.\n        $unsupported\n        ;;; Alignment error.\n        ;;;\n        ;;; This is returned when a pointer does not point to a properly aligned slice of memory.\n        $badalign\n        ;;; Invalid HTTP error.\n        ;;;\n        ;;; This can be returned when a method, URI, header, or status is not valid. This can also\n        ;;; be returned if a message head is too large.\n        $httpinvalid\n        ;;; HTTP user error.\n        ;;;\n        ;;; This is returned in cases where user code caused an HTTP error. For example, attempt to send\n        ;;; a 1xx response code, or a request with a non-absolute URI. This can also be caused by\n        ;;; an unexpected header: both `content-length` and `transfer-encoding`, for example.\n        $httpuser\n        ;;; HTTP incomplete message error.\n        ;;;\n        ;;; This can be returned when a stream ended unexpectedly.\n        $httpincomplete\n        ;;; A `None` error.\n        ;;;\n        ;;; This status code is used to indicate when an optional value did not exist, as opposed to\n        ;;; an empty value.\n        $none\n        ;;; Message head too large.\n        $httpheadtoolarge\n        ;;; Invalid HTTP status.\n        $httpinvalidstatus\n        ;;; Limit exceeded\n        ;;;\n        ;;; This is returned when an attempt to allocate a resource has exceeded the maximum number of\n        ;;; resources permitted. For example, creating too many response handles.\n        $limitexceeded\n        ;;; Resource temporarily unavailable\n        ;;;\n        ;;; This is returned when an attempting to retrieve a resource that is not yet available.\n        ;;; For example when attempting to read trailers from a Body that has not yet been consumed.\n        $again))\n\n;;; A tag indicating HTTP protocol versions.\n(typename $http_version\n    (enum (@witx tag u32)\n        $http_09\n        $http_10\n        $http_11\n        $h2\n        $h3))\n\n;;; HTTP status codes.\n(typename $http_status u16)\n\n(typename $body_write_end\n    (enum (@witx tag u32)\n        $back\n        $front))\n\n;;; A handle to an HTTP request or response body.\n(typename $body_handle (handle))\n;;; A handle to an HTTP request.\n(typename $request_handle (handle))\n;;; A handle to an HTTP response.\n(typename $response_handle (handle))\n;;; A handle to a currently-pending asynchronous HTTP request.\n(typename $pending_request_handle (handle))\n;;; A handle to a logging endpoint.\n(typename $endpoint_handle (handle))\n;;; A handle to an Edge Dictionary.\n(typename $dictionary_handle (handle))\n;;; (DEPRECATED) A handle to an Object Store.\n(typename $object_store_handle (handle))\n;;; (DEPRECATED) A handle to a pending KV lookup.\n(typename $pending_kv_lookup_handle (handle))\n;;; (DEPRECATED) A handle to a pending KV insert.\n(typename $pending_kv_insert_handle (handle))\n;;; (DEPRECATED) A handle to a pending KV delete.\n(typename $pending_kv_delete_handle (handle))\n;;; (DEPRECATED) A handle to a pending KV list.\n(typename $pending_kv_list_handle (handle))\n;;; A handle to an KV Store.\n(typename $kv_store_handle (handle))\n;;; A handle to a KV Store lookup.\n(typename $kv_store_lookup_handle (handle))\n;;; A handle to a KV Store insert.\n(typename $kv_store_insert_handle (handle))\n;;; A handle to a KV Store delete.\n(typename $kv_store_delete_handle (handle))\n;;; A handle to a KV Store list.\n(typename $kv_store_list_handle (handle))\n;;; A handle to a Secret Store.\n(typename $secret_store_handle (handle))\n;;; A handle to an individual secret.\n(typename $secret_handle (handle))\n;;; A handle to an ACL.\n(typename $acl_handle (handle))\n;;; A handle to a request promise.\n(typename $request_promise_handle (handle))\n;;; A handle to an object supporting generic async operations.\n;;; Can be a `body_handle`, `pending_request_handle`,\n;;; `cache_handle`, `cache_busy_handle`, `cache_replace_handle` (see cache.witx),\n;;; `request_promise_handle`, or other handles.\n;;;\n;;; Each async item has an associated I/O action:\n;;;\n;;; * Pending requests: awaiting the response headers / `Response` object\n;;; * Normal bodies: reading bytes from the body\n;;; * Streaming bodies: writing bytes to the body\n;;; * Cache handles: the caller has been selected to perform a fetch, or there is data ready\n;;; * Request promise: a new request is ready, or there will be no request provided via this handle\n;;;\n;;; For writing bytes, note that there is a large host-side buffer that bytes can eagerly be written\n;;; into, even before the origin itself consumes that data.\n(typename $async_item_handle (handle))\n\n;;; A \"multi-value\" cursor.\n(typename $multi_value_cursor u32)\n;;; -1 represents \"finished\", non-negative represents a $multi_value_cursor:\n(typename $multi_value_cursor_result s64)\n\n;;; An override for response caching behavior.\n;;; A zero value indicates that the origin response's cache control headers should be used.\n(typename $cache_override_tag\n    (flags (@witx repr u32)\n        ;;; Do not cache the response to this request, regardless of the origin response's headers.\n        $pass\n        $ttl\n        $stale_while_revalidate\n        $pci))\n(typename $num_bytes (@witx usize))\n(typename $header_count u32)\n(typename $is_done u32)\n(typename $done_idx u32)\n(typename $is_valid u32)\n(typename $inserted u32)\n(typename $ready_idx u32)\n(typename $ddos_detected u32)\n\n(typename $port u16)\n(typename $timeout_ms u32)\n(typename $timeout_secs u32)\n(typename $probe_count u32)\n(typename $backend_exists u32)\n(typename $is_dynamic u32)\n(typename $is_keepalive u32)\n(typename $is_ssl u32)\n(typename $backend_health\n    (enum (@witx tag u32)\n        $unknown\n        $healthy\n        $unhealthy))\n\n(typename $bot_analyzed u32)\n(typename $bot_detected u32)\n(typename $bot_category_kind u32)\n(typename $bot_verified u32)\n\n(typename $content_encodings\n    (flags (@witx repr u32)\n        $gzip))\n\n(typename $framing_headers_mode\n    (enum (@witx tag u32)\n        $automatic\n        $manually_from_headers))\n\n(typename $http_keepalive_mode\n    (enum (@witx tag u32)\n        $automatic\n        $no_keepalive))\n\n(typename $tls_version\n    (enum (@witx tag u32)\n       $tls_1\n       $tls_1_1\n       $tls_1_2\n       $tls_1_3))\n\n(typename $kv_lookup_config_options\n    (flags (@witx repr u32)\n       $reserved\n       ))\n\n(typename $kv_lookup_config\n  (record\n    (field $reserved u32)\n    ))\n\n(typename $kv_delete_config_options\n    (flags (@witx repr u32)\n       $reserved\n       ))\n\n(typename $kv_delete_config\n  (record\n    (field $reserved u32)\n    ))\n\n(typename $kv_insert_config_options\n    (flags (@witx repr u32)\n       $reserved\n       $background_fetch\n       ;; reserved_2 was previously if_generation_match (u32)\n       $reserved_2\n       $metadata\n       $time_to_live_sec\n       $if_generation_match\n       ))\n\n(typename $kv_insert_mode\n    (enum (@witx tag u32)\n       $overwrite\n       $add\n       $append\n       $prepend))\n\n(typename $kv_insert_config\n  (record\n    (field $mode $kv_insert_mode)\n    (field $unused u32)\n    (field $metadata (@witx pointer (@witx char8)))\n    (field $metadata_len u32)\n    (field $time_to_live_sec u32)\n    (field $if_generation_match u64)\n    ))\n\n(typename $kv_list_config_options\n    (flags (@witx repr u32)\n       $reserved\n       $cursor\n       $limit\n       $prefix\n       ))\n\n(typename $kv_list_mode\n    (enum (@witx tag u32)\n       $strong\n       $eventual))\n\n(typename $kv_list_config\n  (record\n    (field $mode $kv_list_mode)\n    (field $cursor (@witx pointer (@witx char8)))\n    (field $cursor_len u32)\n    (field $limit u32)\n    (field $prefix (@witx pointer (@witx char8)))\n    (field $prefix_len u32)\n    ))\n\n(typename $kv_error\n    (enum (@witx tag u32)\n        ;;; The $kv_error has not been set.\n        $uninitialized\n        ;;; There was no error.\n        $ok\n        ;;; KV store cannot or will not process the request due to something that is perceived to be a client error\n        ;;; This will map to the api's 400 codes\n        $bad_request\n        ;;; KV store cannot find the requested resource\n        ;;; This will map to the api's 404 codes\n        $not_found\n        ;;; KV store cannot fulfill the request, as defined by the client's prerequisites (ie. if-generation-match)\n        ;;; This will map to the api's 412 codes\n        $precondition_failed\n        ;;; The size limit for a KV store key was exceeded.\n        ;;; This will map to the api's 413 codes\n        $payload_too_large\n        ;;; The system encountered an unexpected internal error.\n        ;;; This will map to all remaining http error codes\n        $internal_error\n        ;;; Too many requests have been made to the KV store.\n        ;;; This will map to the api's 429 codes\n        $too_many_requests\n        ))\n\n(typename $backend_config_options\n    (flags (@witx repr u32)\n       $reserved\n       $host_override\n       $connect_timeout\n       $first_byte_timeout\n       $between_bytes_timeout\n       $use_ssl\n       $ssl_min_version\n       $ssl_max_version\n       $cert_hostname\n       $ca_cert\n       $ciphers\n       $sni_hostname\n       $dont_pool\n       $client_cert\n       $grpc\n       $keepalive\n       $pooling_limits\n       $prefer_ipv4\n       ))\n\n(typename $dynamic_backend_config\n  (record\n    (field $host_override (@witx pointer (@witx char8)))\n    (field $host_override_len u32)\n    (field $connect_timeout_ms u32)\n    (field $first_byte_timeout_ms u32)\n    (field $between_bytes_timeout_ms u32)\n    (field $ssl_min_version $tls_version)\n    (field $ssl_max_version $tls_version)\n    (field $cert_hostname (@witx pointer (@witx char8)))\n    (field $cert_hostname_len u32)\n    (field $ca_cert (@witx pointer (@witx char8)))\n    (field $ca_cert_len u32)\n    (field $ciphers (@witx pointer (@witx char8)))\n    (field $ciphers_len u32)\n    (field $sni_hostname (@witx pointer (@witx char8)))\n    (field $sni_hostname_len u32)\n    (field $client_certificate (@witx pointer (@witx char8)))\n    (field $client_certificate_len u32)\n    (field $client_key $secret_handle)\n    (field $http_keepalive_time_ms $timeout_ms)\n    (field $tcp_keepalive_enable u32)\n    (field $tcp_keepalive_interval_secs $timeout_secs)\n    (field $tcp_keepalive_probes $probe_count)\n    (field $tcp_keepalive_time_secs $timeout_secs)\n    (field $max_connections u32)\n    (field $max_use u32)\n    (field $max_lifetime_ms $timeout_ms)\n    ))\n\n;;; TLS client certificate verified result from downstream.\n(typename $client_cert_verify_result\n    (enum (@witx tag u32)\n        ;;; Success value.\n        ;;;\n        ;;; This indicates that client certificate verified successfully.\n        $ok\n        ;;; bad certificate error.\n        ;;;\n        ;;; This error means the certificate is corrupt\n        ;;; (e.g., the certificate signatures do not verify correctly).\n        $bad_certificate\n        ;;; certificate revoked error.\n        ;;;\n        ;;; This error means the client certificate is revoked by its signer.\n        $certificate_revoked\n        ;;; certificate expired error.\n        ;;;\n        ;;; This error means the client certificate has expired or is not currently valid.\n        $certificate_expired\n        ;;; unknown CA error.\n        ;;;\n        ;;; This error means the valid certificate chain or partial chain was received,\n        ;;; but the certificate was not accepted because the CA certificate could not be\n        ;;; located or could not be matched with a known trust anchor.\n        $unknown_ca\n        ;;; certificate missing error.\n        ;;;\n        ;;; This error means the client does not provide a certificate\n        ;;; during the handshake..\n        $certificate_missing\n        ;;; certificate unknown error.\n        ;;;\n        ;;; This error means the client certificate was received, but some other (unspecified)\n        ;;; issue arose in processing the certificate, rendering it unacceptable.\n        $certificate_unknown))\n\n(typename $purge_options_mask\n    (flags (@witx repr u32)\n        $soft_purge\n        $ret_buf ;; all ret_buf fields must be populated\n    )\n)\n\n(typename $purge_options\n    (record\n        ;; JSON purge response as in https://developer.fastly.com/reference/api/purging/#purge-tag\n        (field $ret_buf_ptr (@witx pointer u8))\n        (field $ret_buf_len (@witx usize))\n        (field $ret_buf_nwritten_out (@witx pointer (@witx usize)))\n    )\n)\n\n(typename $send_error_detail_tag\n    (enum (@witx tag u32)\n        ;;; The $send_error_detail struct has not been populated.\n        $uninitialized\n        ;;; There was no send error.\n        $ok\n        ;;; The system encountered a timeout when trying to find an IP address for the backend\n        ;;; hostname.\n        $dns_timeout\n        ;;; The system encountered a DNS error when trying to find an IP address for the backend\n        ;;; hostname. The fields $dns_error_rcode and $dns_error_info_code may be set in the\n        ;;; $send_error_detail.\n        $dns_error\n        ;;; The system cannot determine which backend to use, or the specified backend was invalid.\n        $destination_not_found\n        ;;; The system considers the backend to be unavailable; e.g., recent attempts to communicate\n        ;;; with it may have failed, or a health check may indicate that it is down.\n        $destination_unavailable\n        ;;; The system cannot find a route to the next-hop IP address.\n        $destination_ip_unroutable\n        ;;; The system's connection to the backend was refused.\n        $connection_refused\n        ;;; The system's connection to the backend was closed before a complete response was\n        ;;; received.\n        $connection_terminated\n        ;;; The system's attempt to open a connection to the backend timed out.\n        $connection_timeout\n        ;;; The system is configured to limit the number of connections it has to the backend, and\n        ;;; that limit has been exceeded.\n        $connection_limit_reached\n        ;;; The system encountered an error when verifying the certificate presented by the backend.\n        $tls_certificate_error\n        ;;; The system encountered an error with the backend TLS configuration.\n        $tls_configuration_error\n        ;;; The system received an incomplete response to the request from the backend.\n        $http_incomplete_response\n        ;;; The system received a response to the request whose header section was considered too\n        ;;; large.\n        $http_response_header_section_too_large\n        ;;; The system received a response to the request whose body was considered too large.\n        $http_response_body_too_large\n        ;;; The system reached a configured time limit waiting for the complete response.\n        $http_response_timeout\n        ;;; The system received a response to the request whose status code or reason phrase was\n        ;;; invalid.\n        $http_response_status_invalid\n        ;;; The process of negotiating an upgrade of the HTTP version between the system and the\n        ;;; backend failed.\n        $http_upgrade_failed\n        ;;; The system encountered an HTTP protocol error when communicating with the backend. This\n        ;;; error will only be used when a more specific one is not defined.\n        $http_protocol_error\n        ;;; An invalid cache key was provided for the request.\n        $http_request_cache_key_invalid\n        ;;; An invalid URI was provided for the request.\n        $http_request_uri_invalid\n        ;;; The system encountered an unexpected internal error.\n        $internal_error\n        ;;; The system received a TLS alert from the backend. The field $tls_alert_id may be set in\n        ;;; the $send_error_detail.\n        $tls_alert_received\n        ;;; The system encountered a TLS error when communicating with the backend, either during\n        ;;; the handshake or afterwards.\n        $tls_protocol_error\n        ;;; The system received an HTTP/2 error code from the server. The fields $h2_error_frame and\n        ;;; $h2_error_code may be set in the $send_error_detail.\n        $h2_error\n        ))\n\n;;; Mask representing which fields are understood by the guest, and which have been set by the host.\n;;;\n;;; When the guest calls hostcalls with a mask, it should set every bit in the mask that corresponds\n;;; to a defined flag. This signals the host to write only to fields with a set bit, allowing\n;;; forward compatibility for existing guest programs even after new fields are added to the struct.\n(typename $send_error_detail_mask\n    (flags (@witx repr u32)\n       $reserved\n       $dns_error_rcode\n       $dns_error_info_code\n       $tls_alert_id\n       $h2_error\n       ))\n\n(typename $send_error_detail\n  (record\n    (field $tag $send_error_detail_tag)\n    (field $mask $send_error_detail_mask)\n    (field $dns_error_rcode u16)\n    (field $dns_error_info_code u16)\n    (field $tls_alert_id u8)\n    (field $h2_error_frame u8)\n    (field $h2_error_code u32)\n    ))\n\n(typename $blocked u32)\n(typename $rate u32)\n(typename $count u32)\n(typename $has u32)\n\n(typename $body_length u64)\n(typename $vcpu_ms u64)\n(typename $memory_mib u32)\n\n(typename $inspect_info_mask\n    (flags (@witx repr u32)\n        $reserved\n        $corp\n        $workspace\n        $override_client_ip\n    )\n)\n\n(typename $inspect_info\n    (record\n        (field $corp (@witx pointer (@witx char8)))\n        (field $corp_len u32)\n        (field $workspace (@witx pointer (@witx char8)))\n        (field $workspace_len u32)\n        (field $override_client_ip_ptr (@witx pointer u8))\n        (field $override_client_ip_len u32)\n    )\n)\n\n(typename $acl_error\n    (enum (@witx tag u32)\n        ;;; The $acl_error has not been initialized.\n        $uninitialized\n        ;;; There was no error.\n        $ok\n        ;;; This will map to the api's 204 code.\n        ;;; It indicates that the request succeeded, yet returned nothing.\n        $no_content\n        ;;; This will map to the api's 429 code.\n        ;;; Too many requests have been made.\n        $too_many_requests\n        ))\n\n(typename $image_optimizer_transform_config_options\n    (flags (@witx repr u32)\n        $reserved\n        $sdk_claims_opts\n        ))\n\n(typename $image_optimizer_transform_config\n  (record\n    ;; sdk_claims_opts contains any Image Optimizer API parameters that were set\n    ;; as well as the Image Optimizer region the request is meant for.\n    (field $sdk_claims_opts (@witx pointer (@witx char8)))\n    (field $sdk_claims_opts_len u32)\n    ))\n\n(typename $image_optimizer_error_tag\n    (enum (@witx tag u32)\n        $uninitialized\n        $ok\n        $error\n        $warning\n    )\n)\n\n(typename $image_optimizer_error_detail\n    (record\n        (field $tag $image_optimizer_error_tag)\n        (field $message (@witx pointer (@witx char8)))\n        (field $message_len u32)\n    )\n)\n\n(typename $next_request_options_mask\n    (flags (@witx repr u32)\n        $reserved\n        $timeout\n    ))\n\n(typename $next_request_options\n    (record\n        ;; A maximum amount of time to wait for a downstream request to appear, in milliseconds.\n        (field $timeout_ms u64)\n    ))\n\n(typename $resvpnproxy_is_anonymous u32)\n(typename $resvpnproxy_is_anonymous_vpn u32)\n(typename $resvpnproxy_is_hosting_provider u32)\n(typename $resvpnproxy_is_proxy_over_vpn u32)\n(typename $resvpnproxy_is_public_proxy u32)\n(typename $resvpnproxy_is_relay_proxy u32)\n(typename $resvpnproxy_is_residential_proxy u32)\n(typename $resvpnproxy_is_smart_dns_proxy u32)\n(typename $resvpnproxy_is_tor_exit_node u32)\n(typename $resvpnproxy_is_vpn_datacenter u32)\n"
  },
  {
    "path": "wasm_abi/wit/deps/cli/command.wit",
    "content": "package wasi:cli@0.2.6;\n\n@since(version = 0.2.0)\nworld command {\n  @since(version = 0.2.0)\n  include imports;\n\n  @since(version = 0.2.0)\n  export run;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/cli/environment.wit",
    "content": "@since(version = 0.2.0)\ninterface environment {\n  /// Get the POSIX-style environment variables.\n  ///\n  /// Each environment variable is provided as a pair of string variable names\n  /// and string value.\n  ///\n  /// Morally, these are a value import, but until value imports are available\n  /// in the component model, this import function should return the same\n  /// values each time it is called.\n  @since(version = 0.2.0)\n  get-environment: func() -> list<tuple<string, string>>;\n\n  /// Get the POSIX-style arguments to the program.\n  @since(version = 0.2.0)\n  get-arguments: func() -> list<string>;\n\n  /// Return a path that programs should use as their initial current working\n  /// directory, interpreting `.` as shorthand for this.\n  @since(version = 0.2.0)\n  initial-cwd: func() -> option<string>;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/cli/exit.wit",
    "content": "@since(version = 0.2.0)\ninterface exit {\n  /// Exit the current instance and any linked instances.\n  @since(version = 0.2.0)\n  exit: func(status: result);\n\n  /// Exit the current instance and any linked instances, reporting the\n  /// specified status code to the host.\n  ///\n  /// The meaning of the code depends on the context, with 0 usually meaning\n  /// \"success\", and other values indicating various types of failure.\n  ///\n  /// This function does not return; the effect is analogous to a trap, but\n  /// without the connotation that something bad has happened.\n  @unstable(feature = cli-exit-with-code)\n  exit-with-code: func(status-code: u8);\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/cli/imports.wit",
    "content": "package wasi:cli@0.2.6;\n\n@since(version = 0.2.0)\nworld imports {\n  @since(version = 0.2.0)\n  include wasi:clocks/imports@0.2.6;\n  @since(version = 0.2.0)\n  include wasi:filesystem/imports@0.2.6;\n  @since(version = 0.2.0)\n  include wasi:sockets/imports@0.2.6;\n  @since(version = 0.2.0)\n  include wasi:random/imports@0.2.6;\n  @since(version = 0.2.0)\n  include wasi:io/imports@0.2.6;\n\n  @since(version = 0.2.0)\n  import environment;\n  @since(version = 0.2.0)\n  import exit;\n  @since(version = 0.2.0)\n  import stdin;\n  @since(version = 0.2.0)\n  import stdout;\n  @since(version = 0.2.0)\n  import stderr;\n  @since(version = 0.2.0)\n  import terminal-input;\n  @since(version = 0.2.0)\n  import terminal-output;\n  @since(version = 0.2.0)\n  import terminal-stdin;\n  @since(version = 0.2.0)\n  import terminal-stdout;\n  @since(version = 0.2.0)\n  import terminal-stderr;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/cli/run.wit",
    "content": "@since(version = 0.2.0)\ninterface run {\n  /// Run the program.\n  @since(version = 0.2.0)\n  run: func() -> result;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/cli/stdio.wit",
    "content": "@since(version = 0.2.0)\ninterface stdin {\n  @since(version = 0.2.0)\n  use wasi:io/streams@0.2.6.{input-stream};\n\n  @since(version = 0.2.0)\n  get-stdin: func() -> input-stream;\n}\n\n@since(version = 0.2.0)\ninterface stdout {\n  @since(version = 0.2.0)\n  use wasi:io/streams@0.2.6.{output-stream};\n\n  @since(version = 0.2.0)\n  get-stdout: func() -> output-stream;\n}\n\n@since(version = 0.2.0)\ninterface stderr {\n  @since(version = 0.2.0)\n  use wasi:io/streams@0.2.6.{output-stream};\n\n  @since(version = 0.2.0)\n  get-stderr: func() -> output-stream;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/cli/terminal.wit",
    "content": "/// Terminal input.\n///\n/// In the future, this may include functions for disabling echoing,\n/// disabling input buffering so that keyboard events are sent through\n/// immediately, querying supported features, and so on.\n@since(version = 0.2.0)\ninterface terminal-input {\n    /// The input side of a terminal.\n    @since(version = 0.2.0)\n    resource terminal-input;\n}\n\n/// Terminal output.\n///\n/// In the future, this may include functions for querying the terminal\n/// size, being notified of terminal size changes, querying supported\n/// features, and so on.\n@since(version = 0.2.0)\ninterface terminal-output {\n    /// The output side of a terminal.\n    @since(version = 0.2.0)\n    resource terminal-output;\n}\n\n/// An interface providing an optional `terminal-input` for stdin as a\n/// link-time authority.\n@since(version = 0.2.0)\ninterface terminal-stdin {\n    @since(version = 0.2.0)\n    use terminal-input.{terminal-input};\n\n    /// If stdin is connected to a terminal, return a `terminal-input` handle\n    /// allowing further interaction with it.\n    @since(version = 0.2.0)\n    get-terminal-stdin: func() -> option<terminal-input>;\n}\n\n/// An interface providing an optional `terminal-output` for stdout as a\n/// link-time authority.\n@since(version = 0.2.0)\ninterface terminal-stdout {\n    @since(version = 0.2.0)\n    use terminal-output.{terminal-output};\n\n    /// If stdout is connected to a terminal, return a `terminal-output` handle\n    /// allowing further interaction with it.\n    @since(version = 0.2.0)\n    get-terminal-stdout: func() -> option<terminal-output>;\n}\n\n/// An interface providing an optional `terminal-output` for stderr as a\n/// link-time authority.\n@since(version = 0.2.0)\ninterface terminal-stderr {\n    @since(version = 0.2.0)\n    use terminal-output.{terminal-output};\n\n    /// If stderr is connected to a terminal, return a `terminal-output` handle\n    /// allowing further interaction with it.\n    @since(version = 0.2.0)\n    get-terminal-stderr: func() -> option<terminal-output>;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/clocks/monotonic-clock.wit",
    "content": "package wasi:clocks@0.2.6;\n/// WASI Monotonic Clock is a clock API intended to let users measure elapsed\n/// time.\n///\n/// It is intended to be portable at least between Unix-family platforms and\n/// Windows.\n///\n/// A monotonic clock is a clock which has an unspecified initial value, and\n/// successive reads of the clock will produce non-decreasing values.\n@since(version = 0.2.0)\ninterface monotonic-clock {\n    @since(version = 0.2.0)\n    use wasi:io/poll@0.2.6.{pollable};\n\n    /// An instant in time, in nanoseconds. An instant is relative to an\n    /// unspecified initial value, and can only be compared to instances from\n    /// the same monotonic-clock.\n    @since(version = 0.2.0)\n    type instant = u64;\n\n    /// A duration of time, in nanoseconds.\n    @since(version = 0.2.0)\n    type duration = u64;\n\n    /// Read the current value of the clock.\n    ///\n    /// The clock is monotonic, therefore calling this function repeatedly will\n    /// produce a sequence of non-decreasing values.\n    @since(version = 0.2.0)\n    now: func() -> instant;\n\n    /// Query the resolution of the clock. Returns the duration of time\n    /// corresponding to a clock tick.\n    @since(version = 0.2.0)\n    resolution: func() -> duration;\n\n    /// Create a `pollable` which will resolve once the specified instant\n    /// has occurred.\n    @since(version = 0.2.0)\n    subscribe-instant: func(\n        when: instant,\n    ) -> pollable;\n\n    /// Create a `pollable` that will resolve after the specified duration has\n    /// elapsed from the time this function is invoked.\n    @since(version = 0.2.0)\n    subscribe-duration: func(\n        when: duration,\n    ) -> pollable;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/clocks/timezone.wit",
    "content": "package wasi:clocks@0.2.6;\n\n@unstable(feature = clocks-timezone)\ninterface timezone {\n    @unstable(feature = clocks-timezone)\n    use wall-clock.{datetime};\n\n    /// Return information needed to display the given `datetime`. This includes\n    /// the UTC offset, the time zone name, and a flag indicating whether\n    /// daylight saving time is active.\n    ///\n    /// If the timezone cannot be determined for the given `datetime`, return a\n    /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight\n    /// saving time.\n    @unstable(feature = clocks-timezone)\n    display: func(when: datetime) -> timezone-display;\n\n    /// The same as `display`, but only return the UTC offset.\n    @unstable(feature = clocks-timezone)\n    utc-offset: func(when: datetime) -> s32;\n\n    /// Information useful for displaying the timezone of a specific `datetime`.\n    ///\n    /// This information may vary within a single `timezone` to reflect daylight\n    /// saving time adjustments.\n    @unstable(feature = clocks-timezone)\n    record timezone-display {\n        /// The number of seconds difference between UTC time and the local\n        /// time of the timezone.\n        ///\n        /// The returned value will always be less than 86400 which is the\n        /// number of seconds in a day (24*60*60).\n        ///\n        /// In implementations that do not expose an actual time zone, this\n        /// should return 0.\n        utc-offset: s32,\n\n        /// The abbreviated name of the timezone to display to a user. The name\n        /// `UTC` indicates Coordinated Universal Time. Otherwise, this should\n        /// reference local standards for the name of the time zone.\n        ///\n        /// In implementations that do not expose an actual time zone, this\n        /// should be the string `UTC`.\n        ///\n        /// In time zones that do not have an applicable name, a formatted\n        /// representation of the UTC offset may be returned, such as `-04:00`.\n        name: string,\n\n        /// Whether daylight saving time is active.\n        ///\n        /// In implementations that do not expose an actual time zone, this\n        /// should return false.\n        in-daylight-saving-time: bool,\n    }\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/clocks/wall-clock.wit",
    "content": "package wasi:clocks@0.2.6;\n/// WASI Wall Clock is a clock API intended to let users query the current\n/// time. The name \"wall\" makes an analogy to a \"clock on the wall\", which\n/// is not necessarily monotonic as it may be reset.\n///\n/// It is intended to be portable at least between Unix-family platforms and\n/// Windows.\n///\n/// A wall clock is a clock which measures the date and time according to\n/// some external reference.\n///\n/// External references may be reset, so this clock is not necessarily\n/// monotonic, making it unsuitable for measuring elapsed time.\n///\n/// It is intended for reporting the current date and time for humans.\n@since(version = 0.2.0)\ninterface wall-clock {\n    /// A time and date in seconds plus nanoseconds.\n    @since(version = 0.2.0)\n    record datetime {\n        seconds: u64,\n        nanoseconds: u32,\n    }\n\n    /// Read the current value of the clock.\n    ///\n    /// This clock is not monotonic, therefore calling this function repeatedly\n    /// will not necessarily produce a sequence of non-decreasing values.\n    ///\n    /// The returned timestamps represent the number of seconds since\n    /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch],\n    /// also known as [Unix Time].\n    ///\n    /// The nanoseconds field of the output is always less than 1000000000.\n    ///\n    /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16\n    /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time\n    @since(version = 0.2.0)\n    now: func() -> datetime;\n\n    /// Query the resolution of the clock.\n    ///\n    /// The nanoseconds field of the output is always less than 1000000000.\n    @since(version = 0.2.0)\n    resolution: func() -> datetime;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/clocks/world.wit",
    "content": "package wasi:clocks@0.2.6;\n\n@since(version = 0.2.0)\nworld imports {\n    @since(version = 0.2.0)\n    import monotonic-clock;\n    @since(version = 0.2.0)\n    import wall-clock;\n    @unstable(feature = clocks-timezone)\n    import timezone;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/fastly/compute.wit",
    "content": "/// This is a [Wit] file defining the APIs of the [Fastly Compute platform].\n///\n/// This file defines the `fastly:compute/service` world, which defines the\n/// set of interfaces available to, and expected of, Fastly Compute service\n/// applications.\n///\n/// [Wit]: https://component-model.bytecodealliance.org/design/wit.html\n/// [Fastly Compute platform]: https://www.fastly.com/documentation/guides/compute/\npackage fastly:compute@0.1.0;\n\n/// Types used by many interfaces in this package.\ninterface types {\n  /// A common error type used by many functions in this package.\n  ///\n  /// TODO: In the future this should be split up into more-specific error\n  /// enums so that it better documents which errors each function can actually\n  /// return and what they mean.\n  variant error {\n    /// Generic error value.\n    ///\n    /// This means that some unexpected error occurred.\n    generic-error,\n    /// Invalid argument.\n    invalid-argument,\n    /// Auxiliary error value.\n    ///\n    /// For `cache.get-body` and `cache.replace-get-body`, it means the cache implementation was\n    /// busy and not ready to retrieve the body data.\n    ///\n    /// For cache APIs that attempt to write to or update the body of a cache transaction, it means\n    /// that an error occurred while attempting the write or update.\n    ///\n    /// For other cache APIs, it indicates that the underlying cache entry or cache replace entry\n    /// is no longer available.\n    ///\n    /// For writing to a streaming HTTP body, indicates that the body has already been closed.\n    ///\n    /// For a dictionary lookup, indicates that the dictionary was not found.\n    auxiliary-error,\n    /// Buffer length error.\n    ///\n    /// Returned when a buffer is the wrong size.\n    /// Includes the buffer length that would allow the operation to succeed.\n    buffer-len(u64),\n    /// Unsupported operation error.\n    ///\n    /// This error is returned when some operation cannot be performed, because it is not supported.\n    unsupported,\n    /// Invalid HTTP error.\n    ///\n    /// This can be returned when a method, URI, header, or status is not valid. This can also\n    /// be returned if a message head is too large.\n    http-invalid,\n    /// HTTP user error.\n    ///\n    /// This is returned in cases where user code caused an HTTP error. For example, attempt to send\n    /// a 1xx response code, or a request with a non-absolute URI. This can also be caused by\n    /// an unexpected header: both `content-length` and `transfer-encoding`, for example.\n    http-user,\n    /// HTTP incomplete message error.\n    ///\n    /// This can be returned when a stream ended unexpectedly.\n    http-incomplete,\n    /// Cannot read.\n    ///\n    /// An error occurred while attempting to read the body of a cache transaction.\n    cannot-read,\n    /// Message head too large.\n    http-head-too-large,\n    /// Invalid HTTP status.\n    http-invalid-status,\n    /// Limit exceeded\n    ///\n    /// This is returned when an attempt to allocate a resource has exceeded the maximum number of\n    /// resources permitted. For example, creating too many response handles.\n    limit-exceeded,\n  }\n\n  /// An error returned by `open`-like functions.\n  enum open-error {\n    /// The given name of the entity to open was invalid.\n    invalid-syntax,\n    /// The given name is longer the maximum permitted length.\n    name-too-long,\n    /// The given name is a reserved name that may not be opened.\n    reserved,\n    /// No entity by the given name was found.\n    not-found,\n    /// Unsupported operation error.\n    ///\n    /// This error is returned when some operation cannot be performed, because it is not supported.\n    unsupported,\n    /// Limit exceeded\n    ///\n    /// This is returned when an attempt to allocate a resource has exceeded the maximum number of\n    /// resources permitted. For example, creating too many response handles.\n    limit-exceeded,\n    /// Generic error value.\n    ///\n    /// This means that some unexpected error occurred.\n    generic-error,\n  }\n\n  /// IPv4 addresses.\n  type ipv4-address = tuple<u8, u8, u8, u8>;\n\n  /// IPv6 addresses.\n  type ipv6-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>;\n\n  /// IPv4 or IPv6 addresses.\n  variant ip-address {\n    ipv4(ipv4-address),\n    ipv6(ipv6-address),\n  }\n}\n\n/// Types used by HTTP interfaces in this package.\ninterface http-types {\n  /// HTTP protocol versions.\n  enum http-version {\n    /// HTTP/0.9\n    http09,\n    /// HTTP/1.0\n    http10,\n    /// HTTP/1.1\n    http11,\n    /// HTTP/2.0\n    h2,\n    /// HTTP/3.0\n    h3\n  }\n\n  /// HTTP [content encoding] flags\n  ///\n  /// The names of these flags correspond to\n  /// [IANA HTTP Content Coding Registry] names.\n  ///\n  /// Not all of these content encoding flags are supported in all APIs that\n  /// use this `content-encodings` type. See the documentation for individual\n  /// functions for details.\n  ///\n  /// [content encoding]: https://www.rfc-editor.org/rfc/rfc9110.html#field.content-encoding\n  /// [IANA HTTP Content Coding Registry]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding\n  flags content-encodings {\n    /// [Gzip coding]\n    ///\n    /// [Gzip coding]: https://www.rfc-editor.org/rfc/rfc9110.html#gzip.coding\n    gzip,\n\n    /// [Brotli coding]\n    ///\n    /// [Brotli coding]: https://www.rfc-editor.org/rfc/rfc7932.html\n    br,\n\n    /// [Zstandard coding]\n    ///\n    /// [Zstandard coding]: https://www.rfc-editor.org/rfc/rfc8878.html\n    zstd,\n\n    /// [Dictionary-Compressed Brotli]\n    ///\n    /// [Dictionary-Compressed Brotli]: https://www.rfc-editor.org/rfc/rfc9842.html#name-dictionary-compressed-brotl\n    dcb,\n\n    /// [Dictionary-Compressed Zstandard]\n    ///\n    /// [Dictionary-Compressed Zstandard]: https://www.rfc-editor.org/rfc/rfc9842.html#name-dictionary-compressed-zstan\n    dcz,\n\n    /// [AES-GCM encryption with a 128-bit content encryption key]\n    ///\n    /// [AES-GCM encryption with a 128-bit content encryption key]: https://www.rfc-editor.org/rfc/rfc8188.html\n    aes128gcm,\n  }\n\n  /// Determines how the framing headers (`Content-Length`/`Transfer-Encoding`) are set for a\n  /// request or response.\n  enum framing-headers-mode {\n    /// Determine the framing headers automatically based on the message body, and discard any\n    /// framing headers already set in the message. This is the default behavior.\n    ///\n    /// In automatic mode, a `Content-Length` is used when the size of the body can be determined\n    /// before it is sent. Requests/responses sent in streaming mode, where headers are sent\n    /// immediately but the content of the body is streamed later, will receive a\n    /// `Transfer-Encoding: chunked` to accommodate the dynamic generation of the body.\n    automatic,\n\n    /// Use the exact framing headers set in the message, falling back to `automatic` if invalid.\n    ///\n    /// In “from headers” mode, any `Content-Length` or `Transfer-Encoding` headers will be honored.\n    /// You must ensure that those headers have correct values permitted by the\n    /// [HTTP/1.1 specification]. If the provided headers are not permitted by the spec, the headers\n    /// will revert to automatic mode and a log diagnostic will be issued about what was wrong. If a\n    /// `Content-Length` is permitted by the spec, but the value doesn't match the size of the\n    /// actual body, the body will either be truncated (if it is too long), or the connection will\n    /// be hung up early (if it is too short).\n    ///\n    /// [HTTP/1.1 specification]: https://www.rfc-editor.org/rfc/rfc7230#section-3.3.1\n    manually-from-headers\n  }\n\n  /// [Transport Layer Security] (TLS) version\n  ///\n  /// This corresponds to the `ProtocolVersion` type in the RFC, which gives\n  /// the following values the following meanings:\n  ///\n  /// | Value  | Meaning | Notes |\n  /// | ------ | ------- | ----- |\n  /// | 0x0300 | SSL 3.0 | Not supported in this API |\n  /// | 0x0301 | TLS 1.0 |       |\n  /// | 0x0302 | TLS 1.1 | Not explicitly defined in the RFC |\n  /// | 0x0303 | TLS 1.2 |       |\n  /// | 0x0304 | TLS 1.3 |       |\n  ///\n  /// [Transport Layer Security]: https://www.rfc-editor.org/rfc/rfc8446.html\n  type tls-version = u16;\n\n  /// HTTP [status codes].\n  ///\n  /// [status codes]: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml\n  type http-status = u16;\n}\n\n/// HTTP bodies.\ninterface http-body {\n  use types.{error};\n\n  /// An HTTP request or response body.\n  use async-io.{pollable as body};\n\n  /// Creates a new empty body that can be used for outgoing requests and responses.\n  new: func() -> result<body, error>;\n\n  /// Appends the contents of the body `src` to the body `dest`.\n  append: func(dest: borrow<body>, src: body) -> result<_, error>;\n\n  /// Reads from a body.\n  read: func(body: borrow<body>, chunk-size: u32) -> result<list<u8>, error>;\n\n  /// Writes to a body.\n  ///\n  /// This function may write fewer bytes than requested; on success, the number of\n  /// bytes actually written is returned.\n  write: func(body: borrow<body>, buf: list<u8>) -> result<u32, error>;\n\n  /// Prepends bytes to the front of a body.\n  ///\n  /// On success, this function always writes all the bytes of `buf`.\n  write-front: func(body: borrow<body>, buf: list<u8>) -> result<_, error>;\n\n  /// Frees a body.\n  ///\n  /// This releases resources associated with the body.\n  ///\n  /// For streaming bodies, this is a *successful* stream termination, which will signal\n  /// via framing that the body transfer is complete.\n  ///\n  /// If a handle is dropped without calling `close`, it's an *unsuccessful* stream\n  /// termination.\n  close: func(body: body) -> result<_, error>;\n\n  /// Returns a `u64` body length if the length of a body is known, or `none` otherwise.\n  ///\n  /// If the length is unknown, it is likely due to the body arising from an HTTP/1.1 message with\n  /// chunked encoding, an HTTP/2 or later message with no `content-length`, or being a streaming\n  /// body.\n  ///\n  /// Receiving a length from this function does not guarantee that the full number of\n  /// bytes can actually be read from the body. For example, when proxying a response from a\n  /// backend, this length may reflect the `content-length` promised in the response, but if the\n  /// backend connection is closed prematurely, fewer bytes may be delivered before this body\n  /// handle can no longer be read.\n  get-known-length: func(body: borrow<body>) -> option<u64>;\n\n  /// Adds a body trailing header with given value.\n  append-trailer: func(\n    body: borrow<body>,\n    name: string,\n    value: list<u8>,\n  ) -> result<_, error>;\n\n  /// Gets the names of the trailers associated with this body.\n  ///\n  /// The first `cursor` names are skipped. The remaining names are encoded successively with\n  /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining\n  /// names don't fit, the returned `option<u32>` is the index of the first name that didn't fit,\n  /// or `none` if all the remaining names fit. If `max-len` is too small to fit any name, an\n  /// `error.buffer-len` error is returned, providing a recommended buffer size.\n  get-trailer-names: func(\n    body: borrow<body>,\n    max-len: u64,\n    cursor: u32,\n  ) -> result<tuple<string, option<u32>>, trailer-error>;\n\n  /// Gets the value for the trailer with the given name, or `none` if the trailer is not present.\n  ///\n  /// If there are multiple values for this header, only one is returned, which may be\n  /// any of the values. See `get-trailer-values` if you need to get all of the values.\n  ///\n  /// This functions returns `ok(some(v))` if the trailer with the given name is present,\n  /// and `ok(none)` if no trailer with the given name is present. If `max-len` is too\n  /// small to fit the value, an `error.buffer-len` error is returned, providing a\n  /// recommended buffer size.\n  get-trailer-value: func(\n    body: borrow<body>,\n    name: string,\n    max-len: u64,\n  ) -> result<option<list<u8>>, trailer-error>;\n\n  /// Gets multiple values associated with the trailer with the given name.\n  ///\n  /// As opposed to `get-trailer-value`, this function returns all of the values for this trailer.\n  ///\n  /// The first `cursor` values are skipped. The remaining values are encoded successively with\n  /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining\n  /// values don't fit, the returned `option<u32>` is the index of the first value that didn't\n  /// fit, or `none` if all the remaining values fit. If `max-len` is too small to fit any value,\n  /// an `error.buffer-len` error is returned, providing a recommended buffer size.\n  get-trailer-values: func(\n    body: borrow<body>,\n    name: string,\n    max-len: u64,\n    cursor: u32\n  ) -> result<tuple<list<u8>, option<u32>>, trailer-error>;\n\n  /// Trailers aren't available until the body has been completely transmitted, so this error\n  /// type can either indicate that the errors aren't available yet, or that an error occurred.\n  variant trailer-error {\n    /// The trailers aren't available yet.\n    not-available-yet,\n\n    /// An error occurred.\n    error(error),\n  }\n}\n\n/// Low-level interface to Fastly's [Real-Time Log Streaming] endpoints.\n///\n/// [Real-Time Log Streaming]: https://docs.fastly.com/en/guides/about-fastlys-realtime-log-streaming-features\ninterface log {\n  use types.{error, open-error};\n\n  /// A logging endpoint.\n  resource endpoint {\n    /// Tries to get an endpoint by name.\n    ///\n    /// Currently, the conditions on an endpoint name are:\n    /// - It must not be empty.\n    /// - It must not contain newlines (`\\n`) or colons (`:`).\n    /// - It must not be `stdout` or `stderr`, which are reserved for debugging.\n    ///\n    /// Names are case sensitive. Calling `get-endpoint` with a name that doesn't correspond to any\n    /// logging endpoint available in your service will still return a usable endpoint, and writes\n    /// to that endpoint will succeed. Refer to your service dashboard to diagnose missing log\n    /// events.\n    open: static func(name: string) -> result<endpoint, open-error>;\n\n    /// Writes a data to the given endpoint.\n    ///\n    /// Each call to `write` with a non-empty message produces a single log event.\n    write: func(msg: list<u8>);\n  }\n}\n\n/// HTTP downstream requests and metadata.\n///\n/// “Downstream” here refers to incoming HTTP requests.\ninterface http-downstream {\n  use types.{error, ip-address};\n  use http-req.{\n    request, client-cert-verify-result, error-with-detail, cache-override, pending-request,\n    request-with-body,\n  };\n\n  /// Configuration for `next-request`.\n  record next-request-options {\n    timeout-ms: option<u64>,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-next-request-options>>,\n  }\n\n  /// Extensibility for `next-request-options`\n  resource extra-next-request-options {}\n\n  /// Categories of detected bots.\n  variant bot-category {\n    /// No bot was detected.\n    none,\n    /// A suspected bot.\n    suspected,\n    /// Tools that make content accessible (e.g., screen readers).\n    accessibility,\n    /// Crawlers used for training AIs and LLMs, generally used for building AI models or indexes.\n    ai-crawler,\n    /// Fetchers used by AIs and LLMs for enriching results in response to a user query.\n    ai-fetcher,\n    /// Tools that extract content from websites to be used elsewhere.\n    content-fetcher,\n    /// Tools that access your website to monitor things like performance, uptime, and proving domain control.\n    monitoring-site-tools,\n    /// Crawlers from online marketing platforms (e.g., Facebook, Pinterest).\n    online-marketing,\n    /// Tools that access your website to show a preview of the page in other online services and social media platforms.\n    page-preview,\n    /// Integration with other platforms by accessing the website's API, notably Webhooks.\n    platform-integrations,\n    /// Commercial and academic tools that collect and analyze data for research purposes.\n    research,\n    /// Crawlers that index your website for search engines.\n    search-engine-crawler,\n    /// Tools that support search engine optimization tasks (e.g., link analysis, ranking).\n    search-engine-optimization,\n    /// Security analysis tools that inspect your website for vulnerabilities, misconfigurations and other security features.\n    security-tools,\n    /// Additional bot kinds may be added in the future via this resource type.\n    extra(extra-bot-category),\n  }\n\n  /// Extensibility for `bot-category`.\n  resource extra-bot-category {\n    /// Returns an opaque value representing a bot category added in a future interface version.\n    as-raw: func() -> u32;\n  }\n\n  /// Prepares to accept a new downstream request.\n  ///\n  /// By default, each sandbox accepts a single downstream request,\n  /// passed in as an argument to the `http-incoming.handle` call. The\n  /// `next-request` function enables a sandbox to accept additional\n  /// downstream requests. `next-request` returns a `pending-request`, which\n  /// can be passed to `await-request` to wait for the request to become\n  /// available and return the `request`.\n  ///\n  /// When using this function, be mindful of two considerations:\n  ///\n  ///  - When a single sandbox accepts multiple requests, its state\n  ///    isn't automatically cleared between requests. Applications are\n  ///    therefore responsible for preventing sensitive data from leaking from\n  ///    one request to another.\n  ///\n  ///  - There is no guarantee that a single sandbox will receive all\n  ///    requests from a given client or from a given location. When it is\n  ///    necessary to preserve state between multiple requests, store it outside\n  ///    of the sandbox.\n  next-request: func(\n    options: next-request-options,\n  ) -> result<pending-request, error>;\n\n  /// Waits until the next request is available, and then returns the resulting\n  /// request and body.\n  ///\n  /// Returns `ok(none)` if there are no more requests for this sandbox.\n  await-request: func(\n    pending: pending-request,\n  ) -> result<option<request-with-body>, error>;\n\n  /// Returns the client request's header names exactly as they were originally received.\n  ///\n  /// This includes both the original header name characters' cases, as well as the original order\n  /// of the received headers.\n  ///\n  /// The first `cursor` names are skipped. The remaining names are encoded successively with\n  /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining\n  /// names don't fit, the returned `option<u32>` is the index of the first name that didn't fit,\n  /// or `none` if all the remaining names fit. If `max-len` is too small to fit any name,\n  /// an `error.buffer-len` error is returned, providing a recommended buffer size.\n  downstream-original-header-names: func(\n    ds-request: borrow<request>,\n    max-len: u64,\n    cursor: u32,\n  ) -> result<tuple<string, option<u32>>, error>;\n\n  /// Returns the number of headers in the client request as originally received.\n  downstream-original-header-count: func(\n    ds-request: borrow<request>\n  ) -> result<u32, error>;\n\n  /// Returns the IP address of the client making the HTTP request, if known.\n  downstream-client-ip-addr: func(\n    ds-request: borrow<request>\n  ) -> option<ip-address>;\n\n  /// Returns the IP address on which this server received the HTTP request, if known.\n  downstream-server-ip-addr: func(\n    ds-request: borrow<request>\n  ) -> option<ip-address>;\n\n  /// Gets the HTTP/2 fingerprint of client request if available.\n  downstream-client-h2-fingerprint: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<string, error>;\n\n  /// Gets the id of the current request if available.\n  downstream-client-request-id: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<string, error>;\n\n  /// Gets the fingerprint of client request headers if available.\n  downstream-client-oh-fingerprint: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<string, error>;\n\n  /// Returns whether the request was tagged as contributing to a DDoS attack.\n  downstream-client-ddos-detected: func(\n    ds-request: borrow<request>\n  ) -> result<bool, error>;\n\n  /// Gets the cipher suite used to secure the downstream client TLS connection.\n  ///\n  /// The value returned will be consistent with the [OpenSSL name] for the cipher suite.\n  ///\n  /// Returns `ok(none)` if the downstream client connection is not a TLS connection.\n  ///\n  /// [OpenSSL name]: https://testssl.sh/openssl-iana.mapping.html\n  downstream-tls-cipher-openssl-name: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<list<u8>>, error>;\n\n  /// Gets the TLS protocol version used to secure the downstream client TLS connection.\n  ///\n  /// Returns `ok(none)` if the downstream client connection is not a TLS connection.\n  downstream-tls-protocol: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<list<u8>>, error>;\n\n  /// Gets the raw bytes sent by the client in the TLS ClientHello message.\n  ///\n  /// See [RFC 5246] for details.\n  ///\n  /// Returns `ok(none)` if the downstream client connection is not a TLS connection.\n  ///\n  /// [RFC 5246]: https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2\n  downstream-tls-client-hello: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<list<u8>>, error>;\n\n  /// Gets the raw client certificate used to secure the downstream client mTLS connection.\n  ///\n  /// The value returned will be based on PEM format.\n  ///\n  /// Returns `ok(none)` if the downstream client connection is not a TLS connection.\n  downstream-tls-raw-client-certificate: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<list<u8>>, error>;\n\n  /// Returns the `client-cert-verify-result` from the downstream client mTLS handshake.\n  ///\n  /// Returns `ok(none)` if the downstream client connection is not a TLS connection.\n  downstream-tls-client-cert-verify-result: func(\n    ds-request: borrow<request>\n  ) -> result<option<client-cert-verify-result>, error>;\n\n  /// Returns the Server Name Indication from the downstream client TLS handshake.\n  ///\n  /// Returns `ok(none)` if not available.\n  downstream-tls-client-servername: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<string>, error>;\n\n  /// Gets the JA3 hash of the TLS ClientHello message.\n  ///\n  /// Returns `ok(none)` if the downstream client connection is not a TLS connection.\n  downstream-tls-ja3-md5: func(\n    ds-request: borrow<request>\n  ) -> result<option<list<u8>>, error>;\n\n  /// Gets the JA4 hash of the TLS ClientHello message.\n  ///\n  /// Returns `ok(none)` if the downstream client connection is not a TLS connection.\n  downstream-tls-ja4: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<string>, error>;\n\n  /// Gets the compliance region that the client IP address is in.\n  downstream-compliance-region: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<string>, error>;\n\n  /// Returns whether or not the original client request arrived with a\n  /// Fastly-Key belonging to a user with the rights to purge content on this\n  /// service.\n  fastly-key-is-valid: func(\n    ds-request: borrow<request>,\n  ) -> result<bool, error>;\n\n  /// Whether bot detection analysis was performed for this request.\n  downstream-bot-analyzed: func(\n    ds-request: borrow<request>,\n  ) -> result<bool, error>;\n\n  /// Whether a bot was detected in the request.\n  downstream-bot-detected: func(\n    ds-request: borrow<request>,\n  ) -> result<bool, error>;\n\n  /// A string identifying the specific bot detected (e.g., `GoogleBot`, `GPTBot`, `Bingbot`).\n  /// Returns `ok(none)` if bot detection was not executed or no bot was detected.\n  ///\n  /// **Warning:** String values may change over time. Use this for logging or informational purposes.\n  /// For conditional logic, use the `downstream-bot-category-kind` value.\n  downstream-bot-name: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<string>, error>;\n\n  /// A string indicating the type of bot detected (e.g., `SEARCH-ENGINE-CRAWLER`, `AI-CRAWLER`, `SUSPECTED-BOT`).\n  /// Returns `ok(none)` if bot detection was not executed.\n  ///\n  /// **Warning:** String values may change over time. Use this for logging or informational purposes.\n  /// For conditional logic, use the `downstream-bot-category-kind` value.\n  downstream-bot-category: func(\n    ds-request: borrow<request>,\n    max-len: u64\n  ) -> result<option<string>, error>;\n\n  /// A value uniquely identifying the type of bot detected.\n  /// Returns `ok(none)` if bot detection was not executed.\n  downstream-bot-category-kind: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bot-category>, error>;\n\n  /// Whether the detected bot is a verified bot.\n  /// Returns `ok(none)` if bot detection was not executed or no bot was detected.\n  downstream-bot-verified: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range belongs to any kind of anonymous network.\n  downstream-resvpnproxy-is-anonymous: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range is identified as a VPN system.\n  downstream-resvpnproxy-is-anonymous-vpn: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range is identified as a hosting provider.\n  downstream-resvpnproxy-is-hosting-provider: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range is detected with a proxy over VPN technique.\n  downstream-resvpnproxy-is-proxy-over-vpn: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range is identified as a public proxy.\n  downstream-resvpnproxy-is-public-proxy: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP belongs to relay service provider.\n  downstream-resvpnproxy-is-relay-proxy: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range is detected with residential proxy over VPN technique.\n  downstream-resvpnproxy-is-residential-proxy: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range is identified as a Smart DNS Proxy.\n  downstream-resvpnproxy-is-smart-dns-proxy: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range is identified as a Tor exit node.\n  downstream-resvpnproxy-is-tor-exit-node: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// True if the IP range is identified as a VPN datacenter.\n  downstream-resvpnproxy-is-vpn-datacenter: func(\n    ds-request: borrow<request>,\n  ) -> result<option<bool>, error>;\n\n  /// VPN service name. Available only if the range is identified as a VPN system.\n  downstream-resvpnproxy-vpn-service-name: func(\n    ds-request: borrow<request>,\n    max-len: u64,\n  ) -> result<option<string>, error>;\n}\n\n/// HTTP requests.\ninterface http-req {\n  use types.{error, ip-address};\n  use http-types.{http-version, content-encodings, framing-headers-mode};\n  use http-resp.{response};\n  use http-body.{body};\n  use http-resp.{response-with-body};\n  use backend.{backend};\n\n  /// Handle that can be used to wait for a response from a sent request.\n  use async-io.{pollable as pending-response};\n\n  /// Handle that can be used to wait for incoming requests.\n  use async-io.{pollable as pending-request};\n\n  /// An HTTP request.\n  resource request {\n    /// Creates a new `request` with no method, URL, or headers, and an empty body.\n    new: static func() -> result<request, error>;\n\n    /// Sets the cache override behavior for this request.\n    ///\n    /// This setting will override any cache directive headers returned in response to this request.\n    set-cache-override: func(\n      cache-override: cache-override,\n    ) -> result<_, error>;\n\n    /// Reads the request's header names via a buffer of the provided size.\n    ///\n    /// The first `cursor` names are skipped. The remaining names are encoded successively with\n    /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining\n    /// names don't fit, the returned `option<u32>` is the index of the first name that didn't fit,\n    /// or `none` if all the remaining names fit. If `max-len` is too small to fit any name,\n    /// an `error.buffer-len` error is returned, providing a recommended buffer size.\n    get-header-names: func(\n      max-len: u64,\n      cursor: u32,\n    ) -> result<tuple<string, option<u32>>, error>;\n\n    /// Gets the value of a header, or `none` if the header is not present.\n    ///\n    /// If there are multiple values for the header, only one is returned. See\n    /// `get-header-values` if you need to get all of the values.\n    ///\n    /// If header name requires more than `max-len` bytes, this will return an `error.buffer-len`\n    /// containing the required size.\n    get-header-value: func(\n      name: string,\n      max-len: u64,\n    ) -> result<option<list<u8>>, error>;\n\n    /// Gets multiple header values for the given `name` via a buffer of the provided size.\n    ///\n    /// As opposed to `get-header-value`, this function returns all of the values for this header.\n    ///\n    /// The first `cursor` values are skipped. The remaining values are encoded successively with\n    /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining\n    /// values don't fit, the returned `option<u32>` is the index of the first value that didn't\n    /// fit, or `none` if all the remaining values fit. If `max-len` is too small to fit any value,\n    /// an `error.buffer-len` error is returned, providing a recommended buffer size.\n    get-header-values: func(\n      name: string,\n      max-len: u64,\n      cursor: u32\n    ) -> result<tuple<list<u8>, option<u32>>, error>;\n\n    /// Sets the values for the given header name, replacing any headers that previously existed for\n    /// that name.\n    set-header-values: func(\n      name: string,\n      /// contains multiple values each terminated by `\\0` and concatenated\n      values: list<u8>\n    ) -> result<_, error>;\n\n    /// Sets a request header to the given value, discarding any previous values for the given\n    /// header name.\n    insert-header: func(name: string, value: list<u8>) -> result<_, error>;\n\n    /// Adds a request header with given value.\n    ///\n    /// Unlike `set-header-values`, this does not discard existing values for the same header name.\n    append-header: func(\n      name: string,\n      value: list<u8>,\n    ) -> result<_, error>;\n\n    /// Removes all request headers of the given name\n    ///\n    /// Returns `ok` if any headers were successfully removed.\n    remove-header: func(name: string) -> result<_, error>;\n\n    /// Gets the request method.\n    get-method: func(max-len: u64) -> result<string, error>;\n\n    /// Sets the request method.\n    set-method: func(method: string) -> result<_, error>;\n\n    /// Gets the request URI.\n    get-uri: func(max-len: u64) -> result<string, error>;\n\n    /// Sets the request URI.\n    set-uri: func(uri: string) -> result<_, error>;\n\n    /// Gets the HTTP version of this request.\n    get-version: func() -> result<http-version, error>;\n\n    /// Sets the HTTP version of this request.\n    set-version: func(version: http-version) -> result<_, error>;\n\n    /// Sets the content encodings to automatically decompress responses to this request.\n    ///\n    /// If the response to this request is encoded by one of the encodings set by this method, the\n    /// response will be presented to the Compute program in decompressed form with the\n    /// `Content-Encoding` and `Content-Length` headers removed.\n    ///\n    /// Not all of the flags defined in `content-encodings` are supported. Currently the only\n    /// supported flag is `content-encodings.gzip`.\n    set-auto-decompress-response: func(\n      encodings: content-encodings,\n    ) -> result<_, error>;\n\n    /// Passes the WebSocket directly to a backend.\n    ///\n    /// This can only be used on services that have the WebSockets feature enabled and on requests\n    /// that are valid WebSocket requests.\n    ///\n    /// The sending completes in the background. Once this method has been called, no other response\n    /// can be sent to this request, and the application can exit without affecting the send.\n    ///\n    /// See the [WebSockets passthrough] documentation for a high-level description of this feature.\n    ///\n    /// [WebSockets passthrough]: https://www.fastly.com/documentation/guides/concepts/real-time-messaging/websockets-tunnel/\n    redirect-to-websocket-proxy: func(\n      backend: borrow<backend>,\n    ) -> result<_, error>;\n\n    /// Sets how the framing headers `Content-Length` and `Transfer-Encoding` will be determined\n    /// when sending this request.\n    set-framing-headers-mode: func(\n      mode: framing-headers-mode,\n    ) -> result<_, error>;\n\n    redirect-to-grip-proxy: func(\n      backend: borrow<backend>,\n    ) -> result<_, error>;\n  }\n\n  /// Retrieves a response for the request, either from cache or by sending it\n  /// to the given backend server.\n  ///\n  /// Returns once the response headers have been received, or an error occurs.\n  send: func(\n    request: request,\n    body: body,\n    backend: borrow<backend>,\n  ) -> result<response-with-body, error-with-detail>;\n\n  /// Sends the request directly to the backend server without performing any\n  /// caching or inserting any cache-related headers in the response.\n  ///\n  /// Returns once the response headers have been received, or an error occurs.\n  send-uncached: func(\n    request: request,\n    body: body,\n    backend: borrow<backend>,\n  ) -> result<response-with-body, error-with-detail>;\n\n  /// Begins sending the request to the given backend server, and returns a\n  /// `pending-response` that can yield the backend response or an error.\n  ///\n  /// This method returns as soon as the request begins sending to the backend,\n  /// and transmission of the request body and headers will continue in the\n  /// background.\n  ///\n  /// This method allows for sending more than one request at once and receiving\n  /// their responses in arbitrary orders. See `pending-response` for more\n  /// details on how to wait on, poll, or select between pending responses.\n  ///\n  /// This method is also useful for sending requests where the response is\n  /// unimportant, but the request may take longer than the Compute program is\n  /// able to run, as the request will continue sending even after the program\n  /// that initiated it exits.\n  send-async: func(\n    request: request,\n    body: body,\n    backend: borrow<backend>\n  ) -> result<pending-response, error>;\n\n  /// This is to `send-async` as `send-uncached` is to `send`.\n  ///\n  /// As with `send-uncached`, this function sends the request directly to the\n  /// backend server without performing any caching or inserting any\n  /// cache-related headers in the response.\n  send-async-uncached: func(\n    request: request,\n    body: body,\n    backend: borrow<backend>,\n  ) -> result<pending-response, error>;\n\n  /// Begins sending the request to the given backend server, and returns a\n  /// `pending-response` that can yield the backend response or an error.\n  ///\n  /// The `body` argument is not consumed, so that it can accept further data to send.\n  ///\n  /// The backend connection is only closed once `http-body.close` is called. The\n  /// `pending-response` will not yield a `response` until the body is finished.\n  ///\n  /// This method is most useful for programs that do some sort of processing or\n  /// inspection of a potentially-large client request body. Streaming allows the\n  /// program to operate on small parts of the body rather than having to read it all\n  /// into memory at once.\n  ///\n  /// This method returns as soon as the request begins sending to the backend,\n  /// and transmission of the request body and headers will continue in the\n  /// background.\n  send-async-streaming: func(\n    request: request,\n    body: borrow<body>,\n    backend: borrow<backend>,\n  ) -> result<pending-response, error>;\n\n  /// This is to `send-async-streaming` as `send-uncached` is to `send`.\n  ///\n  /// As with `send-uncached`, this function sends the request directly to the\n  /// backend server without performing any caching or inserting any\n  /// cache-related headers in the response.\n  send-async-uncached-streaming: func(\n    request: request,\n    body: borrow<body>,\n    backend: borrow<backend>,\n  ) -> result<pending-response, error>;\n\n  type request-with-body = tuple<request, body>;\n\n  /// Optional override for response caching behavior.\n  variant cache-override {\n    /// Do not override the behavior specified in the origin response’s cache control headers.\n    none,\n\n    /// Do not cache the response to this request, regardless of the origin response’s headers.\n    pass,\n\n    /// Override particular cache control settings.\n    override(cache-override-details)\n  }\n\n  /// The fields for the `override` arm of `cache-override`.\n  ///\n  /// The origin response’s cache control headers will be used for ttl and\n  /// `stale-while-revalidate` if `none`.\n  record cache-override-details {\n    ttl: option<u32>,\n    stale-while-revalidate: option<u32>,\n    pci: bool,\n    surrogate-key: option<string>,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-cache-override-details>>,\n  }\n\n  /// Extensibility for `cache-override-details`\n  resource extra-cache-override-details {}\n\n  /// TLS client certificate verified result from downstream.\n  enum client-cert-verify-result {\n    /// Success value.\n    ///\n    /// This indicates that client certificate verified successfully.\n    ok,\n    /// bad certificate error.\n    ///\n    /// This error means the certificate is corrupt\n    /// (for example, when the certificate signatures do not verify correctly).\n    bad-certificate,\n    /// certificate revoked error.\n    ///\n    /// This error means the client certificate is revoked by its signer.\n    certificate-revoked,\n    /// certificate expired error.\n    ///\n    /// This error means the client certificate has expired or is not currently valid.\n    certificate-expired,\n    /// unknown CA error.\n    ///\n    /// This error means the valid certificate chain or partial chain was received,\n    /// but the certificate was not accepted because the CA certificate could not be\n    /// located or could not be matched with a known trust anchor.\n    unknown-ca,\n    /// certificate missing error.\n    ///\n    /// This error means the client does not provide a certificate\n    /// during the handshake.\n    certificate-missing,\n    /// certificate unknown error.\n    ///\n    /// This error means the client certificate was received, but some other (unspecified)\n    /// issue arose in processing the certificate, rendering it unacceptable.\n    certificate-unknown,\n  }\n\n  /// Information about errors encountered by sent requests.\n  variant send-error-detail {\n      /// The system encountered a timeout when trying to find an IP address for the backend\n      /// hostname.\n      dns-timeout,\n      /// The system encountered a DNS error when trying to find an IP address for the backend\n      /// hostname.\n      dns-error(dns-error-detail),\n      /// The system cannot determine which backend to use, or the specified backend was invalid.\n      destination-not-found,\n      /// The system considers the backend to be unavailable, for example when recent attempts to\n      /// communicate with it may have failed, or a health check may indicate that it is down.\n      destination-unavailable,\n      /// The system cannot find a route to the next-hop IP address.\n      destination-ip-unroutable,\n      /// The system's connection to the backend was refused.\n      connection-refused,\n      /// The system's connection to the backend was closed before a complete response was\n      /// received.\n      connection-terminated,\n      /// The system's attempt to open a connection to the backend timed out.\n      connection-timeout,\n      /// The system is configured to limit the number of connections it has to the backend, and\n      /// that limit has been exceeded.\n      connection-limit-reached,\n      /// The system encountered an error when verifying the certificate presented by the backend.\n      tls-certificate-error,\n      /// The system encountered an error with the backend TLS configuration.\n      tls-configuration-error,\n      /// The system received an incomplete response to the request from the backend.\n      http-incomplete-response,\n      /// The system received a response to the request whose header section was considered too\n      /// large.\n      http-response-header-section-too-large,\n      /// The system received a response to the request whose body was considered too large.\n      http-response-body-too-large,\n      /// The system reached a configured time limit waiting for the complete response.\n      http-response-timeout,\n      /// The system received a response to the request whose status code or reason phrase was\n      /// invalid.\n      http-response-status-invalid,\n      /// The process of negotiating an upgrade of the HTTP version between the system and the\n      /// backend failed.\n      http-upgrade-failed,\n      /// The system encountered an HTTP protocol error when communicating with the backend.\n      ///\n      /// This error will only be used when a more specific one is not defined.\n      http-protocol-error,\n      /// An invalid cache key was provided for the request.\n      http-request-cache-key-invalid,\n      /// An invalid URI was provided for the request.\n      http-request-uri-invalid,\n      /// The system encountered an unexpected internal error.\n      internal-error,\n      /// The system received a TLS alert from the backend.\n      tls-alert-received(tls-alert-received-detail),\n      /// The system encountered a TLS error when communicating with the backend, either during\n      /// the handshake or afterwards.\n      tls-protocol-error,\n      /// The system received an HTTP/2 error frame and code from the backend.\n      h2-error(h2-error-detail),\n      /// Additional error details may be added in the future via this resource type.\n      extra(extra-send-error-detail),\n  }\n\n  /// Variant fields for `send-error.dns-error`.\n  record dns-error-detail {\n      rcode: option<u16>,\n      info-code: option<u16>,\n  }\n\n  /// Variant fields for `send-error.tls-alert-received`.\n  record tls-alert-received-detail {\n      id: option<u8>,\n  }\n\n  /// Variant fields for `send-error.h2-error`.\n  record h2-error-detail {\n      frame-type: u8,\n      error-code: u32,\n  }\n\n  /// Extensibility for `send-error-detail`\n  resource extra-send-error-detail {}\n\n  /// An `error` code, optionally with extra request error information.\n  record error-with-detail {\n      detail: option<send-error-detail>,\n      error: error,\n  }\n\n  /// Waits until the request is completed, and then returns the resulting\n  /// response and body.\n  await-response: func(\n    pending: pending-response\n  ) -> result<response-with-body, error-with-detail>;\n\n  /// Closes the `request`, releasing any associated resources.\n  ///\n  /// A `request` is automatically consumed when you send a request. You should call `close`\n  /// only if you have a `request` you don't intend to use anymore.\n  close: func(request: request) -> result<_, error>;\n\n  upgrade-websocket: func(backend: borrow<backend>) -> result<_, error>;\n}\n\n/// [Fastly Next-Gen WAF] API.\n///\n/// [Fastly Next-Gen WAF]: https://docs.fastly.com/en/ngwaf/\ninterface security {\n  use http-req.{request, body, ip-address, error};\n\n  /// Inspects request HTTP traffic using the [NGWAF] lookaside service.\n  ///\n  /// Returns a JSON-encoded string.\n  ///\n  /// [NGWAF]: https://docs.fastly.com/en/ngwaf/\n  inspect: func(\n    request: borrow<request>,\n    body: borrow<body>,\n    options: inspect-options,\n    max-len: u64\n  ) -> result<string, error>;\n\n  /// Configuration for inspecting a `request` using Security.\n  record inspect-options {\n    corp: option<string>,\n    workspace: option<string>,\n    override-client-ip: option<ip-address>,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-inspect-options>>,\n  }\n\n  /// Extensibility for `inspect-options`\n  resource extra-inspect-options {}\n}\n\n/// HTTP responses.\ninterface http-resp {\n  use types.{error, ip-address};\n\n  use http-types.{\n    http-version, http-status,\n    framing-headers-mode\n  };\n  use http-body.{body};\n\n  /// An HTTP response.\n  resource response {\n    /// Create a new `response`.\n    ///\n    /// The new `response` is created with status code 200 OK, no headers, and an empty body.\n    new: static func() -> result<response, error>;\n\n    /// Read the response's header names via a buffer of the provided size.\n    ///\n    /// The first `cursor` names are skipped. The remaining names are encoded successively with\n    /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining\n    /// names don't fit, the returned `option<u32>` is the index of the first name that didn't fit,\n    /// or `none` if all the remaining names fit. If `max-len` is too small to fit any name,\n    /// an `error.buffer-len` error is returned, providing a recommended buffer size.\n    get-header-names: func(\n      max-len: u64,\n      cursor: u32,\n    ) -> result<tuple<string, option<u32>>, error>;\n\n    /// Gets the value of a header, or `none` if the header is not present.\n    ///\n    /// If there are multiple values for the header, only one is returned. See\n    /// `get-header-values` if you need to get all of the values.\n    ///\n    /// If header name requires more than `max-len` bytes, this will return an `error.buffer-len`\n    /// containing the required size.\n    get-header-value: func(\n      name: string,\n      max-len: u64,\n    ) -> result<option<list<u8>>, error>;\n\n    /// Gets multiple header values for the given `name` via a buffer of the provided size.\n    ///\n    /// As opposed to `get-header-value`, this function returns all of the values for this header.\n    ///\n    /// The first `cursor` values are skipped. The remaining values are encoded successively with\n    /// a NUL byte after each into a list of bytes at most `max-len` long. If any of the remaining\n    /// values don't fit, the returned `option<u32>` is the index of the first value that didn't\n    /// fit, or `none` if all the remaining values fit. If `max-len` is too small to fit any value,\n    /// an `error.buffer-len` error is returned, providing a recommended buffer size.\n    get-header-values: func(\n      name: string,\n      max-len: u64,\n      cursor: u32\n    ) -> result<tuple<list<u8>, option<u32>>, error>;\n\n    /// Sets the values for the given header name, replacing any headers that previously existed for\n    /// that name.\n    set-header-values: func(\n      name: string,\n      /// contains multiple values each terminated by `\\0` and concatenated\n      values: list<u8>\n    ) -> result<_, error>;\n\n    /// Sets a response header to the given value, discarding any previous values for the given\n    /// header name.\n    insert-header: func(\n      name: string,\n      value: list<u8>,\n    ) -> result<_, error>;\n\n    /// Add a response header with given value.\n    ///\n    /// Unlike `set-header-values`, this does not discard existing values for the same header name.\n    append-header: func(\n      name: string,\n      value: list<u8>,\n    ) -> result<_, error>;\n\n    /// Remove all response headers of the given name\n    ///\n    /// Returns `ok` if any headers were successfully removed.\n    remove-header: func(name: string) -> result<_, error>;\n\n    /// Gets the HTTP version of this response.\n    get-version: func() -> result<http-version, error>;\n\n    /// Sets the HTTP version of this response.\n    set-version: func(version: http-version) -> result<_, error>;\n\n    /// Gets the HTTP status code of the response.\n    get-status: func() -> result<http-status, error>;\n\n    /// Sets the HTTP status code of the response.\n    set-status: func(status: http-status) -> result<_, error>;\n\n    /// Sets how the framing headers `Content-Length` and `Transfer-Encoding` will be determined\n    /// when sending this response.\n    set-framing-headers-mode: func(mode: framing-headers-mode) -> result<_, error>;\n\n    /// Adjust the response's connection reuse mode.\n    set-http-keepalive-mode: func(mode: keepalive-mode) -> result<_, error>;\n\n    /// Gets the destination IP address used for this response, if known.\n    get-remote-ip-addr: func() -> option<ip-address>;\n\n    /// Gets the destination port used for this response, if known.\n    get-remote-port: func() -> option<u16>;\n  }\n\n  /// Sends a response to the client that made the request passed to `http-incoming.handle`.\n  ///\n  /// This method returns as soon as the response header begins sending to the client, and\n  /// transmission of the response will continue in the background.\n  ///\n  /// Data for the body must be written before calling this function. To start a response\n  /// and write data to it afterwards, use `send-downstream-streaming` instead.\n  send-downstream: func(\n    response: response,\n    body: body,\n  ) -> result<_, error>;\n\n  /// Starts a response to the client that made the request passed to `http-incoming.handle`.\n  ///\n  /// The body is left open, allowing data to be written after calling this function.\n  send-downstream-streaming: func(\n    response: response,\n    body: borrow<body>,\n  ) -> result<_, error>;\n\n  /// Closes the `response`, releasing any associated resources.\n  ///\n  /// A `response` is consumed when you send a response to a client or stream one to a\n  /// client. You should call `close` only if you have a `response` you don't intend\n  /// to use anymore.\n  close: func(response: response) -> result<_, error>;\n\n  type response-with-body = tuple<response, body>;\n\n  enum keepalive-mode {\n    automatic,\n    no-keepalive,\n  }\n}\n\n/// [Compute Dictionaries] (deprecated in favor of `config-store`)\n///\n/// [Compute Dictionaries]: https://www.fastly.com/documentation/guides/concepts/edge-state/dynamic-config/#dictionaries\ninterface dictionary {\n  use types.{error, open-error};\n\n  /// A Compute Dictionary.\n  resource dictionary {\n    /// Opens a dictionary, given its name.\n    ///\n    /// Names are case sensitive.\n    open: static func(name: string) -> result<dictionary, open-error>;\n\n    /// Tries to look up a value in this dictionary.\n    ///\n    /// If the lookup is successful, this function returns `ok(some(s))` containing the found\n    /// string `s`, or `ok(none)` if no entry with the given key was found.\n    lookup: func(\n      key: string,\n      max-len: u64,\n    ) -> result<option<string>, error>;\n  }\n}\n\n/// [Geographic data] for IP addresses.\n///\n/// [Geographic data]: https://www.fastly.com/blog/improve-performance-and-gain-better-end-user-intelligence-geoip-geography-detection\ninterface geo {\n  use types.{error, ip-address};\n\n  /// Looks up the geographic data associated with a particular IP address.\n  ///\n  /// Returns a list of bytes containing JSON-encoded geographic data. See [here] for descriptions\n  /// of the JSON fields.\n  ///\n  /// [here]: https://www.fastly.com/documentation/reference/vcl/variables/geolocation/\n  lookup: func(ip-addr: ip-address, max-len: u64) -> result<string, error>;\n}\n\n/// Device detection based on the User-Agent header.\ninterface device-detection {\n  use types.{error};\n\n  /// Looks up the data associated with a particular User-Agent string.\n  ///\n  /// Returns a list of bytes containing JSON-encoded device data. See [here] for descriptions\n  /// of the JSON fields.\n  ///\n  /// [here]: https://www.fastly.com/documentation/reference/vcl/variables/client-request/client-identified/\n  lookup: func(user-agent: string, max-len: u64) -> result<option<string>, error>;\n}\n\n/// [Edge rate limiting] API.\n///\n/// [Edge rate limiting]: https://docs.fastly.com/products/edge-rate-limiting\ninterface erl {\n  use types.{error, open-error};\n\n  /// A rate counter that can be used with a edge rate limiter or\n  /// standalone for counting and rate calculations.\n  resource rate-counter {\n    /// Opens a `rate-counter` with the given name.\n    open: static func(name: string) -> result<rate-counter, open-error>;\n\n    /// Returns the name of this rate counter.\n    get-name: func() -> string;\n\n    /// Increments an entry in a rate counter and check if the client has exceeded some average number\n    /// of requests per second (RPS) over the window.\n    ///\n    /// If the client is over the rps limit for the window, add to the penaltybox for ttl. Valid ttl\n    /// span is 1m to 1h and TTL value is truncated to the nearest minute.\n    ///\n    /// Returns `true` if the client is penalized (i.e. should be limited), or `false` if not.\n    check-rate: func(\n      entry: string,\n      delta: u32,\n      window: u32,\n      limit: u32,\n      penalty-box: borrow<penalty-box>,\n      ttl: u32,\n    ) -> result<bool, error>;\n\n    /// Increments an entry in the ratecounter by `delta`.\n    increment: func(\n      entry: string,\n      delta: u32,\n    ) -> result<_, error>;\n\n    /// Looks up the current rate for entry in the ratecounter for a window.\n    lookup-rate: func(\n      entry: string,\n      window: u32,\n    ) -> result<u32, error>;\n\n    /// Looks up the current count for entry in the ratecounter for duration.\n    lookup-count: func(\n      entry: string,\n      duration: u32,\n    ) -> result<u32, error>;\n  }\n\n  /// A penaltybox that can be used with the edge rate limiter or\n  /// standalone for adding and checking if some entry is in the data set.\n  resource penalty-box {\n    /// Opens a `penalty-box` identified by the given name.\n    open: static func(name: string) -> result<penalty-box, open-error>;\n\n    /// Returns the name of this penaltybox.\n    get-name: func() -> string;\n\n    /// Adds `entry` to the penaltybox for the duration of ttl.\n    ///\n    /// Valid ttl span is 1m to 1h and TTL value is truncated to the nearest minute.\n    add: func(\n      entry: string,\n      ttl: u32,\n    ) -> result<_, error>;\n\n    /// Checks if `entry` is in the penaltybox.\n    has: func(\n      entry: string,\n    ) -> result<bool, error>;\n  }\n}\n\n/// Interface to Fastly's [Compute KV Store].\n///\n/// For a high-level introduction to this feature, see this [blog post].\n///\n/// [Compute KV Store]: https://www.fastly.com/documentation/guides/concepts/edge-state/data-stores/#kv-stores\n/// [blog post]: https://www.fastly.com/blog/introducing-the-compute-edge-kv-store-global-persistent-storage-for-compute-functions\ninterface kv-store {\n  use types.{error, open-error};\n  use http-body.{body};\n\n  /// A KV Store.\n  resource store {\n    /// Opens the KV Store with the given name.\n    open: static func(name: string) -> result<store, open-error>;\n\n    /// Looks up a value in the KV Store.\n    ///\n    /// Returns `ok(some(v))` with the value `v` that was found, `ok(none)` if no value was\n    /// found, or `err(e)` indicating the error `e` occurred.\n    ///\n    /// This function waits until the operation completes.\n    lookup: func(\n      key: string,\n    ) -> result<option<entry>, kv-error>;\n\n    /// Look up a value in the KV Store asynchronously.\n    ///\n    /// This function initiates an async lookup of a value in the KV Store. Use\n    /// `await-lookup` to finish the lookup.\n    lookup-async: func(\n      key: string,\n    ) -> result<pending-lookup, error>;\n\n    /// Inserts a value into the KV Store.\n    ///\n    /// If the KV Store already contains a value for this key, the `mode` field\n    /// of the `options` argument specifies how the existing value is handled.\n    ///\n    /// This function waits until the operation completes.\n    insert: func(\n      key: string,\n      body: body,\n      options: insert-options,\n    ) -> result<_, kv-error>;\n\n    /// Insert a value into the KV Store asynchronously.\n    ///\n    /// If the KV Store already contains a value for this key, the `mode` field\n    /// of the `options` argument specifies how the existing value is handled.\n    ///\n    /// This function initiates an async insert of a value in the KV Store. Use\n    /// `await-insert` to finish the lookup.\n    insert-async: func(\n      key: string,\n      body: body,\n      options: insert-options,\n    ) -> result<pending-insert, error>;\n\n    /// Deletes a value in the KV Store.\n    ///\n    /// Returns `ok(true)` if a value was successfully deleted, `ok(false)` if no value was\n    /// found, or `err(e)` indicating the error `e` occurred.\n    ///\n    /// This function waits until the operation completes.\n    delete: func(\n      key: string,\n    ) -> result<bool, kv-error>;\n\n    /// Delete of a value in the KV Store.\n    ///\n    /// This function initiates an async delete of a value in the KV Store. Use\n    /// `await-delete` to finish the lookup.\n    delete-async: func(\n      key: string,\n    ) -> result<pending-delete, error>;\n\n    /// Lists keys in the KV Store.\n    ///\n    /// Returns `ok(b)` with the body `b` on success, or `err(e)` indicating the error `e`\n    /// occurred.\n    ///\n    /// This function waits until the operation completes.\n    %list: func(\n      options: list-options,\n    ) -> result<body, kv-error>;\n\n    /// List of keys in the KV Store.\n    ///\n    /// This function initiates an async list value in the KV Store. Use\n    /// `await-list` to finish the lookup.\n    list-async: func(\n      options: list-options,\n    ) -> result<pending-list, error>;\n  }\n\n  /// An asynchronous KV Store lookup. Use `await-lookup` to resolve.\n  use async-io.{pollable as pending-lookup};\n\n  /// An asynchronous KV Store insert. Use `await-insert` to resolve.\n  use async-io.{pollable as pending-insert};\n\n  /// An asynchronous KV Store delete. Use `await-delete` to resolve.\n  use async-io.{pollable as pending-delete};\n\n  /// An asynchronous KV Store list. Use `await-list` to resolve.\n  use async-io.{pollable as pending-list};\n\n  /// A value indicating the status of a KV store operation.\n  variant kv-error {\n    /// KV store cannot or will not process the request due to something that is perceived to be a\n    /// client error.\n    ///\n    /// This will map to the api's 400 codes.\n    bad-request,\n    /// KV store cannot fulfill the request, as defined by the client's prerequisites, for example\n    /// `if-generation-match`.\n    ///\n    /// This will map to the api's 412 codes.\n    precondition-failed,\n    /// The size limit for a KV store key was exceeded.\n    ///\n    /// This will map to the api's 413 codes.\n    payload-too-large,\n    /// The system encountered an unexpected internal error.\n    ///\n    /// This will map to all remaining http error codes.\n    internal-error,\n    /// Too many requests have been made to the KV store.\n    ///\n    /// This will map to the api's 429 codes.\n    too-many-requests,\n    /// Generic error value.\n    ///\n    /// This means that some unexpected error occurred.\n    generic-error,\n    /// Additional error information may be added in the future via this resource type.\n    extra(extra-kv-error),\n  }\n\n  /// Extensibility for `kv-error`\n  resource extra-kv-error {}\n\n  /// Wait on the async lookup of a value in the KV Store.\n  ///\n  /// Returns `ok(some(v))` with the value `v` that was found, `ok(none)` if no value was\n  /// found, or `err(e)` indicating the error `e` occurred.\n  await-lookup: func(\n    handle: pending-lookup,\n  ) -> result<option<entry>, kv-error>;\n\n  /// Wait on the async insert of a value in the KV Store.\n  ///\n  /// Returns `ok` if the `insert` succeeded, or an error code on failure.\n  await-insert: func(\n    handle: pending-insert,\n  ) -> result<_, kv-error>;\n\n  /// Wait on the async delete of a value in the KV Store.\n  ///\n  /// Returns `ok(true)` if a value was successfully deleted, `ok(false)` if no value was\n  /// found, or `err(e)` indicating the error `e` occurred.\n  await-delete: func(\n    handle: pending-delete,\n  ) -> result<bool, kv-error>;\n\n  /// Wait on the async list of keys in the KV Store.\n  ///\n  /// Returns `ok(b)` with the JSON-encoded body `b` on success, or `err(e)` indicating\n  /// the error `e` occurred.\n  await-list: func(\n    handle: pending-list,\n  ) -> result<body, kv-error>;\n\n  /// A response from a KV Store Lookup operation.\n  ///\n  /// This type holds the `body`, metadata, and generation of found key.\n  resource entry {\n    /// Take and return the body from this `entry`, if it has one; otherwise return `none`.\n    ///\n    /// After calling this method, this entry will no longer have a body.\n    take-body: func() -> option<body>;\n\n    /// Read the metadata of the KV Store item, if present.\n    metadata: func(max-len: u64) -> result<option<string>, error>;\n\n    /// Read the current generation of the KV Store item.\n    generation: func() -> u64;\n  }\n\n  /// Selects the behavior for an insert when the new key matches an existing key.\n  ///\n  /// A KV store maintains the property that its keys are unique from each other. If an insert\n  /// has a key that doesn't match any key already in the store, then the pair of the key and the\n  /// new value is inserted into the store. However, if the insert's key does match a key already\n  /// in the store, then no new key-value pair is inserted, and the insert's `insert-mode.mode`\n  /// determines what it does instead.\n  enum insert-mode {\n    /// Updates the existing key's value by overwriting it with the new value.\n    ///\n    /// This is the default mode.\n    overwrite,\n\n    /// Fails, leaving the existing key's value unmodified.\n    ///\n    /// With this mode, the insert fails with a code of `kv-error.precondition-failed`, and\n    /// does not modify the existing value. Inserts with this mode will only “add” new key-value\n    /// pairs; they are prevented from modifying any existing ones.\n    add,\n\n    /// Updates the existing key's value by appending the new value to it.\n    append,\n\n    /// Updates the existing key's value by prepending the new value to it.\n    prepend,\n  }\n\n  /// Options for configuring the behavior of the `insert` function.\n  record insert-options {\n    /// If set, allows fetching from the origin to occur in the background, enabling a faster\n    /// response with stale content. The cache will be updated with fresh content after the request\n    /// is completed.\n    background-fetch: bool,\n\n    /// Requests for keys will return a “generation” header specific to the version of a key. The\n    /// generation header is a unique, non-serial 64-bit unsigned integer that can be used for\n    /// testing against a specific KV store value.\n    if-generation-match: option<u64>,\n\n    /// Sets an arbitrary data field which can contain up to 2000B of data.\n    metadata: option<string>,\n\n    /// Sets a time for the key to expire. Deletion will take place up to 24 hours after the ttl\n    /// reaches 0.\n    time-to-live-sec: option<u32>,\n\n    /// Select the behavior in the case when the new key matches an existing key.\n    mode: insert-mode,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-insert-options>>,\n  }\n\n  /// Extensibility for `insert-options`\n  resource extra-insert-options {}\n\n  /// Modes of KV Store list operations.\n  ///\n  /// This type serves to facilitate alternative methods of cache interactions with list operations.\n  enum list-mode {\n    /// Performs an un-cached list on every invocation.\n    ///\n    /// This is the default method of listing.\n    strong,\n\n    /// Returns a cached list response to improve performance.\n    ///\n    /// The data may be slightly out of sync with the store, but repeated calls are faster.\n    ///\n    /// The word “eventual” here refers to eventual consistency.\n    eventual,\n  }\n\n  /// Options for `list` and `list-async`.\n  record list-options {\n    /// The level of synchronization to perform.\n    mode: list-mode,\n    /// The item to start the list at.\n    cursor: option<string>,\n    /// The maximum number of items included the response.\n    limit: option<u32>,\n    /// The prefix match for items to include in the resultset.\n    prefix: option<string>,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-list-options>>,\n  }\n\n  /// Extensibility for `list-options`\n  resource extra-list-options {}\n}\n\n/// [Secret Store] API.\n///\n/// [Secret Store]: https://www.fastly.com/documentation/reference/api/services/resources/secret-store/\ninterface secret-store {\n  use types.{error, open-error};\n\n  /// An individual secret.\n  resource secret {\n    /// Creates a new “secret” from the given memory.\n    ///\n    /// This is *not* the suggested way to create `secret`s; instead, we suggest using `get`.\n    /// This secret will *NOT* be shared with other sandboxes.\n    ///\n    /// This method can be used for data that should be secret, but is being obtained by\n    /// some other means than the secret store. New “secrets” created this way use plaintext\n    /// only, and live in the sandbox's memory unencrypted for much longer than secrets\n    /// generated by `get`. They should thus only be used in situations in which an API requires\n    /// a `secret`, but you cannot (for whatever reason) use a `store` to store them.\n    ///\n    /// As the early note says, this `secret` will be local to the current sandbox, and\n    /// will not be shared with other instances of this service.\n    from-bytes: static func(bytes: list<u8>) -> result<secret, error>;\n\n    /// Returns the plaintext value of this secret.\n    plaintext: func(\n      max-len: u64\n    ) -> result<list<u8>, error>;\n  }\n\n  /// A Secret Store.\n  resource store {\n    /// Opens the Secret Store with the given name.\n    open: static func(name: string) -> result<store, open-error>;\n\n    /// Tries to look up a Secret by name in this secret store.\n    ///\n    /// If successful, this method returns `ok(some(s))` containing the found secret `s` if the\n    /// secret is found, or `ok(none)` if the secret was not found.\n    get: func(\n      key: string,\n    ) -> result<option<secret>, error>;\n  }\n}\n\n/// Blocklists using [Access Control Lists] (ACLs)\n///\n/// [Access Control Lists]: https://www.fastly.com/documentation/reference/api/acls/\ninterface acl {\n  use types.{error, open-error, ip-address};\n  use http-body.{body};\n\n  /// An ACL.\n  resource acl {\n    /// Opens an ACL linked to the current service with the given link name.\n    open: static func(name: string) -> result<acl, open-error>;\n\n    /// Performs a lookup of the given IP address in the ACL.\n    ///\n    /// If any matches are found, the result is a JSON-encoded HTTP body.\n    ///\n    /// If no matches are found, then `ok(none)` is returned. This corresponds\n    /// to an HTTP error code of 204, “No Content”.\n    lookup: func(\n      ip-addr: ip-address,\n    ) -> result<option<body>, acl-error>;\n  }\n\n  /// Errors returned on ACL lookup failure.\n  enum acl-error {\n    /// Too many requests have been made.\n    ///\n    /// This corresponds to an HTTP error code of 429, “Too Many Requests”.\n    too-many-requests,\n\n    /// Generic error value.\n    ///\n    /// This means that some unexpected error occurred.\n    generic-error,\n  }\n}\n\n/// [Backends] API.\n///\n/// A backend represents a service that the application can send requests to, potentially\n/// caching the responses received.\n///\n/// Backends come in one of two flavors:\n///   * **Static Backends**: These backends are created using the Fastly UI or API,\n///     and are predefined by the user. Static backends typically have short names that are\n///     usable across every instance of a service.\n///   * **Dynamic Backends**: These backends are created programmatically using the\n///     `register-dynamic-backend` API. They are defined at runtime, and may or may not\n///     be shared across sandboxes depending on how they are configured.\n///\n/// To use a backend, pass it to a `send*` function.\n///\n/// Future versions of this function may return an error if your service does not have a backend\n/// with this name.\n///\n/// [Backends]: https://www.fastly.com/documentation/guides/integrations/non-fastly-services/developer-guide-backends/\ninterface backend {\n  use types.{error, open-error};\n  use http-types.{tls-version};\n  use secret-store.{secret};\n\n  /// Creates a new dynamic backend.\n  ///\n  /// The arguments are the name of the new backend to use, along with a string describing the\n  /// backend host. The latter can be of the form:\n  ///\n  /// - \"<ip address>\"\n  /// - \"<hostname>\"\n  /// - \"<ip address>:<port>\"\n  /// - \"<hostname>:<port>\"\n  ///\n  /// The name can be whatever you would like, as long as it does not match the name of any of the\n  /// static service backends nor match any other dynamic backends built during this service\n  /// instance. (Names can overlap between different instances of the same service—they will be\n  /// treated as completely separate entities and will not be pooled—but you cannot, for example,\n  /// declare a dynamic backend named “dynamic-backend” twice in the same sandbox.)\n  ///\n  /// Dynamic backends must be enabled for the Compute service. You can determine whether or not\n  /// dynamic backends have been allowed for the current service by checking for the\n  /// `error.unsupported` error result. This error only arises when attempting to use dynamic\n  /// backends with a service that has not had dynamic backends enabled, or dynamic backends have\n  /// been administratively prohibited for the node in response to an ongoing incident.\n  register-dynamic-backend: func(\n    prefix: string,\n    target: string,\n    options: dynamic-backend-options,\n  ) -> result<backend, error>;\n\n  /// Options for `register-dynamic-backend`.\n  resource dynamic-backend-options {\n    /// Constructs an options resource with default values for all other possible fields for the\n    /// backend, which can be overridden using the other methods provided.\n    constructor();\n\n    /// Sets a host header override when contacting this backend.\n    ///\n    /// This will force the value of the “Host” header to the given string when sending out the\n    /// origin request. If this is not set and no header already exists, the “Host” header will\n    /// default to the target.\n    ///\n    /// For more information, see [the Fastly documentation on override hosts].\n    ///\n    /// [the Fastly documentation on override hosts]: https://docs.fastly.com/en/guides/specifying-an-override-host>\n    override-host: func(value: string);\n\n    /// Sets the connection timeout, in milliseconds, for this backend.\n    ///\n    /// Defaults to 1,000ms (1s).\n    connect-timeout: func(value: u32);\n\n    /// Sets a timeout, in milliseconds, that applies between the time of connection and the time we\n    /// get the first byte back.\n    ///\n    /// Defaults to 15,000ms (15s).\n    first-byte-timeout: func(value: u32);\n\n    /// Sets a timeout, in milliseconds, that applies between any two bytes we receive across the\n    /// wire.\n    ///\n    /// Defaults to 10,000ms (10s).\n    between-bytes-timeout: func(value: u32);\n\n    /// Enables or disables TLS to connect to the backend.\n    ///\n    /// When using TLS, Fastly checks the validity of the backend’s certificate, and fails the\n    /// connection if the certificate is invalid. This check is not optional: an invalid\n    /// certificate will cause the backend connection to fail (but read on).\n    ///\n    /// By default, the validity check does not require that the certificate hostname matches the\n    /// hostname of your request. You can use `cert-hostname` to request a check of the\n    /// certificate hostname.\n    ///\n    /// By default, certificate validity uses a set of public certificate authorities. You can\n    /// specify an alternative CA using `ca-certificate`.\n    use-tls: func(value: bool);\n\n    /// Sets the minimum TLS version for connecting to the backend.\n    ///\n    /// Setting this will enable TLS for the connection as a side effect.\n    tls-min-version: func(value: tls-version);\n\n    /// Sets the maximum TLS version for connecting to the backend.\n    ///\n    /// Setting this will enable TLS for the connection as a side effect. (\n    tls-max-version: func(value: tls-version);\n\n    /// Defines the hostname that the server certificate should declare, and turn on validation\n    /// during backend connections.\n    ///\n    /// You should enable this if you are using TLS, and setting this will enable TLS for the\n    /// connection as a side effect.\n    ///\n    /// If `cert-hostname` is not provided (default), the server certificate’s hostname may\n    /// have any value.\n    cert-hostname: func(value: string);\n\n    /// Sets the CA certificate to use when checking the validity of the backend.\n    ///\n    /// Setting this will enable TLS for the connection as a side effect.\n    ///\n    /// If `ca-certificate` is not provided (default), the backends’s certificate is validated\n    /// using a set of public root CAs.\n    ca-certificate: func(value: string);\n\n    /// Sets the acceptable cipher suites to use for TLS 1.0 - 1.2 connections.\n    ///\n    /// Setting this will enable TLS for the connection as a side effect.\n    tls-ciphers: func(value: string);\n\n    /// Sets the SNI hostname for the backend connection.\n    ///\n    /// Setting this will enable TLS for the connection as a side effect.\n    sni-hostname: func(value: string);\n\n    /// Provides the given client certificate to the server as part of the TLS handshake.\n    ///\n    /// Setting this will enable TLS for the connection as a side effect. Both the certificate and\n    /// the key to use should be in standard PEM format; providing the information in another\n    /// format will lead to an error. We suggest that (at least the) key should be held in\n    /// something like the Fastly secret store for security, with the handle passed to this\n    /// function without unpacking it via `secret.plaintext`; the certificate can be held in a less\n    /// secure medium.\n    ///\n    /// (If it is absolutely necessary to get the key from another source, we suggest the use of\n    /// `secret.from-bytes`.\n    client-cert: func(client-cert: string, key: borrow<secret>);\n\n    /// Configures up to how long to allow HTTP keepalive connections to remain idle in the\n    /// connection pool.\n    http-keepalive-time-ms: func(value: u32);\n\n    /// Configures whether or not to use TCP keepalive on the connection to the backend.\n    tcp-keepalive-enable: func(value: u32);\n\n    /// Configures how long to wait in between each TCP keepalive probe sent to the backend.\n    tcp-keepalive-interval-secs: func(value: u32);\n\n    /// Configures up to how many TCP keepalive probes to send to the backend before the connection\n    /// is considered dead.\n    tcp-keepalive-probes: func(value: u32);\n\n    /// Configures how long to wait after the last sent data over the TCP connection before starting\n    /// to send TCP keepalive probes.\n    tcp-keepalive-time-secs: func(value: u32);\n\n    /// Configures the maximum number of connections to keep in the local connection pool.\n    ///\n    /// `0` is unlimited.\n    ///\n    /// Note that this limit is best determined experimentally, since the total number of\n    /// connections to the backend will depend on POP sizes, HTTP keepalive limits, and the\n    /// traffic patterns for individual POPs.\n    max-connections: func(value: u32);\n\n    /// Configures how many times a pooled connection can be used.\n    ///\n    /// `0` is unlimited.\n    max-use: func(value: u32);\n\n    /// Configures an upper bound for how long an HTTP keepalive connection can be open before we\n    /// stop trying to reuse it.\n    ///\n    /// `0` is unlimited.\n    max-lifetime-ms: func(value: u32);\n\n    /// Determines whether or not connections to the same backend should be pooled across different\n    /// sandboxes.\n    ///\n    /// Fastly considers two backends “the same” if they’re registered with the same name and\n    /// the exact same settings. In those cases, when pooling is enabled, if one sandbox\n    /// opens a connection to this backend it will be left open, and can be re-used by a different\n    /// sandbox. This can help improve backend latency, by removing the need for the initial\n    /// network / TLS handshake(s).\n    ///\n    /// By default, pooling is enabled for dynamic backends.\n    pooling: func(value: bool);\n\n    /// Sets whether or not this backend will be used for gRPC traffic.\n    ///\n    /// Warning: Setting this for backends that will not be used with gRPC may have unpredictable\n    /// effects. Fastly only currently guarantees that this connection will work for gRPC traffic.\n    grpc: func(value: bool);\n\n    /// Whether to prefer attempting connections to IPv6 addresses over IPv4 addresses when a\n    /// hostname has both A and AAAA records.\n    ///\n    /// The Compute platform defaults to `true`, and will attempt IPv6 first if a AAAA record\n    /// is present.\n    prefer-ipv6: func(value: bool);\n  }\n\n  type timeout-ms = u32;\n  type timeout-secs = u32;\n  type probe-count = u32;\n\n  enum backend-health {\n    unknown,\n    healthy,\n    unhealthy,\n  }\n\n  resource backend {\n      /// Attempts to open the named static backend.\n      open: static func(name: string) -> result<backend, open-error>;\n\n      /// Returns the name of this backend.\n      get-name: func() -> string;\n\n      /// Returns the health of the backend if configured and currently known.\n      ///\n      /// For backends without a configured healthcheck, this will always return\n      /// `backend-health.unknown`.\n      is-healthy: func() -> result<backend-health, error>;\n\n      /// Returns `true` if the backend is a “dynamic” backend.\n      is-dynamic: func() -> result<bool, error>;\n\n      /// Gets the host of this backend.\n      get-host: func(max-len: u64) -> result<string, error>;\n\n      /// Gets the “override host” for this backend.\n      ///\n      /// This is used to change the `Host` header sent to the backend. See\n      /// [the Fastly documentation on override hosts].\n      ///\n      /// [the Fastly documentation on override hosts]: https://docs.fastly.com/en/guides/specifying-an-override-host\n      get-override-host: func(\n        max-len: u64,\n      ) -> result<option<list<u8>>, error>;\n\n      /// Gets the remote TCP port of the backend connection for the request.\n      get-port: func() -> result<u16, error>;\n\n      /// Gets the connection timeout of the backend.\n      get-connect-timeout-ms: func() -> result<timeout-ms, error>;\n\n      /// Gets the first byte timeout of the backend.\n      ///\n      /// This timeout applies between the time of connection and the time we get the first byte back.\n      get-first-byte-timeout-ms: func() -> result<timeout-ms, error>;\n\n      /// Gets the between byte timeout of the backend.\n      ///\n      /// This timeout applies between any two bytes we receive across the wire.\n      get-between-bytes-timeout-ms: func() -> result<timeout-ms, error>;\n\n      /// Returns `true` if the backend is configured to use TLS.\n      is-tls: func() -> result<bool, error>;\n\n      /// Gets the minimum TLS version this backend will use.\n      get-tls-min-version: func() -> result<option<tls-version>, error>;\n\n      /// Gets the maximum TLS version this backend will use.\n      get-tls-max-version: func() -> result<option<tls-version>, error>;\n\n      /// Returns the time for this backend to hold onto an idle HTTP keepalive connection\n      /// after it was last used before closing it.\n      get-http-keepalive-time: func() -> result<timeout-ms, error>;\n\n      /// Returns `true` if TCP keepalives have been enabled for this backend.\n      get-tcp-keepalive-enable: func() -> result<bool, error>;\n\n      /// Returns the time to wait in between sending each TCP keepalive probe to this backend.\n      get-tcp-keepalive-interval: func() -> result<timeout-secs, error>;\n\n      /// Returns the time to wait after the last data was sent before starting to send TCP keepalive\n      /// probes to this backend.\n      get-tcp-keepalive-probes: func() -> result<probe-count, error>;\n\n      /// Returns the time to wait after the last data was sent before starting to send TCP keepalive\n      /// probes to this backend.\n      get-tcp-keepalive-time: func() -> result<timeout-secs, error>;\n  }\n}\n\n/// Async IO support.\n///\n/// This module provides several utilities for performing I/O asynchronously.\n/// See the documentation for `async-io.pollable` for a description of the kinds\n/// of events it supports.\n///\n/// In the future, this interface is expected to be replaced by\n/// [integrated async features].\n///\n/// [integrated async features]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md#-async-explainer\ninterface async-io {\n  /// An object supporting generic async operations.\n  ///\n  /// Can be a `http-body.body`, `http-req.pending-response`, `http-req.pending-request`,\n  /// `cache.pending-entry`. `kv-store.pending-lookup`, `kv-store.pending-insert`,\n  /// `kv-store.pending-delete`, or `kv-store.pending-list`.\n  ///\n  /// Each async item has an associated I/O action:\n  ///\n  /// * Pending requests: awaiting the response headers / `response` object\n  /// * Normal bodies: reading bytes from the body\n  /// * Streaming bodies: writing bytes to the body\n  ///\n  /// For writing bytes, there is a large buffer associated with the handle that bytes\n  /// can eagerly be written into, even before the origin itself consumes that data.\n  resource pollable {\n    /// Make a nonblocking attempt to complete the I/O operation.\n    ///\n    /// Returns `true` if the given async item is “ready” for its associated I/O action, `false`\n    /// otherwise.\n    ///\n    /// If an object is ready, the I/O action is guaranteed to complete without blocking.\n    ///\n    /// Valid object handles includes bodies and pending requests. See the `async-io.pollable`\n    /// definition for more details, including what I/O actions are associated with each handle\n    /// type.\n    is-ready: func() -> bool;\n\n    /// Create a new trivial `pollable` which reports being immediately ready.\n    new-ready: static func() -> pollable;\n  }\n\n  /// Blocks until one of the given objects is ready for I/O.\n  ///\n  /// If an object is ready, the I/O action is guaranteed to complete without blocking.\n  ///\n  /// Valid object handles includes bodies and pending requests. See the `async-io.pollable`\n  /// definition for more details, including what I/O actions are associated with each handle\n  /// type.\n  ///\n  /// Returns the *index* (not handle!) of the first object that is ready.\n  ///\n  /// Traps if the list is empty.\n  select: func(handles: list<borrow<pollable>>) -> u32;\n\n  /// Blocks until one of the given objects is ready for I/O, or the timeout expires.\n  ///\n  /// If an object is ready, the I/O action is guaranteed to complete without blocking.\n  ///\n  /// Valid object handles includes bodies and pending requests. See the `async-io.pollable`\n  /// definition for more details, including what I/O actions are associated with each handle\n  /// type.\n  ///\n  /// The timeout is specified in milliseconds.\n  ///\n  /// Returns the *index* (not handle!) of the first object that is ready, or `none` if the\n  /// timeout expires before any objects are ready for I/O.\n  select-with-timeout: func(handles: list<borrow<pollable>>, timeout-ms: u32) -> option<u32>;\n}\n\n/// [Cache Purging] API.\n///\n/// [Cache Purging]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/\ninterface purge {\n  use types.{error};\n\n  record purge-options {\n    /// Perform a [soft purge] instead of a hard purge.\n    ///\n    /// [soft purge]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/#soft-vs-hard-purging\n    soft-purge: bool,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-purge-options>>,\n  }\n\n  /// Extensibility for `purge-options`\n  resource extra-purge-options {}\n\n  /// Purge a surrogate key for the current service.\n  ///\n  /// A surrogate key can be a max of 1024 characters.\n  /// A surrogate key must contain only printable ASCII characters (those between `0x21` and `0x7E`,\n  /// inclusive).\n  purge-surrogate-key: func(\n    surrogate-keys: string,\n    purge-options: purge-options,\n  ) -> result<_, error>;\n\n  /// Purge a surrogate key for the current service, and return the purge id.\n  ///\n  /// This is similar to `purge-surrogate-key`, but on success, returns a\n  /// [JSON purge response] containing an ASCII alphanumeric string identifying\n  /// a purging.\n  ///\n  /// [JSON purge response]: https://developer.fastly.com/reference/api/purging/#purge-tag\n  purge-surrogate-key-verbose: func(\n    surrogate-keys: string,\n    purge-options: purge-options,\n    max-len: u64,\n  ) -> result<string, error>;\n}\n\n/// [Core Cache] API\n///\n/// [Core Cache]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/#core-cache\ninterface cache {\n  use types.{error};\n  use http-body.{body};\n  use http-req.{request};\n\n  /// The outcome of a cache lookup (either bare or as part of a cache transaction)\n  resource entry {\n    /// Performs a non-request-collapsing cache lookup.\n    ///\n    /// Returns a result without waiting for any request collapsing that may be ongoing.\n    lookup: static func(\n      key: list<u8>,\n      options: lookup-options,\n    ) -> result<entry, error>;\n\n    /// The entrypoint to the request-collapsing cache transaction API.\n    ///\n    /// This operation always participates in request collapsing and may return stale objects. To\n    /// bypass request collapsing, use `entry.lookup` or `insert` instead.\n    transaction-lookup: static func(\n      key: list<u8>,\n      options: lookup-options,\n    ) -> result<entry, error>;\n\n    /// The entrypoint to the request-collapsing cache transaction API, returning instead of waiting\n    /// on busy.\n    ///\n    /// This operation always participates in request collapsing and may return stale objects. To\n    /// bypass request collapsing, use `entry.lookup` or `insert` instead.\n    transaction-lookup-async: static func(\n      key: list<u8>,\n      options: lookup-options,\n    ) -> result<pending-entry, error>;\n\n    /// Inserts an object into the cache with the given metadata.\n    ///\n    /// Can only be used in if the cache handle state includes the `must-insert-or-update` flag.\n    ///\n    /// The returned handle is to a streaming body that is used for writing the object into\n    /// the cache.\n    transaction-insert: func(\n      options: write-options,\n    ) -> result<body, error>;\n\n    /// Inserts an object into the cache with the given metadata, and return a readable stream of the\n    /// bytes as they are stored.\n    ///\n    /// This helps avoid the “slow reader” problem on a teed stream, for example when a program\n    /// wishes to store a backend request in the cache while simultaneously streaming to a client\n    /// in an HTTP response.\n    ///\n    /// The returned body handle is to a streaming body that is used for writing the object *into*\n    /// the cache. The returned cache handle provides a separate transaction for reading out the\n    /// newly cached object to send elsewhere.\n    transaction-insert-and-stream-back: func(\n      options: write-options,\n    ) -> result<tuple<body, entry>, error>;\n\n    /// Update the metadata of an object in the cache without changing its data.\n    ///\n    /// Can only be used in if the cache handle state includes both of the flags:\n    /// - `found`\n    /// - `must-insert-or-update`\n    transaction-update: func(\n      options: write-options,\n    ) -> result<_, error>;\n\n    /// Get the state of a cache lookup, waiting for the lookup to complete if necessary.\n    ///\n    /// Note that FOUND == USABLE, and means \"usable\" (fresh or stale-while-revalidate).\n    /// Some SDKs were released that checked only FOUND to infer \"usable\";\n    /// we preserve the equivalence for backwards compatibility.\n    get-state: func() -> result<lookup-state, error>;\n\n    /// Gets the user metadata of the found object, returning `ok(none)` if no object\n    /// was found.\n    get-user-metadata: func(max-len: u64) -> result<option<list<u8>>, error>;\n\n    /// Gets a range of the found object body, returning `ok(none)` if there\n    /// was no found object.\n    ///\n    /// The returned `body` must be closed before calling this function again on the same\n    /// `entry`.\n    ///\n    /// Note: until the CacheD protocol is adjusted to fully support this functionality,\n    /// the body of objects that are past the stale-while-revalidate period will not\n    /// be available, even when other metadata is.\n    get-body: func(\n      options: get-body-options,\n    ) -> result<body, error>;\n\n    /// Gets the content length of the found object, returning `ok(none)` if\n    /// there was no found object, or no content length was provided.\n    get-length: func() -> result<option<object-length>, error>;\n\n    /// Gets the configured max age of the found object, returning `ok(none)`\n    /// if there was no found object.\n    get-max-age-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the configured stale-while-revalidate period of the found object, returning `ok(none)`\n    /// if there was no found object.\n    get-stale-while-revalidate-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the age of the found object, returning `ok(none)` if there\n    /// was no found object.\n    get-age-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the number of cache hits for the found object, returning `ok(none)`\n    /// if there was no found object.\n    get-hits: func() -> result<option<cache-hit-count>, error>;\n\n    /// Cancel an obligation to provide an object to the cache.\n    ///\n    /// Useful if there is an error before streaming is possible, for example if a backend is\n    /// unreachable.\n    transaction-cancel: func() -> result<_, error>;\n  }\n  /// Handle that can be used to check whether or not a cache lookup is waiting on another client.\n  use async-io.{pollable as pending-entry};\n\n  /// A replace operation.\n  resource replace-entry {\n    /// The entrypoint to the replace API.\n    ///\n    /// This operation always participates in request collapsing and may return stale objects.\n    replace: static func(\n      key: list<u8>,\n      options: replace-options,\n    ) -> result<replace-entry, error>;\n\n    /// Gets the age of the existing object during replace, returning\n    /// `ok(none)` if there was no object.\n    get-age-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets a range of the existing object body, returning `ok(none)` if there\n    /// was no existing object.\n    ///\n    /// The returned `body` must be closed before calling this function\n    /// again on the same `replace-entry`.\n    get-body: func(\n      options: get-body-options,\n    ) -> result<option<body>, error>;\n\n    /// Gets the number of cache hits for the existing object during replace,\n    /// returning `ok(none)` if there was no object.\n    get-hits: func() -> result<option<cache-hit-count>, error>;\n\n    /// Gets the content length of the existing object during replace,\n    /// returning `ok(none)` if there was no object, or no content\n    /// length was provided.\n    get-length: func() -> result<option<object-length>, error>;\n\n    /// Gets the configured max age of the existing object during replace,\n    /// returning `ok(none)` if there was no object.\n    get-max-age-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the configured stale-while-revalidate period of the existing\n    /// object during replace, returning `ok(none)` if there was no\n    /// object.\n    get-stale-while-revalidate-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the lookup state of the existing object during replace, returning\n    /// `ok(none)` if there was no object.\n    ///\n    /// Note that FOUND == USABLE, and means \"usable\" (fresh or stale-while-revalidate).\n    /// Some SDKs were released that checked only FOUND to infer \"usable\";\n    /// we preserve the equivalence for backwards compatibility.\n    get-state: func() -> result<option<lookup-state>, error>;\n\n    /// Gets the user metadata of the existing object during replace, returning\n    /// `ok(none)` if there was no object.\n    get-user-metadata: func(\n      max-len: u64,\n    ) -> result<option<list<u8>>, error>;\n  }\n\n  type object-length = u64;\n  type duration-ns = u64;\n  type cache-hit-count = u64;\n\n  /// Options for cache lookup operations; currently used for both `lookup` and\n  /// `transaction-lookup`.\n  record lookup-options {\n    /// A full request handle, but used only for its headers\n    ///\n    /// May be `none` if the `request-headers` option isn't enabled.\n    ///\n    request-headers: option<borrow<request>>,\n\n    always-use-requested-range: bool,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-lookup-options>>,\n  }\n\n  /// Extensibility for `lookup-options`\n  resource extra-lookup-options {\n    constructor();\n  }\n\n  /// Configuration for several functions that write to the cache:\n  /// - `insert`\n  /// - `transaction-insert`\n  /// - `transaction-insert-and-stream-back`\n  /// - `transaction-update`\n  ///\n  /// Some options are only allowed for certain of these hostcalls; see the comments\n  /// on the fields.\n  record write-options {\n    /// this is a required field\n    max-age-ns: duration-ns,\n    /// a full request handle, but used only for its headers\n    ///\n    /// Only allowed for non-transactional `insert`\n    request-headers: option<borrow<request>>,\n    /// a list of header names separated by spaces\n    vary-rule: option<string>,\n    /// The initial age of the object in nanoseconds (default: 0).\n    ///\n    /// This age is used to determine the freshness lifetime of the object as well as to\n    /// prioritize which variant to return if a subsequent lookup matches more than one vary rule\n    initial-age-ns: option<duration-ns>,\n    stale-while-revalidate-ns: option<duration-ns>,\n    /// a list of surrogate keys separated by spaces\n    surrogate-keys: option<string>,\n    length: option<object-length>,\n    user-metadata: option<list<u8>>,\n    edge-max-age-ns: option<duration-ns>,\n    sensitive-data: bool,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-write-options>>,\n  }\n\n  /// Extensibility for `write-options`\n  resource extra-write-options {\n    constructor();\n  }\n\n  record get-body-options {\n    %from: option<u64>,\n    to: option<u64>,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-get-body-options>>,\n  }\n\n  /// Extensibility for `get-body-options`\n  resource extra-get-body-options {}\n\n  /// The status of this lookup (and potential transaction)\n  flags lookup-state {\n    /// A cached object was found\n    found,\n    /// The cached object is valid to use (implies found)\n    usable,\n    /// The cached object is stale (but may or may not be valid to use)\n    stale,\n    /// This client is requested to insert or revalidate an object\n    must-insert-or-update,\n    /// The cached object is only usable if revalidation has failed.\n    /// If usable-if-error is set, either must-insert-or-update will be set\n    /// (in which case the client must revalidate before use)\n    /// or usable will be set (in which case revalidation has already failed).\n    usable-if-error,\n  }\n\n  /// Performs a non-request-collapsing cache insertion (or update).\n  ///\n  /// The returned handle is to a streaming body that is used for writing the object into\n  /// the cache.\n  insert: func(\n    key: list<u8>,\n    options: write-options,\n  ) -> result<body, error>;\n\n  /// Continues the lookup transaction from which the given busy handle was returned,\n  /// waiting for the leader transaction if request collapsed, and returns a cache handle.\n  await-entry: func(\n    handle: pending-entry,\n  ) -> result<entry, error>;\n\n  /// Closes an interaction with the cache that has not yet finished request collapsing.\n  close-pending-entry: func(handle: pending-entry) -> result<_, error>;\n\n  /// Closes an ongoing interaction with the cache.\n  ///\n  /// If the cache handle state includes the `must-insert-or-update` (and hence no insert or\n  /// update has been performed), closing the handle cancels any request collapsing, potentially\n  /// choosing a new waiter to perform the insertion/update.\n  close-entry: func(handle: entry) -> result<_, error>;\n\n  /// Replace an object in the cache with the given metadata\n  ///\n  /// The returned handle is to a streaming body that is used for writing the object into\n  /// the cache.\n  replace-insert: func(\n    handle: replace-entry,\n    options: write-options,\n  ) -> result<body, error>;\n\n  /// Closes an ongoing replace interaction with the cache.\n  ///\n  /// If the replace handle state includes the `must-insert-or-update` (and hence no insert or\n  /// update has been performed), closing the handle cancels any request collapsing, potentially\n  /// choosing a new waiter to perform the insertion/update.\n  close-replace-entry: func(handle: replace-entry) -> result<_, error>;\n\n  /// Options for cache replace operations\n  record replace-options {\n    /// a full request handle, but used only for its headers\n    request-headers: option<borrow<request>>,\n    replace-strategy: option<replace-strategy>,\n    always-use-requested-range: bool,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-replace-options>>,\n  }\n\n  /// Extensibility for `replace-options`\n  resource extra-replace-options {\n    constructor();\n  }\n\n  enum replace-strategy {\n    /// Immediately start the replace and do not wait for any other pending requests for the same\n    /// object, including insert requests.\n    ///\n    /// With this strategy a replace will race all other pending requests to update the object.\n    ///\n    /// The existing object will be accessible until this replace finishes providing the replacement\n    /// object.\n    ///\n    /// This is the default replace strategy.\n    immediate,\n\n    /// Immediate, but remove the existing object immediately\n    ///\n    /// Requests for the same object that arrive after this replace starts will wait until this\n    /// replace starts providing the replacement object.\n    immediate-force-miss,\n\n    /// Join the wait list behind other pending requests before starting this request.\n    ///\n    /// With this strategy this replace request will wait for an in-progress replace or insert\n    /// request before starting.\n    ///\n    /// This strategy allows implementing a counter, but may cause timeouts if too many requests\n    /// are waiting for in-progress and waiting updates to complete.\n    wait,\n  }\n}\n\n/// [HTTP Cache] API.\n///\n/// Overall, this should look very familiar to users of the Core Cache API. The primary differences\n/// are:\n///\n/// - HTTP `request`s and `response`s are used rather than relying on the user to\n///   encode headers, status codes, etc in `user-metadata`.\n///\n/// - Convenience functions specific to HTTP semantics are provided, such as `is-request-cacheable`,\n///   `get-suggested-backend-request`, `get-suggested-write-options`, and\n///   `transaction-record-not-cacheable`.\n///\n/// The HTTP-specific behavior of these functions is intended to support applications that match the\n/// normative guidance in [RFC 9111]. For example, `is-request-cacheable` returns `false` for `POST`\n/// requests. However, this answer along with those of many of these functions explicitly provide\n/// *suggestions*; they do not necessarily need to be followed if custom behavior is required, such\n/// as caching `POST` responses when the application author knows that to be safe.\n///\n/// The starting points for this API are `lookup` (no request collapsing) and `transaction-lookup`\n/// (request collapsing).\n///\n/// [HTTP Cache]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/cache-freshness/\n/// [RFC 9111]: https://www.rfc-editor.org/rfc/rfc9111.html\ninterface http-cache {\n  use types.{error};\n  use http-body.{body};\n  use http-req.{request};\n  use http-resp.{response, response-with-body};\n  use backend.{backend};\n  use cache.{lookup-state, object-length, duration-ns, cache-hit-count};\n\n  /// An HTTP Cache transaction.\n  resource entry {\n    /// Performs a cache lookup based on the given request.\n    ///\n    /// This operation always participates in request collapsing and may return an obligation to\n    /// insert or update responses, and/or stale responses.\n    ///\n    /// The request is not consumed.\n    transaction-lookup: static func(\n      req-handle: borrow<request>,\n      options: lookup-options,\n    ) -> result<entry, error>;\n\n    /// Inserts a response into the cache with the given options, returning a streaming body handle\n    /// that is ready for writing or appending.\n    ///\n    /// Can only be used if the cache handle state includes the `must-insert-or-update` flag.\n    ///\n    /// The response is consumed.\n    transaction-insert: func(\n      resp-handle: response,\n      options: write-options,\n    ) -> result<body, error>;\n\n    /// Inserts a response into the cache with the given options, and return a fresh cache handle\n    /// that can be used to retrieve and stream the response while it's being inserted.\n    ///\n    /// This helps avoid the “slow reader” problem on a teed stream, for example when a program\n    /// wishes to store a backend request in the cache while simultaneously streaming to a client\n    /// in an HTTP response.\n    ///\n    /// The response is consumed.\n    transaction-insert-and-stream-back: func(\n      resp-handle: response,\n      options: write-options,\n    ) -> result<tuple<body, entry>, error>;\n\n    /// Updates freshness lifetime, response headers, and caching settings without updating the\n    /// response body.\n    ///\n    /// Can only be used in if the cache handle state includes both of the flags:\n    /// - `found`\n    /// - `must-insert-or-update`\n    ///\n    /// The response is consumed.\n    transaction-update: func(\n      resp-handle: response,\n      options: write-options,\n    ) -> result<_, error>;\n\n    /// Updates freshness lifetime, response headers, and caching settings without updating the\n    /// response body, and return a fresh cache handle that can be used to retrieve and stream the\n    /// stored response.\n    ///\n    /// Can only be used in if the cache handle state includes both of the flags:\n    /// - `found`\n    /// - `must-insert-or-update`\n    ///\n    /// The response is consumed.\n    transaction-update-and-return-fresh: func(\n      resp-handle: response,\n      options: write-options,\n    ) -> result<entry, error>;\n\n    /// Fulfill an obligation to provide a response to the cache by selecting a stale-if-error response.\n    ///\n    /// A guest that is obligated to insert/update the cache may not be able to produce an acceptable\n    /// response (e.g. unreachable backend, 5xx response). If the cache contains a response in the\n    /// stale-if-error period, the guest may prefer to use that response rather than returning an error.\n    /// If so, they can call transaction-choose-stale, after which the cache handle will reflect the stale\n    /// response (via get-found-response, get-state, etc).\n    ///\n    /// `transaction-choose-stale` is an alternative to `transaction-update-and-return-fresh` or\n    /// `transaction-insert-and-stream-back`. Like those methods, it completes a request collapse,\n    /// providing the stale response to all collapsed transactions; and, after calling\n    /// `transaction-choose-stale`, the cache handle provides the (stale) response to send to the client.\n    ///\n    /// However, `transaction-choose-stale` does not change the cached item. The next lookup will again\n    /// collapse and/or get an obligation to revalidate.\n    transaction-choose-stale: func() -> result<_, error>;\n\n    /// Fulfill an obligation to provide a response to the cache by disabling request collapsing and\n    /// response caching for this cache entry.\n    ///\n    /// In Varnish terms, this function stores a hit-for-pass object.\n    ///\n    /// Only the max age and, optionally, the vary rule are read from the `options` argument.\n    transaction-record-not-cacheable: func(\n      options: write-options,\n    ) -> result<_, error>;\n\n    /// Prepares a suggested request to make to a backend to satisfy the looked-up request.\n    ///\n    /// If there is a stored, stale response, this suggested request may be for revalidation. If the\n    /// looked-up request is ranged, the suggested request will be unranged in order to try caching\n    /// the entire response.\n    get-suggested-backend-request: func() -> result<request, error>;\n\n    /// Prepares a suggested set of cache write options for a given request and response pair.\n    ///\n    /// The response is not consumed.\n    get-suggested-write-options: func(\n      response: borrow<response>,\n    ) -> result<suggested-write-options, error>;\n\n    /// Adjusts a response into the appropriate form for storage and provides a storage action\n    /// recommendation.\n    ///\n    /// For example, if the looked-up request contains conditional headers, this function will\n    /// interpret a `304 Not Modified` response for revalidation by updating headers.\n    ///\n    /// In addition to the updated response, this function returns the recommended storage action.\n    prepare-response-for-storage: func(\n      response: borrow<response>,\n    ) -> result<tuple<storage-action, response>, error>;\n\n    /// Retrieves a stored response from the cache, returning `ok(none)` if\n    /// there was no response found.\n    ///\n    /// If `transform-for-client` is set, the response will be adjusted according to the looked-up\n    /// request. For example, a response retrieved for a range request may be transformed into a\n    /// `206 Partial Content` response with an appropriate `content-range` header.\n    get-found-response: func(\n      transform-for-client: u32,\n    ) -> result<option<response-with-body>, error>;\n\n    /// Gets the state of a cache transaction.\n    ///\n    /// Primarily useful after performing the lookup to determine what subsequent operations are\n    /// possible and whether any insertion or update obligations exist.\n    get-state: func(\n    ) -> result<lookup-state, error>;\n\n    /// Gets the length of the found response, returning `ok(none)` if there\n    /// was no response found or no length was provided.\n    get-length: func() -> result<option<object-length>, error>;\n\n    /// Gets the configured max age of the found response in nanoseconds, returning `ok(none)`\n    /// if there was no response found.\n    get-max-age-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the configured stale-while-revalidate period of the found response in nanoseconds,\n    /// returning `ok(none)` if there was no response found.\n    get-stale-while-revalidate-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the configured stale-if-error period of the found response in nanoseconds,\n    /// returning `ok(none)` if there was no response found.\n    get-stale-if-error-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the age of the found response in nanoseconds, returning `ok(none)`\n    /// if there was no response found.\n    get-age-ns: func() -> result<option<duration-ns>, error>;\n\n    /// Gets the number of cache hits for the found response, returning `ok(none)`\n    /// if there was no response found.\n    ///\n    /// This figure only reflects hits for a stored response in a particular cache server\n    /// or cluster, not the entire Fastly network.\n    get-hits: func() -> result<option<cache-hit-count>, error>;\n\n    /// Gets whether a found response is marked as containing sensitive data, returning `ok(none)`\n    /// if there was no response found.\n    get-sensitive-data: func() -> result<option<bool>, error>;\n\n    /// Gets the surrogate keys of the found response, returning `ok(none)` if\n    /// there was no response found.\n    ///\n    /// The output is a list of surrogate keys separated by spaces.\n    ///\n    /// If the full list requires more than `max-len` bytes, an `error.buffer-len`\n    /// error is returned containing the required size.\n    get-surrogate-keys: func(\n      max-len: u64,\n    ) -> result<option<string>, error>;\n\n    /// Gets the vary rule of the found response, returning `ok(none)` if there\n    /// was no response found.\n    ///\n    /// The output is a list of header names separated by spaces.\n    ///\n    /// If the full list requires more than `max-len` bytes, an `error.buffer-len`\n    /// error is returned containing the required size.\n    get-vary-rule: func(\n      max-len: u64,\n    ) -> result<option<string>, error>;\n\n    /// Abandons an obligation to provide a response to the cache.\n    ///\n    /// Useful if there is an error before streaming is possible, for example if a backend is\n    /// unreachable.\n    ///\n    /// If there are other requests collapsed on this transaction, one of those other requests will\n    /// be awoken and given the obligation to provide a response. If subsequent requests\n    /// are unlikely to yield cacheable responses, this may lead to undesired serialization of\n    /// requests. Consider using `transaction-record-not-cacheable` to make lookups for this request\n    /// bypass the cache.\n    transaction-abandon: func() -> result<_, error>;\n  }\n\n  /// The suggested action to take for spec-recommended behavior following\n  /// `prepare-response-for-storage`.\n  enum storage-action {\n    /// Insert the response into cache (for `transaction-insert` and\n    /// `transaction-insert-and-stream-back`).\n    insert,\n    /// Update the stale response in cache (for `transaction-update` and\n    /// `transaction-update-and-return-fresh`).\n    update,\n    /// Do not store this response.\n    do-not-store,\n    /// Do not store this response, and furthermore record its non-cacheability for other pending\n    /// requests (`transaction-record-not-cacheable`).\n    record-uncacheable,\n  }\n\n  /// Non-required options for cache lookups.\n  record lookup-options {\n    /// Cache key to use in lieu of the automatically-generated cache key based on the request's\n    /// properties.\n    ///\n    /// The cache key must be exactly 32 bytes long.\n    override-key: option<list<u8>>,\n    /// Backend that will be used for the eventual request.\n    backend: option<borrow<backend>>,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-lookup-options>>,\n  }\n\n  /// Extensibility for `lookup-options`\n  resource extra-lookup-options {}\n\n  /// Options for cache insertions and updates.\n  record write-options {\n    /// The maximum age of the response before it is considered stale, in nanoseconds.\n    ///\n    /// This field is required.\n    max-age-ns: duration-ns,\n\n    /// A list of header names to use when calculating variants for this response.\n    ///\n    /// The format is a string containing header names separated by spaces.\n    vary-rule: option<string>,\n\n    /// The initial age of the response in nanoseconds.\n    ///\n    /// If this field is not set, the default value is zero.\n    ///\n    /// This age is used to determine the freshness lifetime of the response as well as to\n    /// prioritize which variant to return if a subsequent lookup matches more than one vary rule\n    initial-age-ns: option<duration-ns>,\n\n    /// The maximum duration after `max-age` during which the response may be delivered stale\n    /// while being revalidated, in nanoseconds.\n    ///\n    /// If this field is not set, the default value is zero.\n    stale-while-revalidate-ns: option<duration-ns>,\n\n    /// The maximum duration after `max-age` during which the response may be delivered stale\n    /// if synchronous revalidation produces an error.\n    ///\n    /// If this field is not set, the default value is zero.\n    stale-if-error-ns: option<duration-ns>,\n\n    /// A list of surrogate keys that may be used to purge this response.\n    ///\n    /// The format is a string containing [valid surrogate keys] separated by spaces.\n    ///\n    /// If this field is not set, no surrogate keys will be associated with the response. This\n    /// means that the response cannot be purged except via a purge-all operation.\n    ///\n    /// [valid surrogate keys]: https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Key/\n    surrogate-keys: option<string>,\n\n    /// The length of the response body.\n    ///\n    /// If this field is not set, the length of the body is treated as unknown.\n    ///\n    /// When possible, this field should be set so that other clients waiting to retrieve the\n    /// body have enough information to synthesize a `content-length` even before the complete\n    /// body is inserted to the cache.\n    length: option<object-length>,\n\n    /// Enable or disable PCI/HIPAA-compliant non-volatile caching.\n    ///\n    /// See the [Fastly PCI-Compliant Caching and Delivery documentation] for details.\n    ///\n    /// [Fastly PCI-Compliant Caching and Delivery documentation]: https://docs.fastly.com/products/pci-compliant-caching-and-delivery\n    sensitive-data: bool,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-write-options>>,\n  }\n\n  /// Extensibility for `write-options`\n  resource extra-write-options {}\n\n  /// Determines whether a request is cacheable per conservative [RFC 9111] semantics.\n  ///\n  /// In particular, this function checks whether the request method is `GET` or `HEAD`, and\n  /// considers requests with other methods uncacheable. Applications where it is safe to cache\n  /// responses to other methods should consider using their own cacheability check instead of\n  /// this function.\n  ///\n  /// [RFC 9111]: https://www.rfc-editor.org/rfc/rfc9111.html\n  is-request-cacheable: func(request: borrow<request>) -> result<bool, error>;\n\n  /// Retrieves the default cache key for the request.\n  ///\n  /// If the full key requires more than `max-len` bytes, an `error.buffer-len`\n  /// error is returned containing the required size.\n  ///\n  /// At the moment, HTTP cache keys must always be 32 bytes.\n  get-suggested-cache-key: func(\n    request: borrow<request>,\n    max-len: u64,\n  ) -> result<list<u8>, error>;\n\n  /// Closes an ongoing interaction with the cache.\n  ///\n  /// If the cache handle state includes `must-insert-or-update` (and hence no insert or update\n  /// has been performed), closing the handle cancels any request collapsing, potentially choosing\n  /// a new waiter to perform the insertion/update.\n  close-entry: func(\n    handle: entry,\n  ) -> result<_, error>;\n\n  /// The methods in this resource return values that correspond to the fields in a\n  /// `write-options`. This type is used when a `write-options` value would\n  /// be returned, so that it can use `max-len` parameters when returning\n  /// dynamically-sized data, and so that it excludes the `extra` field, since borrowed\n  /// handles cannot be returned from functions.\n  resource suggested-write-options {\n    /// Returns the suggested value for the `write-options.max-age-ns` field.\n    get-max-age-ns: func() -> duration-ns;\n    /// Returns the suggested value for the `write-options.vary-rule` field.\n    get-vary-rule: func(max-len: u64) -> result<string, error>;\n    /// Returns the suggested value for the `write-options.initial-age-ns` field.\n    get-initial-age-ns: func() -> duration-ns;\n    /// Returns the suggested value for the `write-options.stale-while-revalidate-ns` field.\n    get-stale-while-revalidate-ns: func() -> duration-ns;\n    /// Returns the suggested value for the `write-options.stale-if-error-ns` field.\n    get-stale-if-error-ns: func() -> duration-ns;\n    /// Returns the suggested value for the `write-options.surrogate-keys` field.\n    get-surrogate-keys: func(max-len: u64) -> result<string, error>;\n    /// Returns the suggested value for the `write-options.length` field.\n    get-length: func() -> option<object-length>;\n    /// Returns the suggested value for the `write-options.sensitive-data` field.\n    get-sensitive-data: func() -> bool;\n  }\n}\n\n/// [Config Store] API.\n///\n/// [Config Store]: https://www.fastly.com/documentation/guides/concepts/edge-state/dynamic-config/#config-stores\ninterface config-store {\n  use types.{error, open-error};\n\n  /// A Config Store.\n  resource store {\n    /// Attempts to open the named config store.\n    ///\n    /// Names are case sensitive.\n    open: static func(name: string) -> result<store, open-error>;\n\n    /// Fetches a value from the config store, returning `ok(none)` if it doesn't exist.\n    get: func(\n      key: string,\n      max-len: u64,\n    ) -> result<option<string>, error>;\n  }\n}\n\n/// [Shielding] API.\n///\n/// [Shielding]: https://www.fastly.com/documentation/guides/concepts/shielding/\ninterface shielding {\n  use types.{error};\n  use backend.{backend};\n\n  shield-info: func(\n    name: string,\n    max-len: u64,\n  ) -> result<string, error>;\n\n  /// Options for `backend-for-shield`.\n  resource shield-backend-options {\n    constructor();\n\n    set-cache-key: func(cache-key: string);\n    set-first-byte-timeout: func(timeout-ms: u32);\n  }\n\n  backend-for-shield: func(\n    name: string,\n    options: option<borrow<shield-backend-options>>,\n  ) -> result<backend, error>;\n}\n\n/// [Image Optimizer] API.\n///\n/// [Image Optimizer]: https://www.fastly.com/documentation/guides/full-site-delivery/image-optimization/about-fastly-image-optimizer/\ninterface image-optimizer {\n  use http-body.{body};\n  use http-req.{request};\n  use http-resp.{response-with-body};\n  use types.{error};\n  use backend.{backend};\n\n  record image-optimizer-transform-options {\n    /// Contains any Image Optimizer API parameters that were set\n    /// as well as the Image Optimizer region the request is meant for.\n    sdk-claims-opts: option<string>,\n\n    /// Additional options may be added in the future via this resource type.\n    extra: option<borrow<extra-image-optimizer-transform-options>>,\n  }\n\n  /// Extensibility for `image-optimizer-transform-options`\n  resource extra-image-optimizer-transform-options {}\n\n  transform-image-optimizer-request: func(\n    origin-image-request: borrow<request>,\n    origin-image-request-body: option<body>,\n    origin-image-request-backend: borrow<backend>,\n    io-transform-options: image-optimizer-transform-options,\n  ) -> result<response-with-body, error>;\n}\n\n/// The exported interface.\n///\n/// The `handle` function serves as the main entrypoint to applications. Unlike the\n/// rest of the interfaces in this package, this `http-incoming` interface is exported by\n/// applications rather than imported, which means that this is a function defined\n/// by the application and called from the outside, rather than a function called\n/// by the application into the outside.\ninterface http-incoming {\n  use http-body.{body};\n  use http-req.{request};\n\n  /// Handle the given request.\n  ///\n  /// This function is called once per sandbox. When it returns, the\n  /// sandbox exits. To opt into receiving multiple requests in a single\n  /// sandbox, use `http-downstream.next-request`.\n  ///\n  /// To send a response for the given `request`, use `send-downstream`, or to\n  /// stream the response body after the response has been initiated, use\n  /// `send-downstream-streaming`.\n  handle: func(request: request, body: body) -> result;\n}\n\n/// Features for interacting with the Compute runtime.\ninterface compute-runtime {\n  /// A timestamp in milliseconds.\n  type vcpu-ms = u64;\n  /// An amount of memory in mebibytes (2^20 bytes).\n  type memory-mib = u32;\n\n\n  /// Gets the amount of vCPU time that has passed since this sandbox was started, in\n  /// milliseconds.\n  ///\n  /// This function returns only time spent running on a vCPU, and does not include time spent\n  /// performing any I/O operations. However, it is based on clock time passing, and so will include\n  /// time spent executing hostcalls, is heavily affected by what core of what CPU is running the\n  /// code, and can even be influenced by the state of the CPU.\n  ///\n  /// As a result, this function *should not be used in benchmarking across runs*. It can be used,\n  /// with caution, to compare the runtime of different operations within the same sandbox.\n  get-vcpu-ms: func() -> vcpu-ms;\n\n  /// Get a snapshot of the current dynamic memory usage, rounded up to the nearest mebibyte (2^20).\n  ///\n  /// This includes usage from the Wasm linear memory (heap) and usage from host allocations\n  /// made on behalf of this sandbox, e.g. buffered bodies of HTTP responses.\n  /// The returned value is just a snapshot- it can change without any explicit action\n  /// by the sandbox (for instance, additional response data coming in from an HTTP response.)\n  /// It can also change over time / across runs, as the Compute platform's memory usage\n  /// changes. Consider the returned value with these uncertainties in mind.\n  get-heap-mib: func() -> memory-mib;\n\n  /// A UUID generated by Fastly for each sandbox.\n  ///\n  /// This is often a useful value to include in log messages, and also to send to upstream\n  /// servers as an additional custom HTTP header, allowing for straightforward correlation of\n  /// which sandbox processed a request to requests later processed by an origin server.\n  ///\n  /// By default, each sandbox handles exactly one downstream request, in which case\n  /// this sandbox UUID is unique for each request. However, by using\n  /// `http-downstream.next-request`, a single sandbox can accept multiple downstream\n  /// requests. For a UUID that reliably identifies a request, you may wish to use\n  /// `http-downstream.downstream-client-request-id`.\n  ///\n  /// Equivalent to the \"FASTLY_TRACE_ID\" environment variable.\n  get-sandbox-id: func() -> string;\n\n  /// The hostname of the Fastly cache server which is executing the current sandbox, for\n  /// example, `cache-jfk1034`.\n  ///\n  /// Equivalent to the \"FASTLY_HOSTNAME\" environment variable and to [`server.hostname`] in VCL.\n  ///\n  /// [`server.hostname`]: https://www.fastly.com/documentation/reference/vcl/variables/server/server-hostname/\n  get-hostname: func() -> string;\n\n  /// The three-character identifying code of the [Fastly POP] in which the current service\n  /// instance is running.\n  ///\n  /// Equivalent to the \"FASTLY_POP\" environment variable and to [`server.datacenter`] in VCL.\n  ///\n  /// [Fastly POP]: https://www.fastly.com/documentation/guides/concepts/pop/\n  /// [`server.datacenter`]: https://www.fastly.com/documentation/reference/vcl/variables/server/server-datacenter/\n  get-pop: func() -> string;\n\n  /// A code representing the general geographic region in which the [Fastly POP] processing the\n  /// current Compute sandbox resides.\n  ///\n  /// Equivalent to the \"FASTLY_REGION\" environment variable and to [`server.region`] in VCL, and\n  /// has the same possible values.\n  ///\n  /// [`server.region`]: https://www.fastly.com/documentation/reference/vcl/variables/server/server-region/\n  /// [Fastly POP]: https://www.fastly.com/documentation/guides/concepts/pop/\n  get-region: func() -> string;\n\n  /// The current cache generation value for this Fastly service.\n  ///\n  /// The cache generation value is incremented by [purge-all operations].\n  ///\n  /// Equivalent to the \"FASTLY_CACHE_GENERATION\" environment variable and to\n  /// [`req.vcl.generation`] in VCL.\n  ///\n  /// [purge-all operations]: https://www.fastly.com/documentation/guides/concepts/edge-state/cache/purging/\n  /// [`req.vcl.generation`]: https://www.fastly.com/documentation/reference/vcl/variables/miscellaneous/req-vcl-generation/\n  get-cache-generation: func() -> u64;\n\n  /// The customer ID of the Fastly customer account to which the currently executing Fastly\n  /// service belongs.\n  ///\n  /// Equivalent to the \"FASTLY_CUSTOMER_ID\" environment variable and to [`req.customer_id`] in VCL.\n  ///\n  /// [`req.customer_id`]: https://www.fastly.com/documentation/reference/vcl/variables/miscellaneous/req-customer-id/\n  get-customer-id: func() -> string;\n\n  /// Whether the request is running in the Fastly service's [staging environment].\n  ///\n  /// `false` for production or `true` for staging.\n  ///\n  /// Equivalent to the \"FASTLY_IS_STAGING\" environment variable and to [`fastly.is_staging`] in VCL.\n  ///\n  /// [`fastly.is_staging`]: https://www.fastly.com/documentation/reference/vcl/variables/miscellaneous/fastly-is-staging/\n  /// [staging environment]: https://docs.fastly.com/products/staging\n  get-is-staging: func() -> bool;\n\n  /// The identifier for the Fastly service that is processing the current request.\n  ///\n  /// Equivalent to the \"FASTLY_SERVICE_ID\" environment variable and to [`req.service_id`] in VCL.\n  ///\n  /// [`req.service_id`]: https://www.fastly.com/documentation/reference/vcl/variables/miscellaneous/req-service-id/\n  get-service-id: func() -> string;\n\n  /// The version number for the Fastly service that is processing the current request.\n  ///\n  /// Equivalent to the \"FASTLY_SERVICE_VERSION\" environment variable and to [`req.vcl.version`]\n  /// in VCL.\n  ///\n  /// [`req.vcl.version`]: https://www.fastly.com/documentation/reference/vcl/variables/miscellaneous/req-vcl-version/\n  get-service-version: func() -> u64;\n\n  /// This function is not suitable for general-purpose use.\n  get-namespace-id: func() -> string;\n}\n\n/// Interfaces that a Fastly Compute service may import.\n///\n/// This contains the imports used in the `service` world, factored out into a\n/// separate world so that it can be used by library components. Library components\n/// are components that do not export anything themselves.\nworld service-imports {\n  import wasi:clocks/wall-clock@0.2.6;\n  import wasi:clocks/monotonic-clock@0.2.6;\n  import wasi:io/error@0.2.6;\n  import wasi:io/streams@0.2.6;\n  import wasi:io/poll@0.2.6;\n  import wasi:random/random@0.2.6;\n  import wasi:random/insecure@0.2.6;\n  import wasi:random/insecure-seed@0.2.6;\n  import wasi:cli/environment@0.2.6;\n  import wasi:cli/exit@0.2.6;\n  import wasi:cli/stdout@0.2.6;\n  import wasi:cli/stderr@0.2.6;\n  import wasi:cli/stdin@0.2.6;\n\n  import acl;\n  import async-io;\n  import backend;\n  import cache;\n  import compute-runtime;\n  import config-store;\n  import dictionary;\n  import geo;\n  import device-detection;\n  import erl;\n  import http-body;\n  import http-cache;\n  import http-downstream;\n  import http-req;\n  import http-resp;\n  import image-optimizer;\n  import log;\n  import kv-store;\n  import purge;\n  import secret-store;\n  import security;\n  import shielding;\n}\n\n/// A Fastly Compute service.\n///\n/// This defines the set of interfaces available to, and expected of,\n/// Fastly Compute service applications.\n///\n/// This `service` world includes all the `service-imports` imports, and adds the\n/// `http-incoming` exports.\nworld service {\n  include service-imports;\n\n  // Export the `http-incoming` interface.\n  export http-incoming;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/fastly-adapter/adapter.wit",
    "content": "/// Interfaces available to the component adapter, which are not otherwise\n/// part of the Fastly Compute platform.\npackage fastly:adapter;\n\n/// Adapter functions formerly of `fastly:compute/http-req`.\n///\n/// These functions depend on the host maintaining an implicit downstream\n/// request. They were deprecated and replaced by functions in the\n/// `http-downstream` interface which do the same thing but take an explicit\n/// `request` handle.\ninterface adapter-http-req {\n  use fastly:compute/types@0.1.0.{error, ip-address};\n  use fastly:compute/http-req@0.1.0.{\n    client-cert-verify-result, request, body, response-with-body, error-with-detail,\n    pending-response\n  };\n\n  downstream-tls-cipher-openssl-name: func(max-len: u64) -> result<option<list<u8>>, error>;\n  downstream-tls-protocol: func(max-len: u64) -> result<option<list<u8>>, error>;\n\n  /// Deprecated, because it doesn't return `none` on an empty certificate.\n  downstream-tls-raw-client-certificate-deprecated: func(max-len: u64) -> result<option<list<u8>>, error>;\n\n  get-original-header-names: func(\n    max-len: u64,\n    cursor: u32,\n  ) -> result<tuple<string, option<u32>>, error>;\n\n  original-header-count: func() -> result<u32, error>;\n}\n\n/// A world that just imports all the deprecated APIs, split out from the main\n/// world below so that we can refer to it in tests.\nworld adapter-imports {\n  import adapter-http-req;\n}\n\n/// The `fastly:compute/service` world plus the deprecated interfaces.\nworld adapter-service {\n  // Make this world a superset of the public `service` world.\n  include fastly:compute/service@0.1.0;\n\n  // And, add all the deprecated interfaces.\n  include adapter-imports;\n}\n\n/// Like `adapter-service`, but only includes the imports, and not the\n/// exports (`http-incoming.handle`), so that it can be used by library components\n/// that don't have their own `main` function.\nworld adapter-service-imports {\n    include fastly:compute/service-imports@0.1.0;\n    include adapter-imports;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/filesystem/preopens.wit",
    "content": "package wasi:filesystem@0.2.6;\n\n@since(version = 0.2.0)\ninterface preopens {\n    @since(version = 0.2.0)\n    use types.{descriptor};\n\n    /// Return the set of preopened directories, and their paths.\n    @since(version = 0.2.0)\n    get-directories: func() -> list<tuple<descriptor, string>>;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/filesystem/types.wit",
    "content": "package wasi:filesystem@0.2.6;\n/// WASI filesystem is a filesystem API primarily intended to let users run WASI\n/// programs that access their files on their existing filesystems, without\n/// significant overhead.\n///\n/// It is intended to be roughly portable between Unix-family platforms and\n/// Windows, though it does not hide many of the major differences.\n///\n/// Paths are passed as interface-type `string`s, meaning they must consist of\n/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain\n/// paths which are not accessible by this API.\n///\n/// The directory separator in WASI is always the forward-slash (`/`).\n///\n/// All paths in WASI are relative paths, and are interpreted relative to a\n/// `descriptor` referring to a base directory. If a `path` argument to any WASI\n/// function starts with `/`, or if any step of resolving a `path`, including\n/// `..` and symbolic link steps, reaches a directory outside of the base\n/// directory, or reaches a symlink to an absolute or rooted path in the\n/// underlying filesystem, the function fails with `error-code::not-permitted`.\n///\n/// For more information about WASI path resolution and sandboxing, see\n/// [WASI filesystem path resolution].\n///\n/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md\n@since(version = 0.2.0)\ninterface types {\n    @since(version = 0.2.0)\n    use wasi:io/streams@0.2.6.{input-stream, output-stream, error};\n    @since(version = 0.2.0)\n    use wasi:clocks/wall-clock@0.2.6.{datetime};\n\n    /// File size or length of a region within a file.\n    @since(version = 0.2.0)\n    type filesize = u64;\n\n    /// The type of a filesystem object referenced by a descriptor.\n    ///\n    /// Note: This was called `filetype` in earlier versions of WASI.\n    @since(version = 0.2.0)\n    enum descriptor-type {\n        /// The type of the descriptor or file is unknown or is different from\n        /// any of the other types specified.\n        unknown,\n        /// The descriptor refers to a block device inode.\n        block-device,\n        /// The descriptor refers to a character device inode.\n        character-device,\n        /// The descriptor refers to a directory inode.\n        directory,\n        /// The descriptor refers to a named pipe.\n        fifo,\n        /// The file refers to a symbolic link inode.\n        symbolic-link,\n        /// The descriptor refers to a regular file inode.\n        regular-file,\n        /// The descriptor refers to a socket.\n        socket,\n    }\n\n    /// Descriptor flags.\n    ///\n    /// Note: This was called `fdflags` in earlier versions of WASI.\n    @since(version = 0.2.0)\n    flags descriptor-flags {\n        /// Read mode: Data can be read.\n        read,\n        /// Write mode: Data can be written to.\n        write,\n        /// Request that writes be performed according to synchronized I/O file\n        /// integrity completion. The data stored in the file and the file's\n        /// metadata are synchronized. This is similar to `O_SYNC` in POSIX.\n        ///\n        /// The precise semantics of this operation have not yet been defined for\n        /// WASI. At this time, it should be interpreted as a request, and not a\n        /// requirement.\n        file-integrity-sync,\n        /// Request that writes be performed according to synchronized I/O data\n        /// integrity completion. Only the data stored in the file is\n        /// synchronized. This is similar to `O_DSYNC` in POSIX.\n        ///\n        /// The precise semantics of this operation have not yet been defined for\n        /// WASI. At this time, it should be interpreted as a request, and not a\n        /// requirement.\n        data-integrity-sync,\n        /// Requests that reads be performed at the same level of integrity\n        /// requested for writes. This is similar to `O_RSYNC` in POSIX.\n        ///\n        /// The precise semantics of this operation have not yet been defined for\n        /// WASI. At this time, it should be interpreted as a request, and not a\n        /// requirement.\n        requested-write-sync,\n        /// Mutating directories mode: Directory contents may be mutated.\n        ///\n        /// When this flag is unset on a descriptor, operations using the\n        /// descriptor which would create, rename, delete, modify the data or\n        /// metadata of filesystem objects, or obtain another handle which\n        /// would permit any of those, shall fail with `error-code::read-only` if\n        /// they would otherwise succeed.\n        ///\n        /// This may only be set on directories.\n        mutate-directory,\n    }\n\n    /// File attributes.\n    ///\n    /// Note: This was called `filestat` in earlier versions of WASI.\n    @since(version = 0.2.0)\n    record descriptor-stat {\n        /// File type.\n        %type: descriptor-type,\n        /// Number of hard links to the file.\n        link-count: link-count,\n        /// For regular files, the file size in bytes. For symbolic links, the\n        /// length in bytes of the pathname contained in the symbolic link.\n        size: filesize,\n        /// Last data access timestamp.\n        ///\n        /// If the `option` is none, the platform doesn't maintain an access\n        /// timestamp for this file.\n        data-access-timestamp: option<datetime>,\n        /// Last data modification timestamp.\n        ///\n        /// If the `option` is none, the platform doesn't maintain a\n        /// modification timestamp for this file.\n        data-modification-timestamp: option<datetime>,\n        /// Last file status-change timestamp.\n        ///\n        /// If the `option` is none, the platform doesn't maintain a\n        /// status-change timestamp for this file.\n        status-change-timestamp: option<datetime>,\n    }\n\n    /// Flags determining the method of how paths are resolved.\n    @since(version = 0.2.0)\n    flags path-flags {\n        /// As long as the resolved path corresponds to a symbolic link, it is\n        /// expanded.\n        symlink-follow,\n    }\n\n    /// Open flags used by `open-at`.\n    @since(version = 0.2.0)\n    flags open-flags {\n        /// Create file if it does not exist, similar to `O_CREAT` in POSIX.\n        create,\n        /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX.\n        directory,\n        /// Fail if file already exists, similar to `O_EXCL` in POSIX.\n        exclusive,\n        /// Truncate file to size 0, similar to `O_TRUNC` in POSIX.\n        truncate,\n    }\n\n    /// Number of hard links to an inode.\n    @since(version = 0.2.0)\n    type link-count = u64;\n\n    /// When setting a timestamp, this gives the value to set it to.\n    @since(version = 0.2.0)\n    variant new-timestamp {\n        /// Leave the timestamp set to its previous value.\n        no-change,\n        /// Set the timestamp to the current time of the system clock associated\n        /// with the filesystem.\n        now,\n        /// Set the timestamp to the given value.\n        timestamp(datetime),\n    }\n\n    /// A directory entry.\n    record directory-entry {\n        /// The type of the file referred to by this directory entry.\n        %type: descriptor-type,\n\n        /// The name of the object.\n        name: string,\n    }\n\n    /// Error codes returned by functions, similar to `errno` in POSIX.\n    /// Not all of these error codes are returned by the functions provided by this\n    /// API; some are used in higher-level library layers, and others are provided\n    /// merely for alignment with POSIX.\n    enum error-code {\n        /// Permission denied, similar to `EACCES` in POSIX.\n        access,\n        /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX.\n        would-block,\n        /// Connection already in progress, similar to `EALREADY` in POSIX.\n        already,\n        /// Bad descriptor, similar to `EBADF` in POSIX.\n        bad-descriptor,\n        /// Device or resource busy, similar to `EBUSY` in POSIX.\n        busy,\n        /// Resource deadlock would occur, similar to `EDEADLK` in POSIX.\n        deadlock,\n        /// Storage quota exceeded, similar to `EDQUOT` in POSIX.\n        quota,\n        /// File exists, similar to `EEXIST` in POSIX.\n        exist,\n        /// File too large, similar to `EFBIG` in POSIX.\n        file-too-large,\n        /// Illegal byte sequence, similar to `EILSEQ` in POSIX.\n        illegal-byte-sequence,\n        /// Operation in progress, similar to `EINPROGRESS` in POSIX.\n        in-progress,\n        /// Interrupted function, similar to `EINTR` in POSIX.\n        interrupted,\n        /// Invalid argument, similar to `EINVAL` in POSIX.\n        invalid,\n        /// I/O error, similar to `EIO` in POSIX.\n        io,\n        /// Is a directory, similar to `EISDIR` in POSIX.\n        is-directory,\n        /// Too many levels of symbolic links, similar to `ELOOP` in POSIX.\n        loop,\n        /// Too many links, similar to `EMLINK` in POSIX.\n        too-many-links,\n        /// Message too large, similar to `EMSGSIZE` in POSIX.\n        message-size,\n        /// Filename too long, similar to `ENAMETOOLONG` in POSIX.\n        name-too-long,\n        /// No such device, similar to `ENODEV` in POSIX.\n        no-device,\n        /// No such file or directory, similar to `ENOENT` in POSIX.\n        no-entry,\n        /// No locks available, similar to `ENOLCK` in POSIX.\n        no-lock,\n        /// Not enough space, similar to `ENOMEM` in POSIX.\n        insufficient-memory,\n        /// No space left on device, similar to `ENOSPC` in POSIX.\n        insufficient-space,\n        /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX.\n        not-directory,\n        /// Directory not empty, similar to `ENOTEMPTY` in POSIX.\n        not-empty,\n        /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX.\n        not-recoverable,\n        /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX.\n        unsupported,\n        /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX.\n        no-tty,\n        /// No such device or address, similar to `ENXIO` in POSIX.\n        no-such-device,\n        /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX.\n        overflow,\n        /// Operation not permitted, similar to `EPERM` in POSIX.\n        not-permitted,\n        /// Broken pipe, similar to `EPIPE` in POSIX.\n        pipe,\n        /// Read-only file system, similar to `EROFS` in POSIX.\n        read-only,\n        /// Invalid seek, similar to `ESPIPE` in POSIX.\n        invalid-seek,\n        /// Text file busy, similar to `ETXTBSY` in POSIX.\n        text-file-busy,\n        /// Cross-device link, similar to `EXDEV` in POSIX.\n        cross-device,\n    }\n\n    /// File or memory access pattern advisory information.\n    @since(version = 0.2.0)\n    enum advice {\n        /// The application has no advice to give on its behavior with respect\n        /// to the specified data.\n        normal,\n        /// The application expects to access the specified data sequentially\n        /// from lower offsets to higher offsets.\n        sequential,\n        /// The application expects to access the specified data in a random\n        /// order.\n        random,\n        /// The application expects to access the specified data in the near\n        /// future.\n        will-need,\n        /// The application expects that it will not access the specified data\n        /// in the near future.\n        dont-need,\n        /// The application expects to access the specified data once and then\n        /// not reuse it thereafter.\n        no-reuse,\n    }\n\n    /// A 128-bit hash value, split into parts because wasm doesn't have a\n    /// 128-bit integer type.\n    @since(version = 0.2.0)\n    record metadata-hash-value {\n       /// 64 bits of a 128-bit hash value.\n       lower: u64,\n       /// Another 64 bits of a 128-bit hash value.\n       upper: u64,\n    }\n\n    /// A descriptor is a reference to a filesystem object, which may be a file,\n    /// directory, named pipe, special file, or other object on which filesystem\n    /// calls may be made.\n    @since(version = 0.2.0)\n    resource descriptor {\n        /// Return a stream for reading from a file, if available.\n        ///\n        /// May fail with an error-code describing why the file cannot be read.\n        ///\n        /// Multiple read, write, and append streams may be active on the same open\n        /// file and they do not interfere with each other.\n        ///\n        /// Note: This allows using `read-stream`, which is similar to `read` in POSIX.\n        @since(version = 0.2.0)\n        read-via-stream: func(\n            /// The offset within the file at which to start reading.\n            offset: filesize,\n        ) -> result<input-stream, error-code>;\n\n        /// Return a stream for writing to a file, if available.\n        ///\n        /// May fail with an error-code describing why the file cannot be written.\n        ///\n        /// Note: This allows using `write-stream`, which is similar to `write` in\n        /// POSIX.\n        @since(version = 0.2.0)\n        write-via-stream: func(\n            /// The offset within the file at which to start writing.\n            offset: filesize,\n        ) -> result<output-stream, error-code>;\n\n        /// Return a stream for appending to a file, if available.\n        ///\n        /// May fail with an error-code describing why the file cannot be appended.\n        ///\n        /// Note: This allows using `write-stream`, which is similar to `write` with\n        /// `O_APPEND` in POSIX.\n        @since(version = 0.2.0)\n        append-via-stream: func() -> result<output-stream, error-code>;\n\n        /// Provide file advisory information on a descriptor.\n        ///\n        /// This is similar to `posix_fadvise` in POSIX.\n        @since(version = 0.2.0)\n        advise: func(\n            /// The offset within the file to which the advisory applies.\n            offset: filesize,\n            /// The length of the region to which the advisory applies.\n            length: filesize,\n            /// The advice.\n            advice: advice\n        ) -> result<_, error-code>;\n\n        /// Synchronize the data of a file to disk.\n        ///\n        /// This function succeeds with no effect if the file descriptor is not\n        /// opened for writing.\n        ///\n        /// Note: This is similar to `fdatasync` in POSIX.\n        @since(version = 0.2.0)\n        sync-data: func() -> result<_, error-code>;\n\n        /// Get flags associated with a descriptor.\n        ///\n        /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX.\n        ///\n        /// Note: This returns the value that was the `fs_flags` value returned\n        /// from `fdstat_get` in earlier versions of WASI.\n        @since(version = 0.2.0)\n        get-flags: func() -> result<descriptor-flags, error-code>;\n\n        /// Get the dynamic type of a descriptor.\n        ///\n        /// Note: This returns the same value as the `type` field of the `fd-stat`\n        /// returned by `stat`, `stat-at` and similar.\n        ///\n        /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided\n        /// by `fstat` in POSIX.\n        ///\n        /// Note: This returns the value that was the `fs_filetype` value returned\n        /// from `fdstat_get` in earlier versions of WASI.\n        @since(version = 0.2.0)\n        get-type: func() -> result<descriptor-type, error-code>;\n\n        /// Adjust the size of an open file. If this increases the file's size, the\n        /// extra bytes are filled with zeros.\n        ///\n        /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI.\n        @since(version = 0.2.0)\n        set-size: func(size: filesize) -> result<_, error-code>;\n\n        /// Adjust the timestamps of an open file or directory.\n        ///\n        /// Note: This is similar to `futimens` in POSIX.\n        ///\n        /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI.\n        @since(version = 0.2.0)\n        set-times: func(\n            /// The desired values of the data access timestamp.\n            data-access-timestamp: new-timestamp,\n            /// The desired values of the data modification timestamp.\n            data-modification-timestamp: new-timestamp,\n        ) -> result<_, error-code>;\n\n        /// Read from a descriptor, without using and updating the descriptor's offset.\n        ///\n        /// This function returns a list of bytes containing the data that was\n        /// read, along with a bool which, when true, indicates that the end of the\n        /// file was reached. The returned list will contain up to `length` bytes; it\n        /// may return fewer than requested, if the end of the file is reached or\n        /// if the I/O operation is interrupted.\n        ///\n        /// In the future, this may change to return a `stream<u8, error-code>`.\n        ///\n        /// Note: This is similar to `pread` in POSIX.\n        @since(version = 0.2.0)\n        read: func(\n            /// The maximum number of bytes to read.\n            length: filesize,\n            /// The offset within the file at which to read.\n            offset: filesize,\n        ) -> result<tuple<list<u8>, bool>, error-code>;\n\n        /// Write to a descriptor, without using and updating the descriptor's offset.\n        ///\n        /// It is valid to write past the end of a file; the file is extended to the\n        /// extent of the write, with bytes between the previous end and the start of\n        /// the write set to zero.\n        ///\n        /// In the future, this may change to take a `stream<u8, error-code>`.\n        ///\n        /// Note: This is similar to `pwrite` in POSIX.\n        @since(version = 0.2.0)\n        write: func(\n            /// Data to write\n            buffer: list<u8>,\n            /// The offset within the file at which to write.\n            offset: filesize,\n        ) -> result<filesize, error-code>;\n\n        /// Read directory entries from a directory.\n        ///\n        /// On filesystems where directories contain entries referring to themselves\n        /// and their parents, often named `.` and `..` respectively, these entries\n        /// are omitted.\n        ///\n        /// This always returns a new stream which starts at the beginning of the\n        /// directory. Multiple streams may be active on the same directory, and they\n        /// do not interfere with each other.\n        @since(version = 0.2.0)\n        read-directory: func() -> result<directory-entry-stream, error-code>;\n\n        /// Synchronize the data and metadata of a file to disk.\n        ///\n        /// This function succeeds with no effect if the file descriptor is not\n        /// opened for writing.\n        ///\n        /// Note: This is similar to `fsync` in POSIX.\n        @since(version = 0.2.0)\n        sync: func() -> result<_, error-code>;\n\n        /// Create a directory.\n        ///\n        /// Note: This is similar to `mkdirat` in POSIX.\n        @since(version = 0.2.0)\n        create-directory-at: func(\n            /// The relative path at which to create the directory.\n            path: string,\n        ) -> result<_, error-code>;\n\n        /// Return the attributes of an open file or directory.\n        ///\n        /// Note: This is similar to `fstat` in POSIX, except that it does not return\n        /// device and inode information. For testing whether two descriptors refer to\n        /// the same underlying filesystem object, use `is-same-object`. To obtain\n        /// additional data that can be used do determine whether a file has been\n        /// modified, use `metadata-hash`.\n        ///\n        /// Note: This was called `fd_filestat_get` in earlier versions of WASI.\n        @since(version = 0.2.0)\n        stat: func() -> result<descriptor-stat, error-code>;\n\n        /// Return the attributes of a file or directory.\n        ///\n        /// Note: This is similar to `fstatat` in POSIX, except that it does not\n        /// return device and inode information. See the `stat` description for a\n        /// discussion of alternatives.\n        ///\n        /// Note: This was called `path_filestat_get` in earlier versions of WASI.\n        @since(version = 0.2.0)\n        stat-at: func(\n            /// Flags determining the method of how the path is resolved.\n            path-flags: path-flags,\n            /// The relative path of the file or directory to inspect.\n            path: string,\n        ) -> result<descriptor-stat, error-code>;\n\n        /// Adjust the timestamps of a file or directory.\n        ///\n        /// Note: This is similar to `utimensat` in POSIX.\n        ///\n        /// Note: This was called `path_filestat_set_times` in earlier versions of\n        /// WASI.\n        @since(version = 0.2.0)\n        set-times-at: func(\n            /// Flags determining the method of how the path is resolved.\n            path-flags: path-flags,\n            /// The relative path of the file or directory to operate on.\n            path: string,\n            /// The desired values of the data access timestamp.\n            data-access-timestamp: new-timestamp,\n            /// The desired values of the data modification timestamp.\n            data-modification-timestamp: new-timestamp,\n        ) -> result<_, error-code>;\n\n        /// Create a hard link.\n        ///\n        /// Fails with `error-code::no-entry` if the old path does not exist,\n        /// with `error-code::exist` if the new path already exists, and\n        /// `error-code::not-permitted` if the old path is not a file.\n        ///\n        /// Note: This is similar to `linkat` in POSIX.\n        @since(version = 0.2.0)\n        link-at: func(\n            /// Flags determining the method of how the path is resolved.\n            old-path-flags: path-flags,\n            /// The relative source path from which to link.\n            old-path: string,\n            /// The base directory for `new-path`.\n            new-descriptor: borrow<descriptor>,\n            /// The relative destination path at which to create the hard link.\n            new-path: string,\n        ) -> result<_, error-code>;\n\n        /// Open a file or directory.\n        ///\n        /// If `flags` contains `descriptor-flags::mutate-directory`, and the base\n        /// descriptor doesn't have `descriptor-flags::mutate-directory` set,\n        /// `open-at` fails with `error-code::read-only`.\n        ///\n        /// If `flags` contains `write` or `mutate-directory`, or `open-flags`\n        /// contains `truncate` or `create`, and the base descriptor doesn't have\n        /// `descriptor-flags::mutate-directory` set, `open-at` fails with\n        /// `error-code::read-only`.\n        ///\n        /// Note: This is similar to `openat` in POSIX.\n        @since(version = 0.2.0)\n        open-at: func(\n            /// Flags determining the method of how the path is resolved.\n            path-flags: path-flags,\n            /// The relative path of the object to open.\n            path: string,\n            /// The method by which to open the file.\n            open-flags: open-flags,\n            /// Flags to use for the resulting descriptor.\n            %flags: descriptor-flags,\n        ) -> result<descriptor, error-code>;\n\n        /// Read the contents of a symbolic link.\n        ///\n        /// If the contents contain an absolute or rooted path in the underlying\n        /// filesystem, this function fails with `error-code::not-permitted`.\n        ///\n        /// Note: This is similar to `readlinkat` in POSIX.\n        @since(version = 0.2.0)\n        readlink-at: func(\n            /// The relative path of the symbolic link from which to read.\n            path: string,\n        ) -> result<string, error-code>;\n\n        /// Remove a directory.\n        ///\n        /// Return `error-code::not-empty` if the directory is not empty.\n        ///\n        /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.\n        @since(version = 0.2.0)\n        remove-directory-at: func(\n            /// The relative path to a directory to remove.\n            path: string,\n        ) -> result<_, error-code>;\n\n        /// Rename a filesystem object.\n        ///\n        /// Note: This is similar to `renameat` in POSIX.\n        @since(version = 0.2.0)\n        rename-at: func(\n            /// The relative source path of the file or directory to rename.\n            old-path: string,\n            /// The base directory for `new-path`.\n            new-descriptor: borrow<descriptor>,\n            /// The relative destination path to which to rename the file or directory.\n            new-path: string,\n        ) -> result<_, error-code>;\n\n        /// Create a symbolic link (also known as a \"symlink\").\n        ///\n        /// If `old-path` starts with `/`, the function fails with\n        /// `error-code::not-permitted`.\n        ///\n        /// Note: This is similar to `symlinkat` in POSIX.\n        @since(version = 0.2.0)\n        symlink-at: func(\n            /// The contents of the symbolic link.\n            old-path: string,\n            /// The relative destination path at which to create the symbolic link.\n            new-path: string,\n        ) -> result<_, error-code>;\n\n        /// Unlink a filesystem object that is not a directory.\n        ///\n        /// Return `error-code::is-directory` if the path refers to a directory.\n        /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX.\n        @since(version = 0.2.0)\n        unlink-file-at: func(\n            /// The relative path to a file to unlink.\n            path: string,\n        ) -> result<_, error-code>;\n\n        /// Test whether two descriptors refer to the same filesystem object.\n        ///\n        /// In POSIX, this corresponds to testing whether the two descriptors have the\n        /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers.\n        /// wasi-filesystem does not expose device and inode numbers, so this function\n        /// may be used instead.\n        @since(version = 0.2.0)\n        is-same-object: func(other: borrow<descriptor>) -> bool;\n\n        /// Return a hash of the metadata associated with a filesystem object referred\n        /// to by a descriptor.\n        ///\n        /// This returns a hash of the last-modification timestamp and file size, and\n        /// may also include the inode number, device number, birth timestamp, and\n        /// other metadata fields that may change when the file is modified or\n        /// replaced. It may also include a secret value chosen by the\n        /// implementation and not otherwise exposed.\n        ///\n        /// Implementations are encouraged to provide the following properties:\n        ///\n        ///  - If the file is not modified or replaced, the computed hash value should\n        ///    usually not change.\n        ///  - If the object is modified or replaced, the computed hash value should\n        ///    usually change.\n        ///  - The inputs to the hash should not be easily computable from the\n        ///    computed hash.\n        ///\n        /// However, none of these is required.\n        @since(version = 0.2.0)\n        metadata-hash: func() -> result<metadata-hash-value, error-code>;\n\n        /// Return a hash of the metadata associated with a filesystem object referred\n        /// to by a directory descriptor and a relative path.\n        ///\n        /// This performs the same hash computation as `metadata-hash`.\n        @since(version = 0.2.0)\n        metadata-hash-at: func(\n            /// Flags determining the method of how the path is resolved.\n            path-flags: path-flags,\n            /// The relative path of the file or directory to inspect.\n            path: string,\n        ) -> result<metadata-hash-value, error-code>;\n    }\n\n    /// A stream of directory entries.\n    @since(version = 0.2.0)\n    resource directory-entry-stream {\n        /// Read a single directory entry from a `directory-entry-stream`.\n        @since(version = 0.2.0)\n        read-directory-entry: func() -> result<option<directory-entry>, error-code>;\n    }\n\n    /// Attempts to extract a filesystem-related `error-code` from the stream\n    /// `error` provided.\n    ///\n    /// Stream operations which return `stream-error::last-operation-failed`\n    /// have a payload with more information about the operation that failed.\n    /// This payload can be passed through to this function to see if there's\n    /// filesystem-related information about the error to return.\n    ///\n    /// Note that this function is fallible because not all stream-related\n    /// errors are filesystem-related errors.\n    @since(version = 0.2.0)\n    filesystem-error-code: func(err: borrow<error>) -> option<error-code>;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/filesystem/world.wit",
    "content": "package wasi:filesystem@0.2.6;\n\n@since(version = 0.2.0)\nworld imports {\n    @since(version = 0.2.0)\n    import types;\n    @since(version = 0.2.0)\n    import preopens;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/io/error.wit",
    "content": "package wasi:io@0.2.6;\n\n@since(version = 0.2.0)\ninterface error {\n    /// A resource which represents some error information.\n    ///\n    /// The only method provided by this resource is `to-debug-string`,\n    /// which provides some human-readable information about the error.\n    ///\n    /// In the `wasi:io` package, this resource is returned through the\n    /// `wasi:io/streams/stream-error` type.\n    ///\n    /// To provide more specific error information, other interfaces may\n    /// offer functions to \"downcast\" this error into more specific types. For example,\n    /// errors returned from streams derived from filesystem types can be described using\n    /// the filesystem's own error-code type. This is done using the function\n    /// `wasi:filesystem/types/filesystem-error-code`, which takes a `borrow<error>`\n    /// parameter and returns an `option<wasi:filesystem/types/error-code>`.\n    ///\n    /// The set of functions which can \"downcast\" an `error` into a more\n    /// concrete type is open.\n    @since(version = 0.2.0)\n    resource error {\n        /// Returns a string that is suitable to assist humans in debugging\n        /// this error.\n        ///\n        /// WARNING: The returned string should not be consumed mechanically!\n        /// It may change across platforms, hosts, or other implementation\n        /// details. Parsing this string is a major platform-compatibility\n        /// hazard.\n        @since(version = 0.2.0)\n        to-debug-string: func() -> string;\n    }\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/io/poll.wit",
    "content": "package wasi:io@0.2.6;\n\n/// A poll API intended to let users wait for I/O events on multiple handles\n/// at once.\n@since(version = 0.2.0)\ninterface poll {\n    /// `pollable` represents a single I/O event which may be ready, or not.\n    @since(version = 0.2.0)\n    resource pollable {\n\n        /// Return the readiness of a pollable. This function never blocks.\n        ///\n        /// Returns `true` when the pollable is ready, and `false` otherwise.\n        @since(version = 0.2.0)\n        ready: func() -> bool;\n\n        /// `block` returns immediately if the pollable is ready, and otherwise\n        /// blocks until ready.\n        ///\n        /// This function is equivalent to calling `poll.poll` on a list\n        /// containing only this pollable.\n        @since(version = 0.2.0)\n        block: func();\n    }\n\n    /// Poll for completion on a set of pollables.\n    ///\n    /// This function takes a list of pollables, which identify I/O sources of\n    /// interest, and waits until one or more of the events is ready for I/O.\n    ///\n    /// The result `list<u32>` contains one or more indices of handles in the\n    /// argument list that is ready for I/O.\n    ///\n    /// This function traps if either:\n    /// - the list is empty, or:\n    /// - the list contains more elements than can be indexed with a `u32` value.\n    ///\n    /// A timeout can be implemented by adding a pollable from the\n    /// wasi-clocks API to the list.\n    ///\n    /// This function does not return a `result`; polling in itself does not\n    /// do any I/O so it doesn't fail. If any of the I/O sources identified by\n    /// the pollables has an error, it is indicated by marking the source as\n    /// being ready for I/O.\n    @since(version = 0.2.0)\n    poll: func(in: list<borrow<pollable>>) -> list<u32>;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/io/streams.wit",
    "content": "package wasi:io@0.2.6;\n\n/// WASI I/O is an I/O abstraction API which is currently focused on providing\n/// stream types.\n///\n/// In the future, the component model is expected to add built-in stream types;\n/// when it does, they are expected to subsume this API.\n@since(version = 0.2.0)\ninterface streams {\n    @since(version = 0.2.0)\n    use error.{error};\n    @since(version = 0.2.0)\n    use poll.{pollable};\n\n    /// An error for input-stream and output-stream operations.\n    @since(version = 0.2.0)\n    variant stream-error {\n        /// The last operation (a write or flush) failed before completion.\n        ///\n        /// More information is available in the `error` payload.\n        ///\n        /// After this, the stream will be closed. All future operations return\n        /// `stream-error::closed`.\n        last-operation-failed(error),\n        /// The stream is closed: no more input will be accepted by the\n        /// stream. A closed output-stream will return this error on all\n        /// future operations.\n        closed\n    }\n\n    /// An input bytestream.\n    ///\n    /// `input-stream`s are *non-blocking* to the extent practical on underlying\n    /// platforms. I/O operations always return promptly; if fewer bytes are\n    /// promptly available than requested, they return the number of bytes promptly\n    /// available, which could even be zero. To wait for data to be available,\n    /// use the `subscribe` function to obtain a `pollable` which can be polled\n    /// for using `wasi:io/poll`.\n    @since(version = 0.2.0)\n    resource input-stream {\n        /// Perform a non-blocking read from the stream.\n        ///\n        /// When the source of a `read` is binary data, the bytes from the source\n        /// are returned verbatim. When the source of a `read` is known to the\n        /// implementation to be text, bytes containing the UTF-8 encoding of the\n        /// text are returned.\n        ///\n        /// This function returns a list of bytes containing the read data,\n        /// when successful. The returned list will contain up to `len` bytes;\n        /// it may return fewer than requested, but not more. The list is\n        /// empty when no bytes are available for reading at this time. The\n        /// pollable given by `subscribe` will be ready when more bytes are\n        /// available.\n        ///\n        /// This function fails with a `stream-error` when the operation\n        /// encounters an error, giving `last-operation-failed`, or when the\n        /// stream is closed, giving `closed`.\n        ///\n        /// When the caller gives a `len` of 0, it represents a request to\n        /// read 0 bytes. If the stream is still open, this call should\n        /// succeed and return an empty list, or otherwise fail with `closed`.\n        ///\n        /// The `len` parameter is a `u64`, which could represent a list of u8 which\n        /// is not possible to allocate in wasm32, or not desirable to allocate as\n        /// as a return value by the callee. The callee may return a list of bytes\n        /// less than `len` in size while more bytes are available for reading.\n        @since(version = 0.2.0)\n        read: func(\n            /// The maximum number of bytes to read\n            len: u64\n        ) -> result<list<u8>, stream-error>;\n\n        /// Read bytes from a stream, after blocking until at least one byte can\n        /// be read. Except for blocking, behavior is identical to `read`.\n        @since(version = 0.2.0)\n        blocking-read: func(\n            /// The maximum number of bytes to read\n            len: u64\n        ) -> result<list<u8>, stream-error>;\n\n        /// Skip bytes from a stream. Returns number of bytes skipped.\n        ///\n        /// Behaves identical to `read`, except instead of returning a list\n        /// of bytes, returns the number of bytes consumed from the stream.\n        @since(version = 0.2.0)\n        skip: func(\n            /// The maximum number of bytes to skip.\n            len: u64,\n        ) -> result<u64, stream-error>;\n\n        /// Skip bytes from a stream, after blocking until at least one byte\n        /// can be skipped. Except for blocking behavior, identical to `skip`.\n        @since(version = 0.2.0)\n        blocking-skip: func(\n            /// The maximum number of bytes to skip.\n            len: u64,\n        ) -> result<u64, stream-error>;\n\n        /// Create a `pollable` which will resolve once either the specified stream\n        /// has bytes available to read or the other end of the stream has been\n        /// closed.\n        /// The created `pollable` is a child resource of the `input-stream`.\n        /// Implementations may trap if the `input-stream` is dropped before\n        /// all derived `pollable`s created with this function are dropped.\n        @since(version = 0.2.0)\n        subscribe: func() -> pollable;\n    }\n\n\n    /// An output bytestream.\n    ///\n    /// `output-stream`s are *non-blocking* to the extent practical on\n    /// underlying platforms. Except where specified otherwise, I/O operations also\n    /// always return promptly, after the number of bytes that can be written\n    /// promptly, which could even be zero. To wait for the stream to be ready to\n    /// accept data, the `subscribe` function to obtain a `pollable` which can be\n    /// polled for using `wasi:io/poll`.\n    ///\n    /// Dropping an `output-stream` while there's still an active write in\n    /// progress may result in the data being lost. Before dropping the stream,\n    /// be sure to fully flush your writes.\n    @since(version = 0.2.0)\n    resource output-stream {\n        /// Check readiness for writing. This function never blocks.\n        ///\n        /// Returns the number of bytes permitted for the next call to `write`,\n        /// or an error. Calling `write` with more bytes than this function has\n        /// permitted will trap.\n        ///\n        /// When this function returns 0 bytes, the `subscribe` pollable will\n        /// become ready when this function will report at least 1 byte, or an\n        /// error.\n        @since(version = 0.2.0)\n        check-write: func() -> result<u64, stream-error>;\n\n        /// Perform a write. This function never blocks.\n        ///\n        /// When the destination of a `write` is binary data, the bytes from\n        /// `contents` are written verbatim. When the destination of a `write` is\n        /// known to the implementation to be text, the bytes of `contents` are\n        /// transcoded from UTF-8 into the encoding of the destination and then\n        /// written.\n        ///\n        /// Precondition: check-write gave permit of Ok(n) and contents has a\n        /// length of less than or equal to n. Otherwise, this function will trap.\n        ///\n        /// returns Err(closed) without writing if the stream has closed since\n        /// the last call to check-write provided a permit.\n        @since(version = 0.2.0)\n        write: func(\n            contents: list<u8>\n        ) -> result<_, stream-error>;\n\n        /// Perform a write of up to 4096 bytes, and then flush the stream. Block\n        /// until all of these operations are complete, or an error occurs.\n        ///\n        /// This is a convenience wrapper around the use of `check-write`,\n        /// `subscribe`, `write`, and `flush`, and is implemented with the\n        /// following pseudo-code:\n        ///\n        /// ```text\n        /// let pollable = this.subscribe();\n        /// while !contents.is_empty() {\n        ///     // Wait for the stream to become writable\n        ///     pollable.block();\n        ///     let Ok(n) = this.check-write(); // eliding error handling\n        ///     let len = min(n, contents.len());\n        ///     let (chunk, rest) = contents.split_at(len);\n        ///     this.write(chunk  );            // eliding error handling\n        ///     contents = rest;\n        /// }\n        /// this.flush();\n        /// // Wait for completion of `flush`\n        /// pollable.block();\n        /// // Check for any errors that arose during `flush`\n        /// let _ = this.check-write();         // eliding error handling\n        /// ```\n        @since(version = 0.2.0)\n        blocking-write-and-flush: func(\n            contents: list<u8>\n        ) -> result<_, stream-error>;\n\n        /// Request to flush buffered output. This function never blocks.\n        ///\n        /// This tells the output-stream that the caller intends any buffered\n        /// output to be flushed. the output which is expected to be flushed\n        /// is all that has been passed to `write` prior to this call.\n        ///\n        /// Upon calling this function, the `output-stream` will not accept any\n        /// writes (`check-write` will return `ok(0)`) until the flush has\n        /// completed. The `subscribe` pollable will become ready when the\n        /// flush has completed and the stream can accept more writes.\n        @since(version = 0.2.0)\n        flush: func() -> result<_, stream-error>;\n\n        /// Request to flush buffered output, and block until flush completes\n        /// and stream is ready for writing again.\n        @since(version = 0.2.0)\n        blocking-flush: func() -> result<_, stream-error>;\n\n        /// Create a `pollable` which will resolve once the output-stream\n        /// is ready for more writing, or an error has occurred. When this\n        /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an\n        /// error.\n        ///\n        /// If the stream is closed, this pollable is always ready immediately.\n        ///\n        /// The created `pollable` is a child resource of the `output-stream`.\n        /// Implementations may trap if the `output-stream` is dropped before\n        /// all derived `pollable`s created with this function are dropped.\n        @since(version = 0.2.0)\n        subscribe: func() -> pollable;\n\n        /// Write zeroes to a stream.\n        ///\n        /// This should be used precisely like `write` with the exact same\n        /// preconditions (must use check-write first), but instead of\n        /// passing a list of bytes, you simply pass the number of zero-bytes\n        /// that should be written.\n        @since(version = 0.2.0)\n        write-zeroes: func(\n            /// The number of zero-bytes to write\n            len: u64\n        ) -> result<_, stream-error>;\n\n        /// Perform a write of up to 4096 zeroes, and then flush the stream.\n        /// Block until all of these operations are complete, or an error\n        /// occurs.\n        ///\n        /// This is a convenience wrapper around the use of `check-write`,\n        /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with\n        /// the following pseudo-code:\n        ///\n        /// ```text\n        /// let pollable = this.subscribe();\n        /// while num_zeroes != 0 {\n        ///     // Wait for the stream to become writable\n        ///     pollable.block();\n        ///     let Ok(n) = this.check-write(); // eliding error handling\n        ///     let len = min(n, num_zeroes);\n        ///     this.write-zeroes(len);         // eliding error handling\n        ///     num_zeroes -= len;\n        /// }\n        /// this.flush();\n        /// // Wait for completion of `flush`\n        /// pollable.block();\n        /// // Check for any errors that arose during `flush`\n        /// let _ = this.check-write();         // eliding error handling\n        /// ```\n        @since(version = 0.2.0)\n        blocking-write-zeroes-and-flush: func(\n            /// The number of zero-bytes to write\n            len: u64\n        ) -> result<_, stream-error>;\n\n        /// Read from one stream and write to another.\n        ///\n        /// The behavior of splice is equivalent to:\n        /// 1. calling `check-write` on the `output-stream`\n        /// 2. calling `read` on the `input-stream` with the smaller of the\n        /// `check-write` permitted length and the `len` provided to `splice`\n        /// 3. calling `write` on the `output-stream` with that read data.\n        ///\n        /// Any error reported by the call to `check-write`, `read`, or\n        /// `write` ends the splice and reports that error.\n        ///\n        /// This function returns the number of bytes transferred; it may be less\n        /// than `len`.\n        @since(version = 0.2.0)\n        splice: func(\n            /// The stream to read from\n            src: borrow<input-stream>,\n            /// The number of bytes to splice\n            len: u64,\n        ) -> result<u64, stream-error>;\n\n        /// Read from one stream and write to another, with blocking.\n        ///\n        /// This is similar to `splice`, except that it blocks until the\n        /// `output-stream` is ready for writing, and the `input-stream`\n        /// is ready for reading, before performing the `splice`.\n        @since(version = 0.2.0)\n        blocking-splice: func(\n            /// The stream to read from\n            src: borrow<input-stream>,\n            /// The number of bytes to splice\n            len: u64,\n        ) -> result<u64, stream-error>;\n    }\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/io/world.wit",
    "content": "package wasi:io@0.2.6;\n\n@since(version = 0.2.0)\nworld imports {\n    @since(version = 0.2.0)\n    import streams;\n\n    @since(version = 0.2.0)\n    import poll;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/random/insecure-seed.wit",
    "content": "package wasi:random@0.2.6;\n/// The insecure-seed interface for seeding hash-map DoS resistance.\n///\n/// It is intended to be portable at least between Unix-family platforms and\n/// Windows.\n@since(version = 0.2.0)\ninterface insecure-seed {\n    /// Return a 128-bit value that may contain a pseudo-random value.\n    ///\n    /// The returned value is not required to be computed from a CSPRNG, and may\n    /// even be entirely deterministic. Host implementations are encouraged to\n    /// provide pseudo-random values to any program exposed to\n    /// attacker-controlled content, to enable DoS protection built into many\n    /// languages' hash-map implementations.\n    ///\n    /// This function is intended to only be called once, by a source language\n    /// to initialize Denial Of Service (DoS) protection in its hash-map\n    /// implementation.\n    ///\n    /// # Expected future evolution\n    ///\n    /// This will likely be changed to a value import, to prevent it from being\n    /// called multiple times and potentially used for purposes other than DoS\n    /// protection.\n    @since(version = 0.2.0)\n    insecure-seed: func() -> tuple<u64, u64>;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/random/insecure.wit",
    "content": "package wasi:random@0.2.6;\n/// The insecure interface for insecure pseudo-random numbers.\n///\n/// It is intended to be portable at least between Unix-family platforms and\n/// Windows.\n@since(version = 0.2.0)\ninterface insecure {\n    /// Return `len` insecure pseudo-random bytes.\n    ///\n    /// This function is not cryptographically secure. Do not use it for\n    /// anything related to security.\n    ///\n    /// There are no requirements on the values of the returned bytes, however\n    /// implementations are encouraged to return evenly distributed values with\n    /// a long period.\n    @since(version = 0.2.0)\n    get-insecure-random-bytes: func(len: u64) -> list<u8>;\n\n    /// Return an insecure pseudo-random `u64` value.\n    ///\n    /// This function returns the same type of pseudo-random data as\n    /// `get-insecure-random-bytes`, represented as a `u64`.\n    @since(version = 0.2.0)\n    get-insecure-random-u64: func() -> u64;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/random/random.wit",
    "content": "package wasi:random@0.2.6;\n/// WASI Random is a random data API.\n///\n/// It is intended to be portable at least between Unix-family platforms and\n/// Windows.\n@since(version = 0.2.0)\ninterface random {\n    /// Return `len` cryptographically-secure random or pseudo-random bytes.\n    ///\n    /// This function must produce data at least as cryptographically secure and\n    /// fast as an adequately seeded cryptographically-secure pseudo-random\n    /// number generator (CSPRNG). It must not block, from the perspective of\n    /// the calling program, under any circumstances, including on the first\n    /// request and on requests for numbers of bytes. The returned data must\n    /// always be unpredictable.\n    ///\n    /// This function must always return fresh data. Deterministic environments\n    /// must omit this function, rather than implementing it with deterministic\n    /// data.\n    @since(version = 0.2.0)\n    get-random-bytes: func(len: u64) -> list<u8>;\n\n    /// Return a cryptographically-secure random or pseudo-random `u64` value.\n    ///\n    /// This function returns the same type of data as `get-random-bytes`,\n    /// represented as a `u64`.\n    @since(version = 0.2.0)\n    get-random-u64: func() -> u64;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/random/world.wit",
    "content": "package wasi:random@0.2.6;\n\n@since(version = 0.2.0)\nworld imports {\n    @since(version = 0.2.0)\n    import random;\n\n    @since(version = 0.2.0)\n    import insecure;\n\n    @since(version = 0.2.0)\n    import insecure-seed;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/sockets/instance-network.wit",
    "content": "\n/// This interface provides a value-export of the default network handle..\n@since(version = 0.2.0)\ninterface instance-network {\n    @since(version = 0.2.0)\n    use network.{network};\n\n    /// Get a handle to the default network.\n    @since(version = 0.2.0)\n    instance-network: func() -> network;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/sockets/ip-name-lookup.wit",
    "content": "@since(version = 0.2.0)\ninterface ip-name-lookup {\n    @since(version = 0.2.0)\n    use wasi:io/poll@0.2.6.{pollable};\n    @since(version = 0.2.0)\n    use network.{network, error-code, ip-address};\n\n    /// Resolve an internet host name to a list of IP addresses.\n    ///\n    /// Unicode domain names are automatically converted to ASCII using IDNA encoding.\n    /// If the input is an IP address string, the address is parsed and returned\n    /// as-is without making any external requests.\n    ///\n    /// See the wasi-socket proposal README.md for a comparison with getaddrinfo.\n    ///\n    /// This function never blocks. It either immediately fails or immediately\n    /// returns successfully with a `resolve-address-stream` that can be used\n    /// to (asynchronously) fetch the results.\n    ///\n    /// # Typical errors\n    /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address.\n    ///\n    /// # References:\n    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html>\n    /// - <https://man7.org/linux/man-pages/man3/getaddrinfo.3.html>\n    /// - <https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo>\n    /// - <https://man.freebsd.org/cgi/man.cgi?query=getaddrinfo&sektion=3>\n    @since(version = 0.2.0)\n    resolve-addresses: func(network: borrow<network>, name: string) -> result<resolve-address-stream, error-code>;\n\n    @since(version = 0.2.0)\n    resource resolve-address-stream {\n        /// Returns the next address from the resolver.\n        ///\n        /// This function should be called multiple times. On each call, it will\n        /// return the next address in connection order preference. If all\n        /// addresses have been exhausted, this function returns `none`.\n        ///\n        /// This function never returns IPv4-mapped IPv6 addresses.\n        ///\n        /// # Typical errors\n        /// - `name-unresolvable`:          Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY)\n        /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN)\n        /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL)\n        /// - `would-block`:                A result is not available yet. (EWOULDBLOCK, EAGAIN)\n        @since(version = 0.2.0)\n        resolve-next-address: func() -> result<option<ip-address>, error-code>;\n\n        /// Create a `pollable` which will resolve once the stream is ready for I/O.\n        ///\n        /// Note: this function is here for WASI 0.2 only.\n        /// It's planned to be removed when `future` is natively supported in Preview3.\n        @since(version = 0.2.0)\n        subscribe: func() -> pollable;\n    }\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/sockets/network.wit",
    "content": "@since(version = 0.2.0)\ninterface network {\n    @unstable(feature = network-error-code)\n    use wasi:io/error@0.2.6.{error};\n\n    /// An opaque resource that represents access to (a subset of) the network.\n    /// This enables context-based security for networking.\n    /// There is no need for this to map 1:1 to a physical network interface.\n    @since(version = 0.2.0)\n    resource network;\n\n    /// Error codes.\n    ///\n    /// In theory, every API can return any error code.\n    /// In practice, API's typically only return the errors documented per API\n    /// combined with a couple of errors that are always possible:\n    /// - `unknown`\n    /// - `access-denied`\n    /// - `not-supported`\n    /// - `out-of-memory`\n    /// - `concurrency-conflict`\n    ///\n    /// See each individual API for what the POSIX equivalents are. They sometimes differ per API.\n    @since(version = 0.2.0)\n    enum error-code {\n        /// Unknown error\n        unknown,\n\n        /// Access denied.\n        ///\n        /// POSIX equivalent: EACCES, EPERM\n        access-denied,\n\n        /// The operation is not supported.\n        ///\n        /// POSIX equivalent: EOPNOTSUPP\n        not-supported,\n\n        /// One of the arguments is invalid.\n        ///\n        /// POSIX equivalent: EINVAL\n        invalid-argument,\n\n        /// Not enough memory to complete the operation.\n        ///\n        /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY\n        out-of-memory,\n\n        /// The operation timed out before it could finish completely.\n        timeout,\n\n        /// This operation is incompatible with another asynchronous operation that is already in progress.\n        ///\n        /// POSIX equivalent: EALREADY\n        concurrency-conflict,\n\n        /// Trying to finish an asynchronous operation that:\n        /// - has not been started yet, or:\n        /// - was already finished by a previous `finish-*` call.\n        ///\n        /// Note: this is scheduled to be removed when `future`s are natively supported.\n        not-in-progress,\n\n        /// The operation has been aborted because it could not be completed immediately.\n        ///\n        /// Note: this is scheduled to be removed when `future`s are natively supported.\n        would-block,\n\n\n        /// The operation is not valid in the socket's current state.\n        invalid-state,\n\n        /// A new socket resource could not be created because of a system limit.\n        new-socket-limit,\n\n        /// A bind operation failed because the provided address is not an address that the `network` can bind to.\n        address-not-bindable,\n\n        /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available.\n        address-in-use,\n\n        /// The remote address is not reachable\n        remote-unreachable,\n\n\n        /// The TCP connection was forcefully rejected\n        connection-refused,\n\n        /// The TCP connection was reset.\n        connection-reset,\n\n        /// A TCP connection was aborted.\n        connection-aborted,\n\n\n        /// The size of a datagram sent to a UDP socket exceeded the maximum\n        /// supported size.\n        datagram-too-large,\n\n\n        /// Name does not exist or has no suitable associated IP addresses.\n        name-unresolvable,\n\n        /// A temporary failure in name resolution occurred.\n        temporary-resolver-failure,\n\n        /// A permanent failure in name resolution occurred.\n        permanent-resolver-failure,\n    }\n\n    /// Attempts to extract a network-related `error-code` from the stream\n    /// `error` provided.\n    ///\n    /// Stream operations which return `stream-error::last-operation-failed`\n    /// have a payload with more information about the operation that failed.\n    /// This payload can be passed through to this function to see if there's\n    /// network-related information about the error to return.\n    ///\n    /// Note that this function is fallible because not all stream-related\n    /// errors are network-related errors.\n    @unstable(feature = network-error-code)\n    network-error-code: func(err: borrow<error>) -> option<error-code>;\n\n    @since(version = 0.2.0)\n    enum ip-address-family {\n        /// Similar to `AF_INET` in POSIX.\n        ipv4,\n\n        /// Similar to `AF_INET6` in POSIX.\n        ipv6,\n    }\n\n    @since(version = 0.2.0)\n    type ipv4-address = tuple<u8, u8, u8, u8>;\n    @since(version = 0.2.0)\n    type ipv6-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>;\n\n    @since(version = 0.2.0)\n    variant ip-address {\n        ipv4(ipv4-address),\n        ipv6(ipv6-address),\n    }\n\n    @since(version = 0.2.0)\n    record ipv4-socket-address {\n        /// sin_port\n        port: u16,\n        /// sin_addr\n        address: ipv4-address,\n    }\n\n    @since(version = 0.2.0)\n    record ipv6-socket-address {\n        /// sin6_port\n        port: u16,\n        /// sin6_flowinfo\n        flow-info: u32,\n        /// sin6_addr\n        address: ipv6-address,\n        /// sin6_scope_id\n        scope-id: u32,\n    }\n\n    @since(version = 0.2.0)\n    variant ip-socket-address {\n        ipv4(ipv4-socket-address),\n        ipv6(ipv6-socket-address),\n    }\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/sockets/tcp-create-socket.wit",
    "content": "@since(version = 0.2.0)\ninterface tcp-create-socket {\n    @since(version = 0.2.0)\n    use network.{network, error-code, ip-address-family};\n    @since(version = 0.2.0)\n    use tcp.{tcp-socket};\n\n    /// Create a new TCP socket.\n    ///\n    /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX.\n    /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise.\n    ///\n    /// This function does not require a network capability handle. This is considered to be safe because\n    /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect`\n    /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world.\n    ///\n    /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations.\n    ///\n    /// # Typical errors\n    /// - `not-supported`:     The specified `address-family` is not supported. (EAFNOSUPPORT)\n    /// - `new-socket-limit`:  The new socket resource could not be created because of a system limit. (EMFILE, ENFILE)\n    ///\n    /// # References\n    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html>\n    /// - <https://man7.org/linux/man-pages/man2/socket.2.html>\n    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw>\n    /// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2>\n    @since(version = 0.2.0)\n    create-tcp-socket: func(address-family: ip-address-family) -> result<tcp-socket, error-code>;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/sockets/tcp.wit",
    "content": "@since(version = 0.2.0)\ninterface tcp {\n    @since(version = 0.2.0)\n    use wasi:io/streams@0.2.6.{input-stream, output-stream};\n    @since(version = 0.2.0)\n    use wasi:io/poll@0.2.6.{pollable};\n    @since(version = 0.2.0)\n    use wasi:clocks/monotonic-clock@0.2.6.{duration};\n    @since(version = 0.2.0)\n    use network.{network, error-code, ip-socket-address, ip-address-family};\n\n    @since(version = 0.2.0)\n    enum shutdown-type {\n        /// Similar to `SHUT_RD` in POSIX.\n        receive,\n\n        /// Similar to `SHUT_WR` in POSIX.\n        send,\n\n        /// Similar to `SHUT_RDWR` in POSIX.\n        both,\n    }\n    \n    /// A TCP socket resource.\n    ///\n    /// The socket can be in one of the following states:\n    /// - `unbound`\n    /// - `bind-in-progress`\n    /// - `bound` (See note below)\n    /// - `listen-in-progress`\n    /// - `listening`\n    /// - `connect-in-progress`\n    /// - `connected`\n    /// - `closed`\n    /// See <https://github.com/WebAssembly/wasi-sockets/blob/main/TcpSocketOperationalSemantics.md>\n    /// for more information.\n    ///\n    /// Note: Except where explicitly mentioned, whenever this documentation uses\n    /// the term \"bound\" without backticks it actually means: in the `bound` state *or higher*.\n    /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`)\n    ///\n    /// In addition to the general error codes documented on the\n    /// `network::error-code` type, TCP socket methods may always return\n    /// `error(invalid-state)` when in the `closed` state.\n    @since(version = 0.2.0)\n    resource tcp-socket {\n        /// Bind the socket to a specific network on the provided IP address and port.\n        ///\n        /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which\n        /// network interface(s) to bind to.\n        /// If the TCP/UDP port is zero, the socket will be bound to a random free port.\n        ///\n        /// Bind can be attempted multiple times on the same socket, even with\n        /// different arguments on each iteration. But never concurrently and\n        /// only as long as the previous bind failed. Once a bind succeeds, the\n        /// binding can't be changed anymore.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:          The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows)\n        /// - `invalid-argument`:          `local-address` is not a unicast address. (EINVAL)\n        /// - `invalid-argument`:          `local-address` is an IPv4-mapped IPv6 address. (EINVAL)\n        /// - `invalid-state`:             The socket is already bound. (EINVAL)\n        /// - `address-in-use`:            No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows)\n        /// - `address-in-use`:            Address is already in use. (EADDRINUSE)\n        /// - `address-not-bindable`:      `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL)\n        /// - `not-in-progress`:           A `bind` operation is not in progress.\n        /// - `would-block`:               Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)\n        /// \n        /// # Implementors note\n        /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT\n        /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR \n        /// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior\n        /// and SO_REUSEADDR performs something different entirely.\n        ///\n        /// Unlike in POSIX, in WASI the bind operation is async. This enables\n        /// interactive WASI hosts to inject permission prompts. Runtimes that\n        /// don't want to make use of this ability can simply call the native\n        /// `bind` as part of either `start-bind` or `finish-bind`.\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>\n        /// - <https://man7.org/linux/man-pages/man2/bind.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>\n        @since(version = 0.2.0)\n        start-bind: func(network: borrow<network>, local-address: ip-socket-address) -> result<_, error-code>;\n        @since(version = 0.2.0)\n        finish-bind: func() -> result<_, error-code>;\n\n        /// Connect to a remote endpoint.\n        ///\n        /// On success:\n        /// - the socket is transitioned into the `connected` state.\n        /// - a pair of streams is returned that can be used to read & write to the connection\n        ///\n        /// After a failed connection attempt, the socket will be in the `closed`\n        /// state and the only valid action left is to `drop` the socket. A single\n        /// socket can not be used to connect more than once.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:          The `remote-address` has the wrong address family. (EAFNOSUPPORT)\n        /// - `invalid-argument`:          `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS)\n        /// - `invalid-argument`:          `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos)\n        /// - `invalid-argument`:          The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows)\n        /// - `invalid-argument`:          The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows)\n        /// - `invalid-argument`:          The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`.\n        /// - `invalid-state`:             The socket is already in the `connected` state. (EISCONN)\n        /// - `invalid-state`:             The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows)\n        /// - `timeout`:                   Connection timed out. (ETIMEDOUT)\n        /// - `connection-refused`:        The connection was forcefully rejected. (ECONNREFUSED)\n        /// - `connection-reset`:          The connection was reset. (ECONNRESET)\n        /// - `connection-aborted`:        The connection was aborted. (ECONNABORTED)\n        /// - `remote-unreachable`:        The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)\n        /// - `address-in-use`:            Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD)\n        /// - `not-in-progress`:           A connect operation is not in progress.\n        /// - `would-block`:               Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)\n        ///\n        /// # Implementors note\n        /// The POSIX equivalent of `start-connect` is the regular `connect` syscall.\n        /// Because all WASI sockets are non-blocking this is expected to return\n        /// EINPROGRESS, which should be translated to `ok()` in WASI.\n        ///\n        /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT`\n        /// with a timeout of 0 on the socket descriptor. Followed by a check for\n        /// the `SO_ERROR` socket option, in case the poll signaled readiness.\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>\n        /// - <https://man7.org/linux/man-pages/man2/connect.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>\n        /// - <https://man.freebsd.org/cgi/man.cgi?connect>\n        @since(version = 0.2.0)\n        start-connect: func(network: borrow<network>, remote-address: ip-socket-address) -> result<_, error-code>;\n        @since(version = 0.2.0)\n        finish-connect: func() -> result<tuple<input-stream, output-stream>, error-code>;\n\n        /// Start listening for new connections.\n        ///\n        /// Transitions the socket into the `listening` state.\n        ///\n        /// Unlike POSIX, the socket must already be explicitly bound.\n        ///\n        /// # Typical errors\n        /// - `invalid-state`:             The socket is not bound to any local address. (EDESTADDRREQ)\n        /// - `invalid-state`:             The socket is already in the `connected` state. (EISCONN, EINVAL on BSD)\n        /// - `invalid-state`:             The socket is already in the `listening` state.\n        /// - `address-in-use`:            Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE)\n        /// - `not-in-progress`:           A listen operation is not in progress.\n        /// - `would-block`:               Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)\n        ///\n        /// # Implementors note\n        /// Unlike in POSIX, in WASI the listen operation is async. This enables\n        /// interactive WASI hosts to inject permission prompts. Runtimes that\n        /// don't want to make use of this ability can simply call the native\n        /// `listen` as part of either `start-listen` or `finish-listen`.\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html>\n        /// - <https://man7.org/linux/man-pages/man2/listen.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=listen&sektion=2>\n        @since(version = 0.2.0)\n        start-listen: func() -> result<_, error-code>;\n        @since(version = 0.2.0)\n        finish-listen: func() -> result<_, error-code>;\n\n        /// Accept a new client socket.\n        ///\n        /// The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket:\n        /// - `address-family`\n        /// - `keep-alive-enabled`\n        /// - `keep-alive-idle-time`\n        /// - `keep-alive-interval`\n        /// - `keep-alive-count`\n        /// - `hop-limit`\n        /// - `receive-buffer-size`\n        /// - `send-buffer-size`\n        ///\n        /// On success, this function returns the newly accepted client socket along with\n        /// a pair of streams that can be used to read & write to the connection.\n        ///\n        /// # Typical errors\n        /// - `invalid-state`:      Socket is not in the `listening` state. (EINVAL)\n        /// - `would-block`:        No pending connections at the moment. (EWOULDBLOCK, EAGAIN)\n        /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED)\n        /// - `new-socket-limit`:   The new socket resource could not be created because of a system limit. (EMFILE, ENFILE)\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html>\n        /// - <https://man7.org/linux/man-pages/man2/accept.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=accept&sektion=2>\n        @since(version = 0.2.0)\n        accept: func() -> result<tuple<tcp-socket, input-stream, output-stream>, error-code>;\n\n        /// Get the bound local address.\n        ///\n        /// POSIX mentions:\n        /// > If the socket has not been bound to a local name, the value\n        /// > stored in the object pointed to by `address` is unspecified.\n        ///\n        /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet.\n        ///\n        /// # Typical errors\n        /// - `invalid-state`: The socket is not bound to any local address.\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>\n        /// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>\n        /// - <https://man.freebsd.org/cgi/man.cgi?getsockname>\n        @since(version = 0.2.0)\n        local-address: func() -> result<ip-socket-address, error-code>;\n\n        /// Get the remote address.\n        ///\n        /// # Typical errors\n        /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN)\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>\n        /// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>\n        @since(version = 0.2.0)\n        remote-address: func() -> result<ip-socket-address, error-code>;\n\n        /// Whether the socket is in the `listening` state.\n        ///\n        /// Equivalent to the SO_ACCEPTCONN socket option.\n        @since(version = 0.2.0)\n        is-listening: func() -> bool;\n\n        /// Whether this is a IPv4 or IPv6 socket.\n        ///\n        /// Equivalent to the SO_DOMAIN socket option.\n        @since(version = 0.2.0)\n        address-family: func() -> ip-address-family;\n\n        /// Hints the desired listen queue size. Implementations are free to ignore this.\n        ///\n        /// If the provided value is 0, an `invalid-argument` error is returned.\n        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.\n        ///\n        /// # Typical errors\n        /// - `not-supported`:        (set) The platform does not support changing the backlog size after the initial listen.\n        /// - `invalid-argument`:     (set) The provided value was 0.\n        /// - `invalid-state`:        (set) The socket is in the `connect-in-progress` or `connected` state.\n        @since(version = 0.2.0)\n        set-listen-backlog-size: func(value: u64) -> result<_, error-code>;\n\n        /// Enables or disables keepalive.\n        ///\n        /// The keepalive behavior can be adjusted using:\n        /// - `keep-alive-idle-time`\n        /// - `keep-alive-interval`\n        /// - `keep-alive-count`\n        /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true.\n        ///\n        /// Equivalent to the SO_KEEPALIVE socket option.\n        @since(version = 0.2.0)\n        keep-alive-enabled: func() -> result<bool, error-code>;\n        @since(version = 0.2.0)\n        set-keep-alive-enabled: func(value: bool) -> result<_, error-code>;\n\n        /// Amount of time the connection has to be idle before TCP starts sending keepalive packets.\n        ///\n        /// If the provided value is 0, an `invalid-argument` error is returned.\n        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.\n        /// I.e. after setting a value, reading the same setting back may return a different value.\n        ///\n        /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS)\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:     (set) The provided value was 0.\n        @since(version = 0.2.0)\n        keep-alive-idle-time: func() -> result<duration, error-code>;\n        @since(version = 0.2.0)\n        set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>;\n\n        /// The time between keepalive packets.\n        ///\n        /// If the provided value is 0, an `invalid-argument` error is returned.\n        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.\n        /// I.e. after setting a value, reading the same setting back may return a different value.\n        ///\n        /// Equivalent to the TCP_KEEPINTVL socket option.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:     (set) The provided value was 0.\n        @since(version = 0.2.0)\n        keep-alive-interval: func() -> result<duration, error-code>;\n        @since(version = 0.2.0)\n        set-keep-alive-interval: func(value: duration) -> result<_, error-code>;\n\n        /// The maximum amount of keepalive packets TCP should send before aborting the connection.\n        ///\n        /// If the provided value is 0, an `invalid-argument` error is returned.\n        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.\n        /// I.e. after setting a value, reading the same setting back may return a different value.\n        ///\n        /// Equivalent to the TCP_KEEPCNT socket option.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:     (set) The provided value was 0.\n        @since(version = 0.2.0)\n        keep-alive-count: func() -> result<u32, error-code>;\n        @since(version = 0.2.0)\n        set-keep-alive-count: func(value: u32) -> result<_, error-code>;\n\n        /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.\n        ///\n        /// If the provided value is 0, an `invalid-argument` error is returned.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:     (set) The TTL value must be 1 or higher.\n        @since(version = 0.2.0)\n        hop-limit: func() -> result<u8, error-code>;\n        @since(version = 0.2.0)\n        set-hop-limit: func(value: u8) -> result<_, error-code>;\n\n        /// The kernel buffer space reserved for sends/receives on this socket.\n        ///\n        /// If the provided value is 0, an `invalid-argument` error is returned.\n        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.\n        /// I.e. after setting a value, reading the same setting back may return a different value.\n        ///\n        /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:     (set) The provided value was 0.\n        @since(version = 0.2.0)\n        receive-buffer-size: func() -> result<u64, error-code>;\n        @since(version = 0.2.0)\n        set-receive-buffer-size: func(value: u64) -> result<_, error-code>;\n        @since(version = 0.2.0)\n        send-buffer-size: func() -> result<u64, error-code>;\n        @since(version = 0.2.0)\n        set-send-buffer-size: func(value: u64) -> result<_, error-code>;\n\n        /// Create a `pollable` which can be used to poll for, or block on,\n        /// completion of any of the asynchronous operations of this socket.\n        ///\n        /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept`\n        /// return `error(would-block)`, this pollable can be used to wait for\n        /// their success or failure, after which the method can be retried.\n        ///\n        /// The pollable is not limited to the async operation that happens to be\n        /// in progress at the time of calling `subscribe` (if any). Theoretically,\n        /// `subscribe` only has to be called once per socket and can then be\n        /// (re)used for the remainder of the socket's lifetime.\n        ///\n        /// See <https://github.com/WebAssembly/wasi-sockets/blob/main/TcpSocketOperationalSemantics.md#pollable-readiness>\n        /// for more information.\n        ///\n        /// Note: this function is here for WASI 0.2 only.\n        /// It's planned to be removed when `future` is natively supported in Preview3.\n        @since(version = 0.2.0)\n        subscribe: func() -> pollable;\n\n        /// Initiate a graceful shutdown.\n        ///\n        /// - `receive`: The socket is not expecting to receive any data from\n        ///   the peer. The `input-stream` associated with this socket will be\n        ///   closed. Any data still in the receive queue at time of calling\n        ///   this method will be discarded.\n        /// - `send`: The socket has no more data to send to the peer. The `output-stream`\n        ///   associated with this socket will be closed and a FIN packet will be sent.\n        /// - `both`: Same effect as `receive` & `send` combined.\n        ///\n        /// This function is idempotent; shutting down a direction more than once\n        /// has no effect and returns `ok`.\n        ///\n        /// The shutdown function does not close (drop) the socket.\n        ///\n        /// # Typical errors\n        /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN)\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html>\n        /// - <https://man7.org/linux/man-pages/man2/shutdown.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=shutdown&sektion=2>\n        @since(version = 0.2.0)\n        shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>;\n    }\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/sockets/udp-create-socket.wit",
    "content": "@since(version = 0.2.0)\ninterface udp-create-socket {\n    @since(version = 0.2.0)\n    use network.{network, error-code, ip-address-family};\n    @since(version = 0.2.0)\n    use udp.{udp-socket};\n\n    /// Create a new UDP socket.\n    ///\n    /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX.\n    /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise.\n    ///\n    /// This function does not require a network capability handle. This is considered to be safe because\n    /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called,\n    /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world.\n    ///\n    /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations.\n    ///\n    /// # Typical errors\n    /// - `not-supported`:     The specified `address-family` is not supported. (EAFNOSUPPORT)\n    /// - `new-socket-limit`:  The new socket resource could not be created because of a system limit. (EMFILE, ENFILE)\n    ///\n    /// # References:\n    /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html>\n    /// - <https://man7.org/linux/man-pages/man2/socket.2.html>\n    /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw>\n    /// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2>\n    @since(version = 0.2.0)\n    create-udp-socket: func(address-family: ip-address-family) -> result<udp-socket, error-code>;\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/sockets/udp.wit",
    "content": "@since(version = 0.2.0)\ninterface udp {\n    @since(version = 0.2.0)\n    use wasi:io/poll@0.2.6.{pollable};\n    @since(version = 0.2.0)\n    use network.{network, error-code, ip-socket-address, ip-address-family};\n\n    /// A received datagram.\n    @since(version = 0.2.0)\n    record incoming-datagram {\n        /// The payload.\n        /// \n        /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes.\n        data: list<u8>,\n\n        /// The source address.\n        ///\n        /// This field is guaranteed to match the remote address the stream was initialized with, if any.\n        ///\n        /// Equivalent to the `src_addr` out parameter of `recvfrom`.\n        remote-address: ip-socket-address,\n    }\n\n    /// A datagram to be sent out.\n    @since(version = 0.2.0)\n    record outgoing-datagram {\n        /// The payload.\n        data: list<u8>,\n\n        /// The destination address.\n        ///\n        /// The requirements on this field depend on how the stream was initialized:\n        /// - with a remote address: this field must be None or match the stream's remote address exactly.\n        /// - without a remote address: this field is required.\n        ///\n        /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`.\n        remote-address: option<ip-socket-address>,\n    }\n\n    /// A UDP socket handle.\n    @since(version = 0.2.0)\n    resource udp-socket {\n        /// Bind the socket to a specific network on the provided IP address and port.\n        ///\n        /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which\n        /// network interface(s) to bind to.\n        /// If the port is zero, the socket will be bound to a random free port.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:          The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows)\n        /// - `invalid-state`:             The socket is already bound. (EINVAL)\n        /// - `address-in-use`:            No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows)\n        /// - `address-in-use`:            Address is already in use. (EADDRINUSE)\n        /// - `address-not-bindable`:      `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL)\n        /// - `not-in-progress`:           A `bind` operation is not in progress.\n        /// - `would-block`:               Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)\n        ///\n        /// # Implementors note\n        /// Unlike in POSIX, in WASI the bind operation is async. This enables\n        /// interactive WASI hosts to inject permission prompts. Runtimes that\n        /// don't want to make use of this ability can simply call the native\n        /// `bind` as part of either `start-bind` or `finish-bind`.\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>\n        /// - <https://man7.org/linux/man-pages/man2/bind.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>\n        @since(version = 0.2.0)\n        start-bind: func(network: borrow<network>, local-address: ip-socket-address) -> result<_, error-code>;\n        @since(version = 0.2.0)\n        finish-bind: func() -> result<_, error-code>;\n\n        /// Set up inbound & outbound communication channels, optionally to a specific peer.\n        ///\n        /// This function only changes the local socket configuration and does not generate any network traffic.\n        /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well,\n        /// based on the best network path to `remote-address`.\n        ///\n        /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer:\n        /// - `send` can only be used to send to this destination.\n        /// - `receive` will only return datagrams sent from the provided `remote-address`.\n        ///\n        /// This method may be called multiple times on the same socket to change its association, but\n        /// only the most recently returned pair of streams will be operational. Implementations may trap if\n        /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again.\n        /// \n        /// The POSIX equivalent in pseudo-code is:\n        /// ```text\n        /// if (was previously connected) {\n        /// \tconnect(s, AF_UNSPEC)\n        /// }\n        /// if (remote_address is Some) {\n        /// \tconnect(s, remote_address)\n        /// }\n        /// ```\n        ///\n        /// Unlike in POSIX, the socket must already be explicitly bound.\n        /// \n        /// # Typical errors\n        /// - `invalid-argument`:          The `remote-address` has the wrong address family. (EAFNOSUPPORT)\n        /// - `invalid-argument`:          The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL)\n        /// - `invalid-argument`:          The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL)\n        /// - `invalid-state`:             The socket is not bound.\n        /// - `address-in-use`:            Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD)\n        /// - `remote-unreachable`:        The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)\n        /// - `connection-refused`:        The connection was refused. (ECONNREFUSED)\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>\n        /// - <https://man7.org/linux/man-pages/man2/connect.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>\n        /// - <https://man.freebsd.org/cgi/man.cgi?connect>\n        @since(version = 0.2.0)\n        %stream: func(remote-address: option<ip-socket-address>) -> result<tuple<incoming-datagram-stream, outgoing-datagram-stream>, error-code>;\n\n        /// Get the current bound address.\n        ///\n        /// POSIX mentions:\n        /// > If the socket has not been bound to a local name, the value\n        /// > stored in the object pointed to by `address` is unspecified.\n        ///\n        /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet.\n        /// \n        /// # Typical errors\n        /// - `invalid-state`: The socket is not bound to any local address.\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>\n        /// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>\n        /// - <https://man.freebsd.org/cgi/man.cgi?getsockname>\n        @since(version = 0.2.0)\n        local-address: func() -> result<ip-socket-address, error-code>;\n\n        /// Get the address the socket is currently streaming to.\n        ///\n        /// # Typical errors\n        /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN)\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>\n        /// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>\n        @since(version = 0.2.0)\n        remote-address: func() -> result<ip-socket-address, error-code>;\n\n        /// Whether this is a IPv4 or IPv6 socket.\n        ///\n        /// Equivalent to the SO_DOMAIN socket option.\n        @since(version = 0.2.0)\n        address-family: func() -> ip-address-family;\n\n        /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.\n        ///\n        /// If the provided value is 0, an `invalid-argument` error is returned.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:     (set) The TTL value must be 1 or higher.\n        @since(version = 0.2.0)\n        unicast-hop-limit: func() -> result<u8, error-code>;\n        @since(version = 0.2.0)\n        set-unicast-hop-limit: func(value: u8) -> result<_, error-code>;\n\n        /// The kernel buffer space reserved for sends/receives on this socket.\n        ///\n        /// If the provided value is 0, an `invalid-argument` error is returned.\n        /// Any other value will never cause an error, but it might be silently clamped and/or rounded.\n        /// I.e. after setting a value, reading the same setting back may return a different value.\n        ///\n        /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:     (set) The provided value was 0.\n        @since(version = 0.2.0)\n        receive-buffer-size: func() -> result<u64, error-code>;\n        @since(version = 0.2.0)\n        set-receive-buffer-size: func(value: u64) -> result<_, error-code>;\n        @since(version = 0.2.0)\n        send-buffer-size: func() -> result<u64, error-code>;\n        @since(version = 0.2.0)\n        set-send-buffer-size: func(value: u64) -> result<_, error-code>;\n\n        /// Create a `pollable` which will resolve once the socket is ready for I/O.\n        ///\n        /// Note: this function is here for WASI 0.2 only.\n        /// It's planned to be removed when `future` is natively supported in Preview3.\n        @since(version = 0.2.0)\n        subscribe: func() -> pollable;\n    }\n\n    @since(version = 0.2.0)\n    resource incoming-datagram-stream {\n        /// Receive messages on the socket.\n        ///\n        /// This function attempts to receive up to `max-results` datagrams on the socket without blocking.\n        /// The returned list may contain fewer elements than requested, but never more.\n        ///\n        /// This function returns successfully with an empty list when either:\n        /// - `max-results` is 0, or:\n        /// - `max-results` is greater than 0, but no results are immediately available.\n        /// This function never returns `error(would-block)`.\n        ///\n        /// # Typical errors\n        /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)\n        /// - `connection-refused`: The connection was refused. (ECONNREFUSED)\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html>\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html>\n        /// - <https://man7.org/linux/man-pages/man2/recv.2.html>\n        /// - <https://man7.org/linux/man-pages/man2/recvmmsg.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom>\n        /// - <https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms741687(v=vs.85)>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=recv&sektion=2>\n        @since(version = 0.2.0)\n        receive: func(max-results: u64) -> result<list<incoming-datagram>, error-code>;\n\n        /// Create a `pollable` which will resolve once the stream is ready to receive again.\n        ///\n        /// Note: this function is here for WASI 0.2 only.\n        /// It's planned to be removed when `future` is natively supported in Preview3.\n        @since(version = 0.2.0)\n        subscribe: func() -> pollable;\n    }\n\n    @since(version = 0.2.0)\n    resource outgoing-datagram-stream {\n        /// Check readiness for sending. This function never blocks.\n        ///\n        /// Returns the number of datagrams permitted for the next call to `send`,\n        /// or an error. Calling `send` with more datagrams than this function has\n        /// permitted will trap.\n        ///\n        /// When this function returns ok(0), the `subscribe` pollable will\n        /// become ready when this function will report at least ok(1), or an\n        /// error.\n        /// \n        /// Never returns `would-block`.\n        check-send: func() -> result<u64, error-code>;\n\n        /// Send messages on the socket.\n        ///\n        /// This function attempts to send all provided `datagrams` on the socket without blocking and\n        /// returns how many messages were actually sent (or queued for sending). This function never\n        /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned.\n        ///\n        /// This function semantically behaves the same as iterating the `datagrams` list and sequentially\n        /// sending each individual datagram until either the end of the list has been reached or the first error occurred.\n        /// If at least one datagram has been sent successfully, this function never returns an error.\n        ///\n        /// If the input list is empty, the function returns `ok(0)`.\n        ///\n        /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if\n        /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted.\n        ///\n        /// # Typical errors\n        /// - `invalid-argument`:        The `remote-address` has the wrong address family. (EAFNOSUPPORT)\n        /// - `invalid-argument`:        The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL)\n        /// - `invalid-argument`:        The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL)\n        /// - `invalid-argument`:        The socket is in \"connected\" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN)\n        /// - `invalid-argument`:        The socket is not \"connected\" and no value for `remote-address` was provided. (EDESTADDRREQ)\n        /// - `remote-unreachable`:      The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)\n        /// - `connection-refused`:      The connection was refused. (ECONNREFUSED)\n        /// - `datagram-too-large`:      The datagram is too large. (EMSGSIZE)\n        ///\n        /// # References\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html>\n        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html>\n        /// - <https://man7.org/linux/man-pages/man2/send.2.html>\n        /// - <https://man7.org/linux/man-pages/man2/sendmmsg.2.html>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto>\n        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasendmsg>\n        /// - <https://man.freebsd.org/cgi/man.cgi?query=send&sektion=2>\n        @since(version = 0.2.0)\n        send: func(datagrams: list<outgoing-datagram>) -> result<u64, error-code>;\n        \n        /// Create a `pollable` which will resolve once the stream is ready to send again.\n        ///\n        /// Note: this function is here for WASI 0.2 only.\n        /// It's planned to be removed when `future` is natively supported in Preview3.\n        @since(version = 0.2.0)\n        subscribe: func() -> pollable;\n    }\n}\n"
  },
  {
    "path": "wasm_abi/wit/deps/sockets/world.wit",
    "content": "package wasi:sockets@0.2.6;\n\n@since(version = 0.2.0)\nworld imports {\n    @since(version = 0.2.0)\n    import instance-network;\n    @since(version = 0.2.0)\n    import network;\n    @since(version = 0.2.0)\n    import udp;\n    @since(version = 0.2.0)\n    import udp-create-socket;\n    @since(version = 0.2.0)\n    import tcp;\n    @since(version = 0.2.0)\n    import tcp-create-socket;\n    @since(version = 0.2.0)\n    import ip-name-lookup;\n}\n"
  },
  {
    "path": "wasm_abi/wit/viceroy.wit",
    "content": "package fastly:viceroy;\n"
  }
]