From a0826f72e84631d2e922cf93d40903e6aa32e0a9 Mon Sep 17 00:00:00 2001 From: Yuji Yokoo Date: Tue, 3 Nov 2026 00:41:19 +0900 Subject: [PATCH 1/4] feat(python): value rules for every producible type (#1392) Run vitaminc's shared value corpus against the SDK's value conversion. crates/core/testdata/value-conformance.json is a copy at vitaminc 06568ba93, the commit Go uses, because the published crates do not include it. Each vector of a kind the SDK writes must give its exact bytes and read back as its value. Every malformed vector is refused. A new vector of an unknown kind fails the test until it is handled. Test every InvalidValueError code through encrypt and through query, with no request. A float NaN, infinity or -0.0 is accepted: the engine and PostgreSQL both keep them, and a live test round-trips them. Add a cargo-fuzz crate, crates/core/fuzz, over the conversion both ways. An accepted value must frame, read back as itself, and match the bytes ValueFrame::of gives the plain Rust type. Arbitrary bytes read as a passthrough must never panic. The root task fuzz:python-values runs it, and fuzz.yml replays its seeds on a pull request. A live test round-trips one field of each of the 49 producible types through PostgreSQL. JsonSearch is out of scope: the json kind has no Python type yet, and the decorator refuses it. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/fuzz.yml | 10 +- .gitignore | 4 + AGENTS.md | 5 +- docs/fuzzing.md | 14 +- languages/python/crates/core/fuzz/Cargo.lock | 4762 +++++++++++++++++ languages/python/crates/core/fuzz/Cargo.toml | 38 + .../core/fuzz/corpus/value_frame/valid-date | Bin 0 -> 5 bytes .../fuzz/corpus/value_frame/valid-decimal | Bin 0 -> 17 bytes .../fuzz/corpus/value_frame/valid-float32 | Bin 0 -> 5 bytes .../core/fuzz/corpus/value_frame/valid-int64 | Bin 0 -> 9 bytes .../core/fuzz/corpus/value_frame/valid-text | Bin 0 -> 7 bytes .../fuzz/corpus/value_frame/valid-timestamp | Bin 0 -> 13 bytes .../core/fuzz/corpus/value_frame/valid-true | 1 + .../core/fuzz/fuzz_targets/value_frame.rs | 204 + .../python/crates/core/testdata/README.md | 25 + .../core/testdata/value-conformance.json | 767 +++ .../crates/core/tests/value_conformance.rs | 246 + languages/python/tests/test_encrypt_live.py | 95 +- languages/python/tests/test_values.py | 147 + mise.toml | 12 + .../__tests__/cargo-lock-freshness.test.mjs | 10 +- .../__tests__/cargo-publish-opt-out.test.mjs | 3 +- 22 files changed, 6330 insertions(+), 13 deletions(-) create mode 100644 languages/python/crates/core/fuzz/Cargo.lock create mode 100644 languages/python/crates/core/fuzz/Cargo.toml create mode 100644 languages/python/crates/core/fuzz/corpus/value_frame/valid-date create mode 100644 languages/python/crates/core/fuzz/corpus/value_frame/valid-decimal create mode 100644 languages/python/crates/core/fuzz/corpus/value_frame/valid-float32 create mode 100644 languages/python/crates/core/fuzz/corpus/value_frame/valid-int64 create mode 100644 languages/python/crates/core/fuzz/corpus/value_frame/valid-text create mode 100644 languages/python/crates/core/fuzz/corpus/value_frame/valid-timestamp create mode 100644 languages/python/crates/core/fuzz/corpus/value_frame/valid-true create mode 100644 languages/python/crates/core/fuzz/fuzz_targets/value_frame.rs create mode 100644 languages/python/crates/core/testdata/README.md create mode 100644 languages/python/crates/core/testdata/value-conformance.json create mode 100644 languages/python/crates/core/tests/value_conformance.rs create mode 100644 languages/python/tests/test_values.py diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 7b60acd54..953735b60 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -1,7 +1,8 @@ name: Fuzz (crates) # libFuzzer fuzzing for the stack crates' untrusted-input parsers (the -# detached `packages/*/fuzz/` crates and the `fuzz:*` mise tasks). Ported from +# detached `packages/*/fuzz/` crates and the `fuzz:*` mise tasks), and the +# Python SDK's value conversion (`languages/python/crates/core/fuzz/`). Ported from # cipherstash-suite's `fuzz.yml`, for the six targets in the imported set; the # suite keeps its three cts-common targets. Two jobs with different roles: # @@ -24,6 +25,10 @@ on: - packages/stack-auth/** - packages/stack-kms/** - packages/stack-encrypt/** + # The Python SDK's core crate and the manifest its dependencies come + # from: the fuzz:python-values target. + - languages/python/crates/core/** + - languages/python/Cargo.toml - Cargo.toml - Cargo.lock - mise.toml @@ -80,6 +85,7 @@ jobs: - { task: "fuzz:plan-build", slug: plan-build } - { task: "fuzz:json-wire", slug: json-wire } - { task: "fuzz:json-text", slug: json-text } + - { task: "fuzz:python-values", slug: python-values } steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: @@ -104,6 +110,7 @@ jobs: packages/stack-auth/fuzz packages/stack-kms/fuzz packages/stack-encrypt/fuzz + languages/python/crates/core/fuzz key: ${{ matrix.slug }} # `-runs=0` replays the committed seed corpus once and exits without @@ -130,6 +137,7 @@ jobs: - { task: "fuzz:plan-build", slug: plan-build, dir: packages/stack-encrypt, target: plan_build } - { task: "fuzz:json-wire", slug: json-wire, dir: packages/stack-encrypt, target: json_wire_decode } - { task: "fuzz:json-text", slug: json-text, dir: packages/stack-encrypt, target: json_text } + - { task: "fuzz:python-values", slug: python-values, dir: languages/python/crates/core, target: value_frame } steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: diff --git a/.gitignore b/.gitignore index 268d67b85..6c04d5b00 100644 --- a/.gitignore +++ b/.gitignore @@ -107,6 +107,10 @@ packages/*/fuzz/artifacts/ packages/*/fuzz/coverage/ packages/*/fuzz/corpus/*/* !packages/*/fuzz/corpus/*/valid-* +languages/python/crates/core/fuzz/artifacts/ +languages/python/crates/core/fuzz/coverage/ +languages/python/crates/core/fuzz/corpus/*/* +!languages/python/crates/core/fuzz/corpus/*/valid-* # cargo-mutants output (`mise run mutants:` writes it to the cwd). mutants.out/ diff --git a/AGENTS.md b/AGENTS.md index 4ddc42071..a496a3a77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,7 +93,7 @@ Every npm package except EQL lives under `languages/typescript/`: packages in `l - `packages/stack-auth`, `packages/stack-profile`, `packages/stack-kms`, `packages/stack-encrypt`, `packages/stack-encrypt-derive`, `packages/stack-guest-abi`: The Rust crates imported from `cipherstash/cipherstash-suite` with their history — `stack-auth` and `stack-profile` (published to crates.io, one version group), `stack-kms` (published to crates.io from 0.1.0, its own version group, re-exported by `stack-encrypt` as `stack_encrypt::kms`), `stack-encrypt` and `stack-encrypt-derive` (published to crates.io from 0.1.0, one version group; `eql-bindings`' `stack-encrypt` feature depends on them from the registry), and `stack-guest-abi` (`publish = false`). They are the members of the **root Cargo workspace**, with the three node binding crates below. See "Working on the Rust crates". - `languages/typescript/packages/auth`, `languages/typescript/packages/profile`, `languages/typescript/packages/stack-auth-wasm`: The node bindings of those crates. `@cipherstash/auth` (napi-rs v2) and its six `platforms/*` packages are published to npm from this repository by `release.yml` (`auth-artifacts`, `publish-auth`); a change to what it ships, the `stack-auth` crate included, needs an `@cipherstash/auth` changeset (`require-auth-npm-changeset.yml`). `@cipherstash/profile` and its platforms are private and never published; `@cipherstash/stack-auth-wasm` is private and builds the wasm that `@cipherstash/auth` ships. Their `build` and `test` scripts never invoke cargo; `build:native`, `build:debug` and `test:cargo` do. - `languages/golang`: The Go module — the SDK `encrypt` with its generated-code support `encrypt/gensupport` and the policy packages `encrypt/policy` and `encrypt/policy/protosource`; the credential package `auth`; the generator `stashgen` and its command `cmd/stashgen`; and `internal` (the shared guest plumbing and the `record` wire model). A wazero host with no cgo. Its two WASI guests (`encrypt/guest`, `auth/guest`) are detached Cargo workspaces built by `mise run wasm:guest:build` and `mise run wasm:auth-guest:build`; `mise run wasm:guest:build:deterministic` builds the seeded TEST build the hermetic Go tests use; the `.wasm` files they embed are gitignored. The stack-encrypt guest has a second build with the EQL types (`mise run wasm:guest:build:eql`, cargo feature `eql`, linking `eql-bindings` by path; `wasm:guest:build:eql:deterministic` is its test build), embedded by `encrypt/eql` and registered on import, so a program whose generated code names an EQL type runs it (ADR-0007, amended). `encrypt/eql`'s types and `Types` table are generated from the EQL catalog by `eql-codegen` (`eql_gen.go`, written by `mise run types:generate` in `packages/eql` and drift-gated by `types:check` and `cargo test -p eql-codegen`). Generated `*_stash.go` files are committed and CI fails when `go generate ./...` changes one. Releases are cut by hand with `.github/workflows/release-golang.yml` (`gh workflow run release-golang.yml -f version=X.Y.Z[-pre] --ref `): because the guests are gitignored on main and Go fetches a version as the files committed at its tag, the workflow builds them, commits them on a release-only commit no branch points at, tags it `languages/golang/vX.Y.Z` and pushes the tag alone, then attaches the EQL v4 SQL bundle to the GitHub release. The ref must carry the EQL v4 bundle, or the build stops at "Build the EQL SQL bundles". `scripts/go-release-version.mjs` refuses, before anything is built, every version Go would refuse (non-canonical semver, `+build` metadata, a v2+ major on a module path with no `/vN` suffix, a version shaped like a pseudo-version), a final version dispatched from anywhere but `main`, and a final version while `languages/golang/go.mod` pins a `github.com/cipherstash/vitaminc/…` module at a pseudo-version (vitaminc's Go bindings have no tags, and a TEMPORARY pin on an unreleased commit must be gone before the module is tagged); a pre-release may come from a branch and may carry such a pin. The build job also refuses a version the Go proxy already lists in `@v/list`, which catches a tag deleted after the proxy fetched it; it never asks the proxy for the version itself before the tag exists, because the proxy would cache "not found" for up to 30 minutes and `verify` would fail after the push. The build checks the `Cargo.lock` of the EQL workspace and both guest workspaces with `--locked` first, since none of those builds passes it. It runs in a job with no write token and hands the guests to a `publish` job that holds `contents: write` and runs only git and gh. `publish` fetches the commit with plain unauthenticated git rather than `actions/checkout`, whose `token` input defaults to the job's writable token, and refuses a release commit whose tree is not the one the build tested. `publish` takes the paths it commits from the workflow's fixed `GUESTS` list, never from the artifact, and refuses a `guests.sha256` that is not exactly one SHA-256 line for each of those three guests: the build job runs third-party code after writing that manifest, so a path read from it could put any file on the tag (`scripts/__tests__/go-release-artifact.test.mjs`). A version is immutable once the Go proxy has fetched it, so a tag is never moved or deleted: if `publish` or `verify` fails after the tag is pushed, use "Re-run failed jobs" on the same run, which keeps a tag that run pushed and creates or completes the release; a new dispatch of the same version is refused. Never tag a main commit by hand: `go get` would fetch a module with no guests. -- `languages/python`: The Python SDK, `stackencrypt`, a pyo3 extension over `stack-encrypt`, built by maturin. **The fourth Cargo workspace**, excluded from the root one. It has two crates. `crates/core` has no pyo3: the engine-side logic, linked and tested without a Python interpreter. `crates/engine` is the `_engine` cdylib, `abi3-py39`, one wheel per platform for Python 3.9 to 3.14. `crates/core` compiles the Go guest's EQL resolver, `languages/golang/encrypt/guest/src/targets.rs`, in place through `#[path]`. So both SDKs run one resolver, and a change to that file is a change to both. The `extension-module` feature is set only by maturin in `pyproject.toml`, never in a crate manifest, so `cargo test` links libpython. Its tasks live in its own `mise.toml` and run from that folder. `mise run build` builds the wheel into `.venv`. `mise run test` runs pytest. `mise run test:cargo` runs plain `cargo test --locked` for both crates, with libpython on the loader path. It does not use nextest, and its CI job sets no `MISE_ENV`. The nextest rule under "Working on the Rust crates" is for the root workspace, whose `stack-kms` tests set environment variables. No test in this workspace does, and nextest comes from `mise.test.toml`, which this workspace's CI does not load. The wheel's version is `crates/engine/Cargo.toml`'s. `stackencrypt.__engine__` is the commit of this checkout, recorded by `crates/core/build.rs`. Its `Cargo.lock` is seeded from the root one, so every crate it shares with the root workspace resolves to the version the root tests. After a root dependency moves, re-seed it the same way: copy the root `Cargo.lock` over it and run `cargo metadata --offline` there. That command prunes the copy to what the workspace uses. Until then, `tests-python.yml` fails on `--locked`. Nothing is published. CI is `tests-python.yml`. It runs the Rust checks and pytest on Python 3.9 and 3.14, where `MISE_PYTHON_VERSION` moves a leg off the local version. A `live` job runs `mise run test:live`: `encrypt`, `decrypt` and `query` against ZeroKMS with the repository's `CS_*` secrets, and the rows inserted into, searched in and read back from a `postgres:17` service with the EQL v4 bundle built from the tree. It skips on a fork's pull request, and the task fails on any skipped test. It then builds one abi3 wheel per FFI platform, uploads them as artifacts, and writes each build time to the job summary. `scripts/__tests__/python-ci.test.mjs` compares its path filter with the workspace's `Cargo.lock` and its wheel matrix with the protect-ffi platform packages. There is no Python lockfile yet. The package has no runtime dependency, so only the `test` extra could be locked: pytest, attrs, Pydantic, SQLAlchemy, and psycopg. The middle three are for the declaration tests, and psycopg is for the live tests. None has an upper bound, so the tests meet each new release when users do. `python/stackencrypt/_eql.py`, the EQL type markers, is generated from the EQL catalog by `eql-codegen py-eql`. To regenerate it, run `mise run types:generate` in `packages/eql`. Never edit it by hand: `tests-python.yml` runs its parity tests and fails on a hand edit. Design: the Python ADRs under `languages/python/docs/adr/` (ADR-0001, the declaration; ADR-0002, the call shape of `query`), the spec in #1341. +- `languages/python`: The Python SDK, `stackencrypt`, a pyo3 extension over `stack-encrypt`, built by maturin. **The fourth Cargo workspace**, excluded from the root one. It has two crates. `crates/core` has no pyo3: the engine-side logic, linked and tested without a Python interpreter. `crates/engine` is the `_engine` cdylib, `abi3-py39`, one wheel per platform for Python 3.9 to 3.14. `crates/core/fuzz` is a detached cargo-fuzz crate over the value conversion. The root `mise.toml`'s `fuzz:python-values` task runs it, from `fuzz.yml`, so it is the one Python task outside `languages/python/mise.toml`. `crates/core/testdata/value-conformance.json` is a copy of vitaminc's shared value corpus; its `README.md` records the commit it came from. `crates/core` compiles the Go guest's EQL resolver, `languages/golang/encrypt/guest/src/targets.rs`, in place through `#[path]`. So both SDKs run one resolver, and a change to that file is a change to both. The `extension-module` feature is set only by maturin in `pyproject.toml`, never in a crate manifest, so `cargo test` links libpython. Its tasks live in its own `mise.toml` and run from that folder. `mise run build` builds the wheel into `.venv`. `mise run test` runs pytest. `mise run test:cargo` runs plain `cargo test --locked` for both crates, with libpython on the loader path. It does not use nextest, and its CI job sets no `MISE_ENV`. The nextest rule under "Working on the Rust crates" is for the root workspace, whose `stack-kms` tests set environment variables. No test in this workspace does, and nextest comes from `mise.test.toml`, which this workspace's CI does not load. The wheel's version is `crates/engine/Cargo.toml`'s. `stackencrypt.__engine__` is the commit of this checkout, recorded by `crates/core/build.rs`. Its `Cargo.lock` is seeded from the root one, so every crate it shares with the root workspace resolves to the version the root tests. After a root dependency moves, re-seed it the same way: copy the root `Cargo.lock` over it and run `cargo metadata --offline` there. That command prunes the copy to what the workspace uses. Until then, `tests-python.yml` fails on `--locked`. Nothing is published. CI is `tests-python.yml`. It runs the Rust checks and pytest on Python 3.9 and 3.14, where `MISE_PYTHON_VERSION` moves a leg off the local version. A `live` job runs `mise run test:live`: `encrypt`, `decrypt` and `query` against ZeroKMS with the repository's `CS_*` secrets, and the rows inserted into, searched in and read back from a `postgres:17` service with the EQL v4 bundle built from the tree. It skips on a fork's pull request, and the task fails on any skipped test. It then builds one abi3 wheel per FFI platform, uploads them as artifacts, and writes each build time to the job summary. `scripts/__tests__/python-ci.test.mjs` compares its path filter with the workspace's `Cargo.lock` and its wheel matrix with the protect-ffi platform packages. There is no Python lockfile yet. The package has no runtime dependency, so only the `test` extra could be locked: pytest, attrs, Pydantic, SQLAlchemy, and psycopg. The middle three are for the declaration tests, and psycopg is for the live tests. None has an upper bound, so the tests meet each new release when users do. `python/stackencrypt/_eql.py`, the EQL type markers, is generated from the EQL catalog by `eql-codegen py-eql`. To regenerate it, run `mise run types:generate` in `packages/eql`. Never edit it by hand: `tests-python.yml` runs its parity tests and fails on a hand edit. Design: the Python ADRs under `languages/python/docs/adr/` (ADR-0001, the declaration; ADR-0002, the call shape of `query`), the spec in #1341. - `e2e/*`: Cross-package end-to-end tests (package managers, supply chain, Prisma example README) - `languages/typescript/examples/*`: Working apps (basic, prisma, supabase-worker) - `docs/plans/*`: Internal design plans. User-facing documentation lives at https://cipherstash.com/docs (not in this repo). @@ -720,7 +720,8 @@ exact pin would stop the suite sharing one `cts-common` with it. crates and the three node binding crates), protect-ffi's, EQL's and the Python SDK's (`languages/python`). The root `Cargo.toml` excludes the other three; protect-ffi and EQL set their own vitaminc version. The - three fuzz crates, the two Go guests and the EQL v4 block ORE timing + four fuzz crates (three under `packages/*/fuzz`, one under + `languages/python/crates/core/fuzz`), the two Go guests and the EQL v4 block ORE timing harness's ciphertext generator (`packages/eql/tests/timing/ore_block_v2/ctgen`) are detached workspaces too, each with its own `Cargo.lock`. None of them has a Dependabot entry. diff --git a/docs/fuzzing.md b/docs/fuzzing.md index d946e179d..0f4dd1441 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -29,6 +29,7 @@ Current targets: | `fuzz:check-record` | `stack-encrypt` | `check_record` | `dynamic::record::check_record` over an `Arbitrary`-derived plan and tree, against a model of the record rules (structure-aware) | | `fuzz:plan-build` | `stack-encrypt` | `plan_build` | `plan::FieldsBuilder::build` over `Arbitrary`-derived untrusted names (context, fields, identities); a built field's label must render and parse back, and passthrough names are never refused (structure-aware) | | `fuzz:json-text` | `stack-encrypt` | `json_text` | `json::parse_text`, the boundary parser for a language SDK's JSON document, against serde_json: it reads what serde_json reads to the same value, refuses beyond it only repeated members and out-of-range integers, and what it reads writes back to itself | +| `fuzz:python-values` | `stackencrypt-core` | `value_frame` | the Python SDK's value conversion, `values::write` and `values::read`, both ways: a value the write check accepts must frame, read back as itself, and match the bytes `ValueFrame::of` gives the plain Rust type; arbitrary bytes read as a passthrough must never panic (structure-aware) | | `fuzz:json-wire` | `stack-encrypt` | `json_wire_decode` | the JSON index's wire decoders (`JsonDocument`, `JsonNode`, `JsonSelectorSet`, `JsonLeafQuery` from JSON text; `JsonHeader::from_bytes`), over base64 tags and base85 ciphertexts; an accepted document or node must round-trip through its wire form | Each target is a few lines — `libfuzzer-sys` hands a `&str` (or `&[u8]` @@ -69,6 +70,7 @@ packages/stack-auth/fuzz/ packages/stack-kms/fuzz/ packages/stack-encrypt/fuzz/ … +languages/python/crates/core/fuzz/ # the Python SDK's core crate ``` The committed `corpus//` seeds are valid examples of each format. @@ -128,10 +130,14 @@ Two jobs with deliberately different roles: `restore-keys`) so coverage compounds, minimized with `cargo fuzz cmin` to stay small, and any crash reproducer is uploaded as an artifact. -The `pull_request` trigger is path-filtered to `packages/stack-auth/**`, -`packages/stack-kms/**`, `packages/stack-encrypt/**`, the root `Cargo.toml`, -`Cargo.lock` and `mise.toml`, and the workflow file, with `!**.md` / `!**.example` excludes last so docs-only changes are -skipped. +The `pull_request` trigger is path-filtered to these paths: + +- `packages/stack-auth/**`, `packages/stack-kms/**`, and `packages/stack-encrypt/**` +- `languages/python/crates/core/**` and `languages/python/Cargo.toml` +- the root `Cargo.toml`, `Cargo.lock`, and `mise.toml` +- the workflow file + +The `!**.md` and `!**.example` excludes come last, so a change to documents only is skipped. ## Adding a new target diff --git a/languages/python/crates/core/fuzz/Cargo.lock b/languages/python/crates/core/fuzz/Cargo.lock new file mode 100644 index 000000000..218fb01c5 --- /dev/null +++ b/languages/python/crates/core/fuzz/Cargo.lock @@ -0,0 +1,4762 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "arbitrary" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "async-compression" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "base85-simd" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbd16ef96137ca89cc38b0acb5bee36fada9707c044bc9c753f55c816bfa719" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", + "zeroize", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", + "zeroize", +] + +[[package]] +name = "block-modes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2211b0817f061502a8dd9f11a37e879e79763e3c698d2418cf824d8cb2f21e" + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cached" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9718806c4a2fe9e8a56fd736f97b340dd10ed1be8ed733ed50449f351dc33cae" +dependencies = [ + "ahash", + "cached_proc_macro", + "cached_proc_macro_types", + "hashbrown 0.14.5", + "once_cell", + "thiserror 1.0.69", + "web-time", +] + +[[package]] +name = "cached_proc_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + +[[package]] +name = "cipherstash-config" +version = "0.42.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d098935e395d7346d0cdc8cdf3ed9674ab03fa8b415e828d02e65c81836a73c" +dependencies = [ + "bitflags", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "clww-ore" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95747f5724666cb973b2ca0659e10b6083c11f99877c4727b4c9f534a591c74" +dependencies = [ + "blake3", + "hex", + "subtle", + "thiserror 1.0.69", + "vitaminc-ore", + "vitaminc-protected 0.8.1", + "zeroize", +] + +[[package]] +name = "cmac" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" +dependencies = [ + "cipher", + "dbl", + "digest 0.10.7", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "const-hex" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cts-common" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cb0f5ffa463e8facbe6ad78cfe925d132a051c6b1c9a5da2f3961296b7e632" +dependencies = [ + "arrayvec", + "base32", + "cached", + "chrono", + "derive_more", + "either", + "getrandom 0.4.2", + "miette", + "nom", + "regex", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", + "url", + "utoipa", + "uuid", + "vitaminc 0.5.1", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +dependencies = [ + "generic-array", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.114", + "unicode-xid", +] + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid", + "crypto-common 0.2.1", + "ctutils", + "zeroize", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "dummy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac124e13ae9aa56acc4241f8c8207501d93afdd8d8e62f0c1f2e12f6508c65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "eql-bindings" +version = "3.0.6" +dependencies = [ + "base64 0.22.1", + "base85-simd", + "hex", + "schemars", + "serde", + "serde_json", + "stack-encrypt", + "thiserror 2.0.18", + "ts-rs", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fake" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d391ba4af7f1d93f01fcf7b2f29e2bc9348e109dfdbf4dcbdc51dfa38dab0b6" +dependencies = [ + "deunicode", + "dummy", + "rand 0.8.6", + "uuid", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30" +dependencies = [ + "rustix", + "windows-targets 0.52.6", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "hickory-net" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c480823ed7c2c5d0f09c41020cb6b7c28029ce60ec42dc942158dcf22f8e0a4d" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "futures-channel", + "futures-io", + "futures-util", + "hickory-proto", + "idna", + "ipnet", + "jni 0.22.4", + "rand 0.10.1", + "thiserror 2.0.18", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-proto" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d" +dependencies = [ + "data-encoding", + "idna", + "ipnet", + "jni 0.22.4", + "once_cell", + "prefix-trie", + "rand 0.10.1", + "ring", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3da5255c95d5a716857d54b5b8f4e8d67c3484d3beaaaae2ce25063b3ba981" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-net", + "hickory-proto", + "ipconfig", + "ipnet", + "jni 0.22.4", + "moka", + "ndk-context", + "once_cell", + "parking_lot", + "rand 0.10.1", + "resolv-conf", + "smallvec", + "system-configuration", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +dependencies = [ + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.0", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.114", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.114", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "10.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" +dependencies = [ + "aws-lc-rs", + "base64 0.22.1", + "getrandom 0.2.17", + "js-sys", + "pem", + "serde", + "serde_json", + "signature", + "simple_asn1", + "zeroize", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moka" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "parking_lot", + "portable-atomic", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "mutants" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ac067452ff1aca8c5002111bd6b1c895baee6e45fcbc44e0193aea17be56" + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "open" +version = "5.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +dependencies = [ + "is-wsl", + "libc", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "ore-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd65962ec1409c09d20e1e016f4fe1b4ae2e2e58dba38c155538b2c0801a185" +dependencies = [ + "aes", + "block-modes", + "byteorder", + "hex", + "num", + "rand 0.10.1", + "subtle-ng", + "thiserror 1.0.69", + "vitaminc-context 0.8.1", + "vitaminc-hmac", + "vitaminc-ore", + "vitaminc-prf", + "vitaminc-protected 0.8.1", + "vitaminc-random 0.8.1", + "zeroize", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prefix-trie" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf6e3177f0684016a5c209b00882e15f8bdd3f3bb48f0491df10cd102d0c6e7" +dependencies = [ + "either", + "ipnet", + "num-traits", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags", + "num-traits", + "rand 0.9.3", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "unarray", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.2", + "lru-slab", + "rand 0.10.1", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.0", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "recipher" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e156e2d485b51cc67c19241e7d81ad524bda9fd4f77791b698ab10c8e26e9" +dependencies = [ + "aes", + "cmac", + "getrandom 0.2.17", + "hex", + "hex-literal", + "opaque-debug", + "rand 0.8.6", + "rand_chacha 0.3.1", + "serde", + "serde_cbor", + "sha2 0.10.9", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-core", + "futures-util", + "hickory-resolver", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.21.1", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.6", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serdect" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" +dependencies = [ + "base16ct", + "serde", + "zeroize", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stack-auth" +version = "0.43.0" +dependencies = [ + "aquamarine", + "base64 0.22.1", + "bytes", + "cts-common", + "jsonwebtoken", + "miette", + "open", + "reqwest", + "rustls", + "rustls-platform-verifier", + "rustls-webpki", + "serde", + "serde_json", + "serde_urlencoded", + "sha2 0.10.9", + "stack-profile", + "thiserror 1.0.69", + "tokio", + "tracing", + "url", + "uuid", + "vitaminc 0.8.1", + "vitaminc-protected 0.8.1", + "web-time", + "zeroize", + "zerokms-protocol", +] + +[[package]] +name = "stack-encrypt" +version = "0.3.0" +dependencies = [ + "base64ct", + "base85-simd", + "clww-ore", + "futures", + "miette", + "ore-rs", + "rand 0.10.1", + "serde", + "serde_json", + "serdect", + "sha2 0.11.0", + "stack-auth", + "stack-encrypt-derive", + "stack-kms", + "thiserror 1.0.69", + "uuid", + "vitaminc 0.8.1", + "vitaminc-aead 0.8.1", + "vitaminc-aead-frame", + "vitaminc-encrypt 0.8.1", + "vitaminc-hmac", + "vitaminc-normalize", + "vitaminc-ore", + "vitaminc-prf", + "vitaminc-protected 0.8.1", + "zeroize", +] + +[[package]] +name = "stack-encrypt-derive" +version = "0.3.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "stack-kms" +version = "0.1.0" +dependencies = [ + "base16ct", + "base64ct", + "blake3", + "futures", + "lazy_static", + "miette", + "opaque-debug", + "recipher", + "reqwest", + "rustls", + "serde", + "serde_cbor", + "serde_json", + "serdect", + "sha2 0.10.9", + "stack-auth", + "stack-profile", + "thiserror 1.0.69", + "tracing", + "url", + "uuid", + "vitaminc 0.8.1", + "vitaminc-protected 0.8.1", + "zeroize", + "zerokms-protocol", +] + +[[package]] +name = "stack-profile" +version = "0.43.0" +dependencies = [ + "dirs", + "gethostname", + "miette", + "serde", + "serde_json", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "stackencrypt-core" +version = "0.0.0" +dependencies = [ + "eql-bindings", + "miette", + "serde", + "serde_json", + "stack-auth", + "stack-encrypt", + "stack-kms", + "tokio", + "zerokms-protocol", +] + +[[package]] +name = "stackencrypt-core-fuzz" +version = "0.0.0" +dependencies = [ + "arbitrary", + "libfuzzer-sys", + "stack-encrypt", + "stackencrypt-core", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ts-rs" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e640d9b0964e9d39df633548591090ab92f7a4567bc31d3891af23471a3365c6" +dependencies = [ + "lazy_static", + "thiserror 2.0.18", + "ts-rs-macros", +] + +[[package]] +name = "ts-rs-macros" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "termcolor", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + +[[package]] +name = "unicode-ident" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utoipa" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "url", + "uuid", +] + +[[package]] +name = "uuid" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" +dependencies = [ + "atomic", + "getrandom 0.4.2", + "js-sys", + "md-5", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "validator" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vitaminc" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35042fb7a81d72dc43c53fec835e61027f18b4d01cc0f7e12d6c6a6ffbf3bfb9" +dependencies = [ + "vitaminc-aead 0.5.1", + "vitaminc-context 0.5.1", + "vitaminc-encrypt 0.5.1", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", + "vitaminc-traits 0.5.1", +] + +[[package]] +name = "vitaminc" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd347492314ce981cd9fba0361e07d240e4e4a2e12e0107a7b86cd16abb9f8aa" +dependencies = [ + "vitaminc-aead 0.8.1", + "vitaminc-context 0.8.1", + "vitaminc-encrypt 0.8.1", + "vitaminc-protected 0.8.1", + "vitaminc-random 0.8.1", + "vitaminc-traits 0.8.1", +] + +[[package]] +name = "vitaminc-aead" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62cacd4dae485e98cfe5407e919187a8468dd933ea4234be5115b7d13a30f3c6" +dependencies = [ + "bytes", + "serde", + "vitaminc-aead-derive 0.5.1", + "vitaminc-context 0.5.1", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", + "zeroize", +] + +[[package]] +name = "vitaminc-aead" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e723c592529da633fe7ba94d95904a9b6832e7995fc3f1d0da17bdbf8587c7a1" +dependencies = [ + "bytes", + "futures-util", + "serde", + "thiserror 2.0.18", + "vitaminc-aead-derive 0.8.1", + "vitaminc-context 0.8.1", + "vitaminc-protected 0.8.1", + "vitaminc-random 0.8.1", + "zeroize", +] + +[[package]] +name = "vitaminc-aead-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff34056b70a8417ad3bf9db73b2bd3be97f448794566c6de2080bd8a7cd9b6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "vitaminc-aead-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553187b663af2a88f50cddc6fc4b5e58f045aa5f7296075cf33a56c972ff6b2b" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "vitaminc-aead-frame" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a65349bb75a9ecc9fd4f057e5a6aac964bea68e57d03d55118a22455002a424" +dependencies = [ + "serde", + "vitaminc-aead 0.8.1", + "vitaminc-protected 0.8.1", + "zeroize", +] + +[[package]] +name = "vitaminc-context" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7df52501fe33cdb3ee9e155b5242fac184c037d147e58c96e5143ed011252a8" +dependencies = [ + "mutants", + "vitaminc-protected 0.5.1", +] + +[[package]] +name = "vitaminc-context" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0179df09c5d8cab1c9d9f104eb807812851d1a36f97212542fb61f29e01646e4" +dependencies = [ + "mutants", + "vitaminc-protected 0.8.1", +] + +[[package]] +name = "vitaminc-encrypt" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbd0e1748055e381e02033e08af385de879c4dcf32e0f6739ac0c0cf0c6f13d" +dependencies = [ + "aes-gcm", + "aws-lc-rs", + "vitaminc-aead 0.5.1", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", + "zeroize", +] + +[[package]] +name = "vitaminc-encrypt" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265383e1184382f22570fa5be60db8b4f6b2321c21a2c5bf08a858705c4c8828" +dependencies = [ + "aes-gcm", + "aws-lc-rs", + "vitaminc-aead 0.8.1", + "vitaminc-protected 0.8.1", + "vitaminc-random 0.8.1", + "zeroize", +] + +[[package]] +name = "vitaminc-hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a244886bff4915e15000a69f5a10f14410cabb30c445e3d585057c06a9ce1a" +dependencies = [ + "hmac", + "sha2 0.11.0", + "vitaminc-prf", + "vitaminc-protected 0.8.1", + "zeroize", +] + +[[package]] +name = "vitaminc-normalize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d820d507ca7cce81772e38c1277b26acc422a4fd99402e6d9d49c412a5a135f0" +dependencies = [ + "thiserror 2.0.18", + "unicode-general-category", + "unicode-normalization", + "vitaminc-aead 0.8.1", + "vitaminc-protected 0.8.1", +] + +[[package]] +name = "vitaminc-ore" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b80bf6789c1e2e1c192a255a9e2f6c3e7ffbce2fcb61d60edecee6ffc2f71ef" +dependencies = [ + "thiserror 2.0.18", + "unicode-general-category", + "unicode-normalization", + "vitaminc-aead 0.8.1", + "vitaminc-context 0.8.1", + "vitaminc-normalize", + "vitaminc-protected 0.8.1", + "zeroize", +] + +[[package]] +name = "vitaminc-prf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86cfcbfb4089f5bf334723cbf106f7d0d4709abf0ad12aaf49d6cf89f3e78de" +dependencies = [ + "mutants", + "thiserror 2.0.18", + "vitaminc-aead 0.8.1", + "vitaminc-context 0.8.1", + "vitaminc-normalize", + "vitaminc-protected 0.8.1", +] + +[[package]] +name = "vitaminc-protected" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7b514f605a63f88874ba46ffea81775fad64994475ab239cb19da159e884b7b" +dependencies = [ + "bitvec", + "digest 0.11.3", + "libc", + "serde", + "serde_bytes", + "subtle", + "thiserror 2.0.18", + "vitaminc-protected-derive 0.5.1", + "zeroize", +] + +[[package]] +name = "vitaminc-protected" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81704a2c5bb14d108dd4aa20b4124d4b0c47a2695fc6330131cf0897350737aa" +dependencies = [ + "bitvec", + "digest 0.11.3", + "libc", + "serde", + "serde_bytes", + "subtle", + "thiserror 2.0.18", + "vitaminc-protected-derive 0.8.1", + "zeroize", +] + +[[package]] +name = "vitaminc-protected-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb0a3d11afbe2c909f0cfc9996fe37fd89b1196eb0b8d89a66e28716360ee09" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "vitaminc-protected-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eacd4b9ad7ae5196ee117944325aead3508da45d4c7ad73f682e64d62b09bb7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "vitaminc-random" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7b1bfab35ee322de37269c6fd94d8cd94047a66f97cf6722e53e3321482167" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand 0.10.1", + "thiserror 2.0.18", + "vitaminc-protected 0.5.1", + "vitaminc-random-derives 0.5.1", + "zeroize", +] + +[[package]] +name = "vitaminc-random" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9bc57a6532bfa847fa5093c2c39bdc333d1c0b724f982cf3c240e8b091d491" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand 0.10.1", + "thiserror 2.0.18", + "vitaminc-protected 0.8.1", + "vitaminc-random-derives 0.8.1", + "zeroize", +] + +[[package]] +name = "vitaminc-random-derives" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0915e2bfb417ce11e23123fb7ec122af5d27d8cfd22d6ad6a2cf7a1c2dbb4712" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "vitaminc-random-derives" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e700b3a8e01ee5130939fbdc4dadb38bc4d4160c5f1d5f7fd139bab0491dfa" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "vitaminc-traits" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b31ec300e351a7a59e64899d0a2d7e7227cfa74ac0514f4e745acb2cc9335cee" +dependencies = [ + "anyhow", + "bytes", + "rmp-serde", + "serde", + "thiserror 2.0.18", + "vitaminc-protected 0.5.1", + "vitaminc-random 0.5.1", + "zeroize", +] + +[[package]] +name = "vitaminc-traits" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a090c4d967d90bc3c609b73a3629e27ca752e0a5a62a5b00e0ea91f04617c60d" +dependencies = [ + "anyhow", + "bytes", + "rmp-serde", + "serde", + "thiserror 2.0.18", + "vitaminc-protected 0.8.1", + "vitaminc-random 0.8.1", + "zeroize", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.114", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.114", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zerokms-protocol" +version = "0.12.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c28e88315a5109d0a1e7ee4b7b4b8776a0bff5f5b139ae83960a3debe84e92e" +dependencies = [ + "base64 0.22.1", + "cipherstash-config", + "const-hex", + "cts-common", + "fake", + "getrandom 0.2.17", + "opaque-debug", + "rand 0.8.6", + "serde", + "static_assertions", + "thiserror 1.0.69", + "utoipa", + "uuid", + "validator", + "zeroize", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zmij" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" diff --git a/languages/python/crates/core/fuzz/Cargo.toml b/languages/python/crates/core/fuzz/Cargo.toml new file mode 100644 index 000000000..d701faef9 --- /dev/null +++ b/languages/python/crates/core/fuzz/Cargo.toml @@ -0,0 +1,38 @@ +# Fuzz crate for the Python SDK's value conversion (stackencrypt-core's +# `values` module). +# +# This is a DETACHED crate: the `[workspace]` table at the bottom makes it its +# own workspace root, so the libfuzzer-sys dependency and the nightly-only +# build never touch the Python workspace in `languages/python`. Run via the +# root `fuzz:python-values` mise task, which invokes `cargo +nightly fuzz run`. +[package] +name = "stackencrypt-core-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +# Structure-aware inputs: the mirror types derive `Arbitrary`, so the fuzzer +# mutates a kind and a host value, not bytes. +arbitrary = { version = "1", features = ["derive"] } +stackencrypt-core = { path = ".." } + +[dependencies.stack-encrypt] +path = "../../../../../packages/stack-encrypt" +# The frame types only. The core crate turns on the features it needs. +default-features = false +features = ["dynamic"] + +[[bin]] +name = "value_frame" +path = "fuzz_targets/value_frame.rs" +test = false +doc = false +bench = false + +[workspace] +resolver = "2" diff --git a/languages/python/crates/core/fuzz/corpus/value_frame/valid-date b/languages/python/crates/core/fuzz/corpus/value_frame/valid-date new file mode 100644 index 0000000000000000000000000000000000000000..15c46c861ca85268eeb38bf8cfec6c99e1e70242 GIT binary patch literal 5 McmWe?bmL|K00T<^z literal 0 HcmV?d00001 diff --git a/languages/python/crates/core/fuzz/corpus/value_frame/valid-text b/languages/python/crates/core/fuzz/corpus/value_frame/valid-text new file mode 100644 index 0000000000000000000000000000000000000000..77324ca537f8dfd918a36f90a09b45a86527657e GIT binary patch literal 7 Ocmd;LVqjo6yb=Hchyi&3 literal 0 HcmV?d00001 diff --git a/languages/python/crates/core/fuzz/corpus/value_frame/valid-timestamp b/languages/python/crates/core/fuzz/corpus/value_frame/valid-timestamp new file mode 100644 index 0000000000000000000000000000000000000000..0395bb4b90e61d03c9e020a21defbf35df028166 GIT binary patch literal 13 OcmWe;fPw>8Lf8QZF9HJq literal 0 HcmV?d00001 diff --git a/languages/python/crates/core/fuzz/corpus/value_frame/valid-true b/languages/python/crates/core/fuzz/corpus/value_frame/valid-true new file mode 100644 index 000000000..fc2b5693e --- /dev/null +++ b/languages/python/crates/core/fuzz/corpus/value_frame/valid-true @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/languages/python/crates/core/fuzz/fuzz_targets/value_frame.rs b/languages/python/crates/core/fuzz/fuzz_targets/value_frame.rs new file mode 100644 index 000000000..07ef6d21c --- /dev/null +++ b/languages/python/crates/core/fuzz/fuzz_targets/value_frame.rs @@ -0,0 +1,204 @@ +#![no_main] + +// Fuzz the Python SDK's value conversion, both ways. +// +// Write: an arbitrary kind and host value go through `values::scalar`, the +// check `encrypt` and `query` run. A value it refuses is fine. A value it +// accepts must frame with no error, and must read back through +// `values::read` as the same value, narrowed to its kind. For a kind with a +// plain Rust type, the frame must also be the bytes `ValueFrame::of` gives +// that type: an independent writer, so a conversion that writes and reads +// symmetrically wrong is still caught. +// +// Read: arbitrary bytes go through `values::read`, as a passthrough the +// engine returns does. It must never panic. A scalar it accepts that the +// write check also accepts must write back as a passthrough and read as +// itself. + +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; +use stack_encrypt::dynamic::plaintext::Sink; +use stack_encrypt::dynamic::ValueFrame; +use stack_encrypt::Encrypt; +use stackencrypt_core::values::{self, Kind, Scalar, Value}; + +#[derive(Arbitrary, Debug)] +enum Input { + Write(MirrorKind, Host), + Read(Vec), +} + +/// `Option`: `Passthrough` is `None`. +#[derive(Arbitrary, Debug, Clone, Copy)] +enum MirrorKind { + Passthrough, + String, + Int16, + Int32, + Int64, + Float32, + Float64, + Bool, + Date, + Timestamp, + Decimal, +} + +impl MirrorKind { + fn kind(self) -> Option { + Some(match self { + Self::Passthrough => return None, + Self::String => Kind::String, + Self::Int16 => Kind::Int16, + Self::Int32 => Kind::Int32, + Self::Int64 => Kind::Int64, + Self::Float32 => Kind::Float32, + Self::Float64 => Kind::Float64, + Self::Bool => Kind::Bool, + Self::Date => Kind::Date, + Self::Timestamp => Kind::Timestamp, + Self::Decimal => Kind::Decimal, + }) + } +} + +/// `Value`, owned. +#[derive(Arbitrary, Debug)] +enum Host { + Text(String), + Int(i128), + Float(f64), + Bool(bool), + Bytes(Vec), + Date(i64), + Timestamp(i128), + Decimal { mantissa: i128, scale: u32 }, +} + +impl Host { + fn value(&self) -> Value<'_> { + match self { + Self::Text(s) => Value::Text(s), + Self::Int(i) => Value::Int(*i), + Self::Float(f) => Value::Float(*f), + Self::Bool(b) => Value::Bool(*b), + Self::Bytes(b) => Value::Bytes(b), + Self::Date(d) => Value::Date(*d), + Self::Timestamp(t) => Value::Timestamp(*t), + Self::Decimal { mantissa, scale } => Value::Decimal { + mantissa: *mantissa, + scale: *scale, + }, + } + } +} + +struct Written<'a>(Option, Value<'a>); + +impl Encrypt for Written<'_> { + fn visit(self, sink: S) -> Result { + values::write(sink, self.0, self.1) + } +} + +/// The scalar an accepted value reads back as: the value, narrowed to its +/// kind. +fn expected(kind: Option, value: Value<'_>) -> Scalar { + match (kind, value) { + (_, Value::Text(s)) => Scalar::Text(s.to_owned()), + (_, Value::Int(i)) => Scalar::Int(i), + (Some(Kind::Float32), Value::Float(f)) => Scalar::Float(f64::from(f as f32)), + (_, Value::Float(f)) => Scalar::Float(f), + (_, Value::Bool(b)) => Scalar::Bool(b), + (_, Value::Bytes(b)) => Scalar::Bytes(b.to_vec()), + (_, Value::Date(d)) => Scalar::Date(i32::try_from(d).expect("an accepted date")), + (_, Value::Timestamp(t)) => Scalar::Timestamp(t), + (_, Value::Decimal { mantissa, scale }) => Scalar::Decimal { + mantissa, + scale: u8::try_from(scale).expect("an accepted scale"), + }, + } +} + +/// The frame of the plain Rust type for `kind`, where there is one. +fn independent(kind: Option, value: Value<'_>) -> Option { + Some( + match (kind, value) { + (Some(Kind::String), Value::Text(s)) => ValueFrame::of(s), + (Some(Kind::Int16), Value::Int(i)) => ValueFrame::of(i16::try_from(i).ok()?), + (Some(Kind::Int32), Value::Int(i)) => ValueFrame::of(i32::try_from(i).ok()?), + (Some(Kind::Int64), Value::Int(i)) => ValueFrame::of(i64::try_from(i).ok()?), + (Some(Kind::Float32), Value::Float(f)) => ValueFrame::of(f as f32), + (Some(Kind::Float64), Value::Float(f)) => ValueFrame::of(f), + (Some(Kind::Bool), Value::Bool(b)) => ValueFrame::of(b), + _ => return None, + } + .expect("a plain Rust value frames"), + ) +} + +/// Equal, with a NaN equal to a NaN: the value model keeps NaN. +fn same(a: &Scalar, b: &Scalar) -> bool { + match (a, b) { + (Scalar::Float(a), Scalar::Float(b)) => { + a.to_bits() == b.to_bits() || (a.is_nan() && b.is_nan()) + } + _ => a == b, + } +} + +/// The scalar as the value the SDK writes, or `None` for a null. +fn value_of(scalar: &Scalar) -> Option> { + Some(match scalar { + Scalar::Null => return None, + Scalar::Text(s) => Value::Text(s), + Scalar::Int(i) => Value::Int(*i), + Scalar::Float(f) => Value::Float(*f), + Scalar::Bool(b) => Value::Bool(*b), + Scalar::Bytes(b) => Value::Bytes(b), + Scalar::Date(d) => Value::Date(i64::from(*d)), + Scalar::Timestamp(t) => Value::Timestamp(*t), + Scalar::Decimal { mantissa, scale } => Value::Decimal { + mantissa: *mantissa, + scale: u32::from(*scale), + }, + }) +} + +fuzz_target!(|input: Input| { + match input { + Input::Write(kind, host) => { + let (kind, value) = (kind.kind(), host.value()); + if values::scalar(kind, value).is_err() { + return; + } + let frame = ValueFrame::of(Written(kind, value)).expect("an accepted value frames"); + let read = values::read(frame.risky_ref()).expect("a written frame reads"); + assert!( + same(&read, &expected(kind, value)), + "{read:?} for {kind:?} {value:?}" + ); + if let Some(plain) = independent(kind, value) { + assert_eq!(frame.risky_ref(), plain.risky_ref(), "{kind:?} {value:?}"); + } + } + Input::Read(bytes) => { + let Ok(read) = values::read(&bytes) else { + return; + }; + let Some(value) = value_of(&read) else { + return; + }; + // A frame can hold a timestamp in seconds whose count, in + // nanoseconds, is past the range the SDK writes. The engine only + // returns a passthrough the SDK wrote, so read accepting more + // than write is not a defect. + if values::scalar(None, value).is_err() { + return; + } + let frame = ValueFrame::of(Written(None, value)).expect("a read scalar writes back"); + let again = values::read(frame.risky_ref()).expect("a written frame reads"); + assert!(same(&again, &read), "{again:?} after {read:?}"); + } + } +}); diff --git a/languages/python/crates/core/testdata/README.md b/languages/python/crates/core/testdata/README.md new file mode 100644 index 000000000..b8d35c296 --- /dev/null +++ b/languages/python/crates/core/testdata/README.md @@ -0,0 +1,25 @@ +# Test data for the core crate + +## `value-conformance.json` + +This file is a copy of vitaminc's shared value corpus. Each vector gives a host value, its kind, and the exact bytes of its frame. `tests/value_conformance.rs` runs the vectors against the SDK's value conversion. + +| | | +|---|---| +| Source | `testdata/value-conformance.json` in `cipherstash/vitaminc` | +| Commit | `06568ba93bd94455cce9887c98898ca0f96b6440`, the commit that `languages/golang/go.mod` names for `vcffi` and `vcvalue` | +| Git blob hash | `97475e1f4a5f0942bd3c79f4ac7a93352fa79194` | + +The file is a copy because the published vitaminc crates do not include it. Never edit the copy by hand. + +vitaminc only adds rows to this corpus. It never changes or removes a row. So an older copy is still correct. It can miss newer rows. + +To update the copy, download the file at a newer commit and replace this one: + +```sh +gh api "repos/cipherstash/vitaminc/contents/testdata/value-conformance.json?ref=" \ + -H "Accept: application/vnd.github.raw" > value-conformance.json +git hash-object value-conformance.json +``` + +Then record the new commit and blob hash in the table above. `cargo test` fails on a new vector of a kind the SDK writes, until the test handles it. diff --git a/languages/python/crates/core/testdata/value-conformance.json b/languages/python/crates/core/testdata/value-conformance.json new file mode 100644 index 000000000..97475e1f4 --- /dev/null +++ b/languages/python/crates/core/testdata/value-conformance.json @@ -0,0 +1,767 @@ +{ + "vectors": [ + { + "name": "null", + "kind": "null", + "value": null, + "transport": "00" + }, + { + "name": "undefined", + "kind": "undefined", + "value": null, + "transport": "01", + "go_reencode": "00" + }, + { + "name": "false", + "kind": "bool", + "value": false, + "transport": "02" + }, + { + "name": "true", + "kind": "bool", + "value": true, + "transport": "03" + }, + { + "name": "int8", + "kind": "int8", + "value": "-128", + "transport": "0c80" + }, + { + "name": "uint8", + "kind": "uint8", + "value": "255", + "transport": "0dff" + }, + { + "name": "int16", + "kind": "int16", + "value": "-32768", + "transport": "0e0080" + }, + { + "name": "uint16", + "kind": "uint16", + "value": "65535", + "transport": "0fffff" + }, + { + "name": "int32", + "kind": "int32", + "value": "-2147483648", + "transport": "0400000080" + }, + { + "name": "uint32", + "kind": "uint32", + "value": "4294967295", + "transport": "06ffffffff" + }, + { + "name": "int64", + "kind": "int64", + "value": "-9223372036854775808", + "transport": "050000000000000080" + }, + { + "name": "uint64", + "kind": "uint64", + "value": "18446744073709551615", + "transport": "07ffffffffffffffff" + }, + { + "name": "int128", + "kind": "int128", + "value": "-170141183460469231731687303715884105728", + "transport": "1000000000000000000000000000000080" + }, + { + "name": "uint128", + "kind": "uint128", + "value": "340282366920938463463374607431768211455", + "transport": "11ffffffffffffffffffffffffffffffff" + }, + { + "name": "float32", + "kind": "float32", + "value": 1.5, + "transport": "080000c03f", + "js_reencode": "09000000000000f83f" + }, + { + "name": "float64", + "kind": "float64", + "value": -1.5, + "transport": "09000000000000f8bf" + }, + { + "name": "text", + "kind": "string", + "value": "é", + "transport": "0a02000000c3a9" + }, + { + "name": "text-nul", + "kind": "string", + "value": "a\u0000b", + "transport": "0a03000000610062" + }, + { + "name": "bytes", + "kind": "bytes", + "value": "00ff07", + "transport": "0b0300000000ff07" + }, + { + "name": "date", + "kind": "date", + "value": "2024-02-29", + "transport": "1281460b00" + }, + { + "name": "date-year-zero", + "kind": "date", + "value": "0000-01-01", + "transport": "1293feffff" + }, + { + "name": "timestamp", + "kind": "timestamp", + "value": "1970-01-01T00:00:00.123Z", + "transport": "130000000000000000c0d45407", + "js_date": true, + "js_reencode": "f30301057b00000000000000" + }, + { + "name": "timestamp-nanos", + "kind": "timestamp", + "value": "1969-12-31T23:59:59.999999999Z", + "transport": "13ffffffffffffffffffc99a3b", + "js_date": false + }, + { + "name": "timestamp-expanded-year", + "kind": "timestamp", + "value": "+12000-01-01T00:00:00.000000001Z", + "transport": "130001d6b14900000001000000", + "js_date": false + }, + { + "name": "timestamp-negative-year", + "kind": "timestamp", + "value": "-0005-03-01T12:00:00.000000001Z", + "transport": "1340a2727df1ffffff01000000", + "js_date": false + }, + { + "name": "decimal", + "kind": "decimal", + "value": "1.50", + "transport": "1400000200960000000000000000000000" + }, + { + "name": "decimal-min", + "kind": "decimal", + "value": "-79228162514264337593543950335", + "transport": "1400000080ffffffffffffffffffffffff" + }, + { + "name": "decimal-small", + "kind": "decimal", + "value": "0.0000000000000000000000000001", + "transport": "1400001c00010000000000000000000000" + }, + { + "name": "empty-array", + "kind": "array", + "value": [], + "transport": "f000000000" + }, + { + "name": "empty-object", + "kind": "object", + "value": {}, + "transport": "f100000000" + }, + { + "name": "uuid", + "kind": "uuid", + "value": "123e4567-e89b-12d3-a456-426614174000", + "transport": "f305000b10000000123e4567e89b12d3a456426614174000", + "rule": "ADR 0001 section 3: Uuid has no shorthand tag, so it travels in the 0xF3 envelope over sixteen bytes" + }, + { + "name": "timestamp-millis", + "kind": "timestamp", + "unit": "millis", + "value": "1970-01-01T00:00:00.123Z", + "transport": "f30301057b00000000000000", + "js_date": true, + "go_reencode": "130000000000000000c0d45407", + "rule": "ADR 0001 section 3: Timestamp(Millis) over an i64 count travels in the 0xF3 envelope; Go reads it as time.Time and writes nanoseconds" + }, + { + "name": "timestamp-seconds", + "kind": "timestamp", + "unit": "seconds", + "value": "1970-01-01T00:00:10Z", + "transport": "f30300050a00000000000000", + "js_date": true, + "go_reencode": "130a0000000000000000000000", + "js_reencode": "f30301051027000000000000", + "rule": "ADR 0001 section 3: Timestamp(Seconds) travels in the 0xF3 envelope; Go writes it back in nanoseconds and Node writes a Date back in milliseconds" + }, + { + "name": "timestamp-micros", + "kind": "timestamp", + "unit": "micros", + "value": "1970-01-01T00:00:00.000001000Z", + "transport": "f30302050100000000000000", + "go_reencode": "130000000000000000e8030000", + "js_reencode": "130000000000000000e8030000", + "rule": "ADR 0001 section 3: Timestamp(Micros) travels in the 0xF3 envelope; Go and Node write it back in nanoseconds" + }, + { + "name": "unknown-logical", + "kind": "unknown", + "value": { + "id": 200, + "param": 9, + "kind": "uint32", + "value": "34" + }, + "transport": "f3c8090622000000", + "js": "refused", + "rule": "ADR 0001 section 3: a logical id this build does not know is read and re-encoded unchanged (Rust, and Go through Tagged); a host builder with no value to carry it in refuses the leaf rather than dropping the annotation, which is what Node does today (#446 tracks a carrier)" + } + ], + "malformed": [ + { + "name": "unknown-tag", + "transport": "ef" + }, + { + "name": "invalid-utf8", + "transport": "0a01000000ff" + }, + { + "name": "trailing", + "transport": "0000" + }, + { + "name": "invalid-date", + "transport": "12ffffff7f" + }, + { + "name": "invalid-timestamp-nanos", + "transport": "13000000000000000000ca9a3b" + }, + { + "name": "invalid-timestamp-range", + "transport": "13ffffffffffffff7f00000000" + }, + { + "name": "decimal-reserved-bits", + "transport": "1401000000000000000000000000000000" + }, + { + "name": "decimal-scale", + "transport": "1400001d00000000000000000000000000" + }, + { + "name": "tag-0c-width-0", + "transport": "0c" + }, + { + "name": "tag-0c-width-2", + "transport": "0c0000" + }, + { + "name": "tag-0d-width-0", + "transport": "0d" + }, + { + "name": "tag-0d-width-2", + "transport": "0d0000" + }, + { + "name": "tag-0e-width-1", + "transport": "0e00" + }, + { + "name": "tag-0e-width-3", + "transport": "0e000000" + }, + { + "name": "tag-0f-width-1", + "transport": "0f00" + }, + { + "name": "tag-0f-width-3", + "transport": "0f000000" + }, + { + "name": "tag-10-width-15", + "transport": "10000000000000000000000000000000" + }, + { + "name": "tag-10-width-17", + "transport": "100000000000000000000000000000000000" + }, + { + "name": "tag-11-width-15", + "transport": "11000000000000000000000000000000" + }, + { + "name": "tag-11-width-17", + "transport": "110000000000000000000000000000000000" + }, + { + "name": "tag-12-width-3", + "transport": "12000000" + }, + { + "name": "tag-12-width-5", + "transport": "120000000000" + }, + { + "name": "tag-13-width-11", + "transport": "130000000000000000000000" + }, + { + "name": "tag-13-width-13", + "transport": "1300000000000000000000000000" + }, + { + "name": "tag-14-width-15", + "transport": "14000000000000000000000000000000" + }, + { + "name": "tag-14-width-17", + "transport": "140000000000000000000000000000000000" + }, + { + "name": "timestamp-leap-second", + "transport": "133b000000000000001597f642", + "rule": "ADR 0001 section 3: a leap second has no count since the epoch in any unit and is refused" + }, + { + "name": "envelope-over-shorthand", + "transport": "f302000400000000", + "rule": "ADR 0001 section 3: there is one encoding per value, so an envelope over a type with a shorthand tag is refused" + }, + { + "name": "envelope-id-zero", + "transport": "f3000000", + "rule": "ADR 0001 section 3: id 0 means no logical type and has no envelope" + }, + { + "name": "envelope-known-id-bad-param", + "transport": "f305010b10000000123e4567e89b12d3a456426614174000", + "rule": "ADR 0001 section 3: a known id with a parameter it cannot take is refused" + }, + { + "name": "envelope-uuid-wrong-length", + "transport": "f305000b03000000010203", + "rule": "ADR 0001 section 3: a Uuid is sixteen bytes" + }, + { + "name": "envelope-nested", + "transport": "f3c800f3c80000", + "rule": "ADR 0001 section 3: the envelope carries a physical scalar, not another envelope" + }, + { + "name": "envelope-unknown-over-undefined", + "transport": "f3c80001", + "rule": "ADR 0001 section 3: the envelope carries a physical scalar; Undefined has a shorthand tag and is refused" + }, + { + "name": "envelope-unknown-over-date", + "transport": "f3c8001200000000", + "rule": "ADR 0001 section 3: the envelope carries a physical scalar; Date has a shorthand tag and is refused" + }, + { + "name": "envelope-unknown-over-array", + "transport": "f3c800f000000000", + "rule": "ADR 0001 section 3: the envelope carries a physical scalar, not a container" + }, + { + "name": "leaf-0xf4", + "transport": "f4", + "rule": "ADR 0001 section 3: 0xF4 is reserved for a longer envelope and is refused" + }, + { + "name": "leaf-0xf4-with-payload", + "transport": "f405000b00000000", + "rule": "ADR 0001 section 3: 0xF4 is reserved for a longer envelope and is refused" + }, + { + "name": "envelope-timestamp-nanos", + "transport": "f30303050000000000000000", + "rule": "ADR 0001 section 3: a logical type with a shorthand tag is written with it, so Timestamp in nanoseconds (unit 3) inside the envelope is refused; units 0 to 2 have no shorthand and are accepted" + }, + { + "name": "envelope-undefined", + "transport": "f3010000", + "rule": "ADR 0001 section 3: Undefined (id 1) has the shorthand tag 0x01 and is refused inside the envelope" + }, + { + "name": "envelope-decimal", + "transport": "f304021000000000000000000000000000000000", + "rule": "ADR 0001 section 3: Decimal (id 4) has the shorthand tag 0x14 and is refused inside the envelope" + }, + { + "name": "envelope-timestamp-bad-unit", + "transport": "f30304050000000000000000", + "rule": "ADR 0001 section 3: Timestamp takes a unit of 0 to 3 as its parameter; 4 is refused" + }, + { + "name": "envelope-timestamp-not-int", + "transport": "f303010a00000000", + "rule": "ADR 0001 section 3: Timestamp admits only an int as its physical value; text is refused" + }, + { + "name": "envelope-timestamp-out-of-range", + "transport": "f30300100000000000000000000000000000ff7f", + "rule": "ADR 0001 section 3: a timestamp count outside the representable range (here an i128 of seconds far past year 262143) is refused" + }, + { + "name": "envelope-uuid-over-text", + "transport": "f305000a1000000030313233343536373839616263646566", + "rule": "ADR 0001 section 3: Uuid admits only sixteen bytes as its physical value; text of that length is refused" + }, + { + "name": "envelope-truncated", + "transport": "f3c8", + "rule": "ADR 0001 section 3: the envelope is id, parameter and a physical scalar; a parameter and payload cut off are refused" + } +, + { + "name": "typed-null-truncated", + "transport": "15", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-no-padding", + "transport": "1506", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-short-padding", + "transport": "15060000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-nonzero-padding", + "transport": "1506000100", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-trailing", + "transport": "150600000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-of-null", + "transport": "1500", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-of-undefined", + "transport": "1501", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-of-bool", + "transport": "1502", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-of-text", + "transport": "150a", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-of-plain-bytes", + "transport": "150b000000000000000000000000000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-nested", + "transport": "151506000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-of-array", + "transport": "15f000000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-envelope-over-shorthand", + "transport": "15f3020004000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-envelope-timestamp-nanos", + "transport": "15f3030310000000000000000000000000000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-envelope-id-zero", + "transport": "15f3000004000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-envelope-undefined", + "transport": "15f3010004000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-envelope-decimal", + "transport": "15f3040004000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-uuid-over-int", + "transport": "15f3050004000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-uuid-with-length", + "transport": "15f305000b10000000000000000000000000000000000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-uuid-with-parameter", + "transport": "15f305010b000000000000000000000000000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-timestamp-over-float", + "transport": "15f303010900000000000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-unknown-over-bytes", + "transport": "15f3c8000b000000000000000000000000000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-unknown-over-bool", + "transport": "15f3c80002", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-inside-envelope", + "transport": "f3c800150c", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + }, + { + "name": "typed-null-0xf4", + "transport": "15f4c80004000000", + "rule": "Typed null (vitaminc#493): every absence has one encoding, so a typed null is TYPED_NULL, the header a value of a fixed-width type starts with, then zero padding to that value's length, and nothing else" + } + ], + "normalized": [ + { + "name": "decimal-negative-zero", + "kind": "decimal", + "value": "-0.00", + "transport": "1400000280000000000000000000000000", + "reencode": "1400000200000000000000000000000000", + "reencode_value": "0.00", + "rule": "ADR 0001 section 3: a decimal negative zero reads back as zero with its scale kept, and zero is always written without the sign" + } +, + { + "name": "typed-null-uint32", + "kind": "null", + "value": null, + "transport": "1506000000", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + }, + { + "name": "typed-null-int8", + "kind": "null", + "value": null, + "transport": "150c", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + }, + { + "name": "typed-null-float64", + "kind": "null", + "value": null, + "transport": "150900000000000000", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + }, + { + "name": "typed-null-date", + "kind": "null", + "value": null, + "transport": "1512000000", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + }, + { + "name": "typed-null-timestamp", + "kind": "null", + "value": null, + "transport": "15130000000000000000000000", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + }, + { + "name": "typed-null-decimal", + "kind": "null", + "value": null, + "transport": "1514000000000000000000000000000000", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + }, + { + "name": "typed-null-timestamp-millis", + "kind": "null", + "value": null, + "transport": "15f303010500000000000000", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + }, + { + "name": "typed-null-uuid", + "kind": "null", + "value": null, + "transport": "15f305000b000000000000000000000000000000", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + }, + { + "name": "typed-null-unknown-over-float32", + "kind": "null", + "value": null, + "transport": "15f3c80908000000", + "reencode": "00", + "reencode_value": null, + "rule": "Typed null (vitaminc#493): an absent value of a fixed-width type, padded to that type's leaf length so its sealed length matches a present value's. A frame is never sealed, so it reads as null and is written back as the plain null" + } + ], + "ciphertext_malformed": [ + { + "name": "ct-none", + "transport": "0204000000deadbeef", + "rule": "ADR 0001 section 3: CT_NONE (0x02) is retired because a None is a null leaf; it is never written and refused when read" + }, + { + "name": "ct-none-in-sequence", + "transport": "03010000000204000000deadbeef", + "rule": "ADR 0001 section 3: CT_NONE is refused wherever it appears" + } + ], + "leaf_type_aad": { + "rule": "LeafTypeAad (ADR 0001 section 3): the context is PAE(domain, base, kind, id) under the vitaminc-context encoding, where PAE is a u64-le count then each element as a u64-le length and its bytes, and each element is itself a typed piece: a PAE of two parts, the u64-le-prefixed label vitaminc/context/value/v1 and the u64-le-prefixed type label then the payload. The domain is bytes/v1 over vitaminc/aead/leaf-type/v2; the base is utf8/v1 over the base string; the kind is u8-le/v1 over the physical kind byte (null 0, bool 1, int 2, float 3, text 4, bytes 5; a cross-language commitment, never renumbered); the id is u8-le/v1 over the logical id byte, 0 for no logical type. The parameter is not bound: two vectors with the same base, kind and id and different parameters have the same context.", + "vectors": [ + { + "name": "int-date", + "base": "ctx", + "kind": "int", + "logical": { + "id": 2, + "param": 0 + }, + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632540000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f76310300000000000000637478530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000002530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000002" + }, + { + "name": "text-no-logical", + "base": "ctx", + "kind": "text", + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632540000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f76310300000000000000637478530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000004530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000000" + }, + { + "name": "bytes-uuid", + "base": "schema", + "kind": "bytes", + "logical": { + "id": 5, + "param": 0 + }, + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632570000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f76310600000000000000736368656d61530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000005530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000005" + }, + { + "name": "int-timestamp-millis", + "base": "events", + "kind": "int", + "logical": { + "id": 3, + "param": 1 + }, + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632570000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f763106000000000000006576656e7473530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000002530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000003" + }, + { + "name": "int-decimal-scale-2", + "base": "price", + "kind": "int", + "logical": { + "id": 4, + "param": 2 + }, + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632560000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f763105000000000000007072696365530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000002530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000004" + }, + { + "name": "null-undefined", + "base": "ctx", + "kind": "null", + "logical": { + "id": 1, + "param": 0 + }, + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632540000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f76310300000000000000637478530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000000530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000001" + }, + { + "name": "bool-no-logical", + "base": "ctx", + "kind": "bool", + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632540000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f76310300000000000000637478530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000001530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000000" + }, + { + "name": "float-no-logical", + "base": "ctx", + "kind": "float", + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632540000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f76310300000000000000637478530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000003530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000000" + }, + { + "name": "int-decimal-scale-0", + "base": "price", + "kind": "int", + "logical": { + "id": 4, + "param": 0 + }, + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632560000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f763105000000000000007072696365530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000002530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000004" + }, + { + "name": "int-timestamp-nanos", + "base": "events", + "kind": "int", + "logical": { + "id": 3, + "param": 3 + }, + "context": "04000000000000006c0000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f62797465732f76311a00000000000000766974616d696e632f616561642f6c6561662d747970652f7632570000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311800000000000000766974616d696e632f636f6e746578742f757466382f763106000000000000006576656e7473530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000002530000000000000003000000000000001900000000000000766974616d696e632f636f6e746578742f76616c75652f76311900000000000000766974616d696e632f636f6e746578742f75382d6c652f7631010000000000000003" + } + ] + } +} diff --git a/languages/python/crates/core/tests/value_conformance.rs b/languages/python/crates/core/tests/value_conformance.rs new file mode 100644 index 000000000..b8d203f89 --- /dev/null +++ b/languages/python/crates/core/tests/value_conformance.rs @@ -0,0 +1,246 @@ +//! vitaminc's shared value corpus against the SDK's value conversion. +//! +//! The corpus (`testdata/value-conformance.json`, see `testdata/README.md`) +//! gives each vector a host value, its kind, and the exact bytes of its +//! frame. For every kind the SDK writes, the value written through +//! [`values::write`] must give those bytes, and the bytes read through +//! [`values::read`] must give the value back. Every malformed vector must +//! be refused, and every normalized vector must read as its canonical +//! value. +//! +//! A vector of a kind the SDK does not write is skipped by name, so a new +//! vector in the corpus fails here until it is handled or listed. + +use serde_json::Value as Json; +use stack_encrypt::dynamic::plaintext::Sink; +use stack_encrypt::dynamic::ValueFrame; +use stack_encrypt::Encrypt; +use stackencrypt_core::values::{self, Kind, Scalar, Value}; + +const CORPUS: &str = include_str!("../testdata/value-conformance.json"); + +/// The vectors whose kind the SDK never writes: no Python type maps to it +/// (`int8`, the unsigned and 128-bit integers, `uuid`), it is not a scalar +/// (`array`, `object`), or it is a timestamp in a unit other than the +/// nanoseconds the SDK writes. +const NOT_WRITTEN: &[&str] = &[ + "null", + "undefined", + "int8", + "uint8", + "uint16", + "uint32", + "uint64", + "int128", + "uint128", + "empty-array", + "empty-object", + "uuid", + "timestamp-millis", + "timestamp-seconds", + "timestamp-micros", + "unknown-logical", +]; + +fn corpus() -> Json { + serde_json::from_str(CORPUS).expect("the corpus is JSON") +} + +fn unhex(text: &str) -> Vec { + (0..text.len()) + .step_by(2) + .map(|at| u8::from_str_radix(&text[at..at + 2], 16).expect("hex")) + .collect() +} + +/// A value written as `kind`, or as a passthrough for `None`. +struct Written<'a>(Option, Value<'a>); + +impl Encrypt for Written<'_> { + fn visit(self, sink: S) -> Result { + values::write(sink, self.0, self.1) + } +} + +/// The SDK's kind for a vector, its value, and the scalar it reads back as. +/// `None` for a vector the SDK does not write. +fn host(vector: &Json, text: &mut String, bytes: &mut Vec) -> Option<(Option, Scalar)> { + let value = &vector["value"]; + let int = || { + value + .as_str() + .expect("an integer as text") + .parse::() + .expect("an integer") + }; + Some(match vector["kind"].as_str().expect("a kind") { + "bool" => ( + Some(Kind::Bool), + Scalar::Bool(value.as_bool().expect("a bool")), + ), + "int16" => (Some(Kind::Int16), Scalar::Int(int())), + "int32" => (Some(Kind::Int32), Scalar::Int(int())), + "int64" => (Some(Kind::Int64), Scalar::Int(int())), + "float32" => ( + Some(Kind::Float32), + Scalar::Float(value.as_f64().expect("a float")), + ), + "float64" => ( + Some(Kind::Float64), + Scalar::Float(value.as_f64().expect("a float")), + ), + "string" => { + *text = value.as_str().expect("text").to_owned(); + (Some(Kind::String), Scalar::Text(text.clone())) + } + // A Python `bytes` field is a passthrough: no EQL type takes bytes. + "bytes" => { + *bytes = unhex(value.as_str().expect("hex")); + (None, Scalar::Bytes(bytes.clone())) + } + "date" => ( + Some(Kind::Date), + Scalar::Date(days(value.as_str().expect("a date"))), + ), + "timestamp" if vector.get("unit").is_none() => ( + Some(Kind::Timestamp), + Scalar::Timestamp(nanos(value.as_str().expect("a timestamp"))), + ), + "decimal" => { + let (mantissa, scale) = decimal(value.as_str().expect("a decimal")); + (Some(Kind::Decimal), Scalar::Decimal { mantissa, scale }) + } + _ => return None, + }) +} + +/// The scalar as the value the SDK writes. +fn value_of<'a>(scalar: &'a Scalar) -> Value<'a> { + match scalar { + Scalar::Bool(b) => Value::Bool(*b), + Scalar::Int(i) => Value::Int(*i), + Scalar::Float(f) => Value::Float(*f), + Scalar::Text(s) => Value::Text(s), + Scalar::Bytes(b) => Value::Bytes(b), + Scalar::Date(d) => Value::Date(i64::from(*d)), + Scalar::Timestamp(t) => Value::Timestamp(*t), + Scalar::Decimal { mantissa, scale } => Value::Decimal { + mantissa: *mantissa, + scale: u32::from(*scale), + }, + Scalar::Null => panic!("the SDK never writes a null"), + } +} + +/// Days since 1970-01-01 of a proleptic Gregorian date, `[+-]YYYY-MM-DD`. +fn days(date: &str) -> i32 { + let (sign, date) = match date.strip_prefix('-') { + Some(rest) => (-1, rest), + None => (1, date.strip_prefix('+').unwrap_or(date)), + }; + let mut parts = date.split('-'); + let mut next = || { + parts + .next() + .expect("a date part") + .parse::() + .expect("a number") + }; + let (year, month, day) = (sign * next(), next(), next()); + // Howard Hinnant's days_from_civil. + let year = if month <= 2 { year - 1 } else { year }; + let era = year.div_euclid(400); + let of_era = year - era * 400; + let of_year = (153 * (month + if month > 2 { -3 } else { 9 }) + 2) / 5 + day - 1; + let of_era_days = of_era * 365 + of_era / 4 - of_era / 100 + of_year; + i32::try_from(era * 146_097 + of_era_days - 719_468).expect("a date in range") +} + +/// Nanoseconds since the Unix epoch of `[+-]YYYY-MM-DDTHH:MM:SS[.fraction]Z`. +fn nanos(timestamp: &str) -> i128 { + let (date, time) = timestamp.split_once('T').expect("a T"); + let time = time.strip_suffix('Z').expect("a Z"); + let (clock, fraction) = time.split_once('.').unwrap_or((time, "")); + let mut parts = clock + .split(':') + .map(|part| part.parse::().expect("a number")); + let mut next = || parts.next().expect("a clock part"); + let seconds = next() * 3600 + next() * 60 + next(); + let fraction = format!("{fraction:0<9}") + .parse::() + .expect("a fraction"); + (i128::from(days(date)) * 86_400 + seconds) * 1_000_000_000 + fraction +} + +/// The mantissa and scale of decimal text, such as `-1.50`. +fn decimal(text: &str) -> (i128, u8) { + let scale = text + .split_once('.') + .map_or(0, |(_, fraction)| fraction.len()); + let mantissa = text.replace('.', "").parse::().expect("a mantissa"); + (mantissa, u8::try_from(scale).expect("a scale")) +} + +#[test] +fn every_vector_the_sdk_writes_gives_its_bytes_and_reads_back() { + let corpus = corpus(); + let mut written = 0; + for vector in corpus["vectors"].as_array().expect("vectors") { + let name = vector["name"].as_str().expect("a name"); + let (mut text, mut bytes) = (String::new(), Vec::new()); + let Some((kind, expected)) = host(vector, &mut text, &mut bytes) else { + assert!( + NOT_WRITTEN.contains(&name), + "{name}: a new kind, not handled" + ); + continue; + }; + assert!( + !NOT_WRITTEN.contains(&name), + "{name}: listed as not written" + ); + let transport = unhex(vector["transport"].as_str().expect("transport")); + + let frame = ValueFrame::of(Written(kind, value_of(&expected))) + .unwrap_or_else(|error| panic!("{name}: refused: {error}")); + + assert_eq!( + frame.risky_ref(), + &transport[..], + "{name}: the written bytes" + ); + assert_eq!( + values::read(&transport).unwrap(), + expected, + "{name}: the read value" + ); + written += 1; + } + // Every scalar kind the SDK has, from the corpus at this commit. + assert_eq!(written, 19); +} + +#[test] +fn every_malformed_vector_is_refused() { + let corpus = corpus(); + for vector in corpus["malformed"].as_array().expect("malformed") { + let name = vector["name"].as_str().expect("a name"); + let transport = unhex(vector["transport"].as_str().expect("transport")); + assert!(values::read(&transport).is_err(), "{name}: read"); + } +} + +#[test] +fn every_normalized_vector_reads_as_its_canonical_value() { + let corpus = corpus(); + for vector in corpus["normalized"].as_array().expect("normalized") { + let name = vector["name"].as_str().expect("a name"); + let transport = unhex(vector["transport"].as_str().expect("transport")); + let reencode = unhex(vector["reencode"].as_str().expect("reencode")); + + let read = values::read(&transport); + + let read = read.unwrap_or_else(|error| panic!("{name}: refused: {error:?}")); + assert_eq!(Ok(read), values::read(&reencode), "{name}"); + } +} diff --git a/languages/python/tests/test_encrypt_live.py b/languages/python/tests/test_encrypt_live.py index df0def480..124f0d5dc 100644 --- a/languages/python/tests/test_encrypt_live.py +++ b/languages/python/tests/test_encrypt_live.py @@ -9,8 +9,10 @@ """ import dataclasses +import datetime import decimal import json +import math import os from typing import Annotated, List, NamedTuple @@ -22,7 +24,7 @@ import stackencrypt from stackencrypt import Cipher, DecryptError, Ignore, Passthrough, TextEq -from stackencrypt import _engine +from stackencrypt import _engine, _eql CREDENTIALS = ("CS_CLIENT_ACCESS_KEY", "CS_WORKSPACE_CRN", "CS_CLIENT_ID", "CS_CLIENT_KEY") DATABASE_URL = os.environ.get("EQL_TEST_DATABASE_URL", "") @@ -305,3 +307,94 @@ def test_100_queries_make_no_request(): assert _engine.request_count() == before assert len(terms) == 100 + + +# One field per producible type. JsonSearch is out of scope: the json kind +# has no Python type in this version, and the decorator refuses it. +PRODUCIBLE = [m for m in _eql.TYPES if m.producible and m.name != "JsonSearch"] + +# A value and its Python type for each plaintext kind, near the edge of +# what the kind holds. +KIND_VALUES = { + "string": (str, "héllo\x00wörld"), + "int16": (int, -(2**15)), + "int32": (int, 2**31 - 1), + "int64": (int, -(2**63)), + "float32": (float, 1.5), + "float64": (float, 0.1), + "bool": (bool, True), + "date": (datetime.date, datetime.date(2024, 2, 29)), + "timestamp": ( + datetime.datetime, + datetime.datetime( + 2024, 2, 29, 23, 59, 59, 999999, + tzinfo=datetime.timezone(datetime.timedelta(hours=10)), + ), + ), + "decimal": (decimal.Decimal, decimal.Decimal("-1234.5600")), +} + + +def every_type_model(): + cls = dataclasses.make_dataclass( + "EveryType", + [(m.name.lower(), Annotated[KIND_VALUES[m.plaintext][0], m]) for m in PRODUCIBLE], + ) + return stackencrypt.model("every_type")(cls) + + +def test_every_producible_type_except_json_search_is_covered(): + assert {m.plaintext for m in PRODUCIBLE} == set(KIND_VALUES) + assert len(PRODUCIBLE) == 49 + + +@needs_database +def test_every_producible_type_round_trips_through_postgres(): + psycopg = pytest.importorskip("psycopg") + from psycopg.rows import dict_row + + cls = every_type_model() + cipher = Cipher(cls) + model = cls(**{m.name.lower(): KIND_VALUES[m.plaintext][1] for m in PRODUCIBLE}) + [row] = cipher.encrypt([model]) + columns = [m.name.lower() for m in PRODUCIBLE] + with psycopg.connect(DATABASE_URL) as conn, conn.cursor(row_factory=dict_row) as cur: + cur.execute( + "CREATE TEMPORARY TABLE every_type (" + + ", ".join(f"{m.name.lower()} {m.sql_domain}" for m in PRODUCIBLE) + + ")" + ) + cur.execute( + f"INSERT INTO every_type ({', '.join(columns)}) " + f"VALUES ({', '.join(['%s'] * len(columns))})", + [row[column] for column in columns], + ) + cur.execute("SELECT * FROM every_type") + [decrypted] = cipher.decrypt(cur.fetchall()) + + assert decrypted == model + # The same instant, whatever zone it comes back in. + timestamp = KIND_VALUES["timestamp"][1] + assert decrypted.timestamp == timestamp + assert decrypted.timestamp.utcoffset() is not None + + +@stackencrypt.model("floats") +@dataclasses.dataclass +class Floats: + real: Annotated[float, stackencrypt.RealOrdOre] + double: Annotated[float, stackencrypt.DoubleEq] + + +@pytest.mark.parametrize("value", [math.nan, math.inf, -math.inf, -0.0], ids=repr) +def test_nan_infinity_and_negative_zero_round_trip(value): + cipher = Cipher(Floats) + + [floats] = cipher.decrypt(cipher.encrypt([Floats(real=value, double=value)])) + + for opened in (floats.real, floats.double): + if math.isnan(value): + assert math.isnan(opened) + else: + assert opened == value + assert math.copysign(1.0, opened) == math.copysign(1.0, value) diff --git a/languages/python/tests/test_values.py b/languages/python/tests/test_values.py new file mode 100644 index 000000000..ad68d52e0 --- /dev/null +++ b/languages/python/tests/test_values.py @@ -0,0 +1,147 @@ +"""The value rules, through ``encrypt`` and ``query`` alike. + +Every value is checked by exact type and range before any request, by one +function that both calls use. Each case here runs through both, and the +``untouched`` fixture fails a case that built the client or sent a request. + +A value that passes every check reaches the engine. With fake credentials +and no client key, the engine then fails to build its client: that failure +is a ``StashError`` that is not an ``InvalidValueError``, which shows the +checks passed. The round trip of each value is in ``test_encrypt_live.py``. +""" + +import dataclasses +import datetime +import decimal +import math +from typing import Annotated, Any, List, Tuple + +import pytest + +import stackencrypt +from stackencrypt import ( + BigintEq, + Boolean, + Cipher, + DateEq, + DoubleEq, + IntegerEq, + InvalidValueError, + NumericEq, + RealEq, + SmallintEq, + StashError, + TextEq, + TimestampEq, +) + +pytestmark = pytest.mark.usefixtures("credentials") + +UTC = datetime.timezone.utc +TEN_HOURS_AHEAD = datetime.timezone(datetime.timedelta(hours=10)) + +# The Python type each marker's field is declared with. +PYTHON_TYPE = { + SmallintEq: int, + IntegerEq: int, + BigintEq: int, + RealEq: float, + DoubleEq: float, + NumericEq: decimal.Decimal, + Boolean: bool, + DateEq: datetime.date, + TimestampEq: datetime.datetime, + TextEq: str, +} + + +def through_encrypt(marker: Any, value: Any) -> None: + cls = dataclasses.make_dataclass( + "Row", [("x", Annotated[PYTHON_TYPE[marker], marker])] + ) + Cipher(stackencrypt.model("rows")(cls)).encrypt([cls(x=value)]) + + +def through_query(marker: Any, value: Any) -> None: + cls = dataclasses.make_dataclass( + "Row", [("x", Annotated[PYTHON_TYPE[marker], marker])] + ) + Cipher(stackencrypt.model("rows")(cls)).query("x", value) + + +def calls(cases: List[Tuple[Any, ...]]) -> List[Any]: + """Each case through ``encrypt``, and through ``query`` where the + marker has an ``eq`` index. ``Boolean`` has none.""" + return [ + pytest.param(call, *case, id=f"{call.__name__}-{case[0].__name__}-{case[1]!r}") + for case in cases + for call in (through_encrypt, through_query) + if call is through_encrypt or "eq" in case[0].indexes + ] + + +# (marker, value, code, reason): one value per refusal the rules make. +REFUSED: List[Tuple[Any, Any, str, str]] = [ + (BigintEq, 2**63, "value_out_of_range", "int64"), + (BigintEq, -(2**63) - 1, "value_out_of_range", "int64"), + (IntegerEq, 2**31, "value_out_of_range", "int32"), + (SmallintEq, -(2**15) - 1, "value_out_of_range", "int16"), + (RealEq, 0.1, "value_not_representable", "float32"), + (NumericEq, decimal.Decimal("NaN"), "value_not_representable", "decimal"), + (NumericEq, decimal.Decimal("Infinity"), "value_not_representable", "decimal"), + (NumericEq, decimal.Decimal("1e-29"), "value_not_representable", "decimal"), + (TimestampEq, datetime.datetime(2024, 1, 1), "value_naive_datetime", "timestamp"), + (TextEq, None, "value_none", "string"), + (BigintEq, None, "value_none", "int64"), + (BigintEq, True, "value_wrong_type", "int64"), + (IntegerEq, 1.0, "value_wrong_type", "int32"), + (DoubleEq, 1, "value_wrong_type", "float64"), + (NumericEq, 1.5, "value_wrong_type", "decimal"), + (DateEq, datetime.datetime(2024, 1, 1, tzinfo=UTC), "value_wrong_type", "date"), + (TextEq, b"bytes", "value_wrong_type", "string"), + (Boolean, 1, "value_wrong_type", "bool"), +] + + +@pytest.mark.parametrize(("call", "marker", "value", "code", "reason"), calls(REFUSED)) +def test_a_value_that_does_not_fit_is_refused_before_any_request( + untouched, call, marker, value, code, reason +): + with pytest.raises(InvalidValueError) as raised: + call(marker, value) + + assert raised.value.code == code + assert raised.value.field == "x" + assert raised.value.reason == reason + + +# (marker, value): values at the edge of what each rule accepts. +ACCEPTED: List[Tuple[Any, Any]] = [ + (BigintEq, 2**63 - 1), + (BigintEq, -(2**63)), + (IntegerEq, 2**31 - 1), + (SmallintEq, -(2**15)), + (RealEq, 0.5), + (RealEq, math.nan), + (RealEq, math.inf), + (RealEq, -math.inf), + (RealEq, -0.0), + (DoubleEq, 0.1), + (DoubleEq, math.nan), + (DoubleEq, -math.inf), + (NumericEq, decimal.Decimal("-79228162514264337593543950335")), + (NumericEq, decimal.Decimal("1e-28")), + (Boolean, False), + (DateEq, datetime.date(1, 1, 1)), + (TimestampEq, datetime.datetime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC)), + (TimestampEq, datetime.datetime(2024, 1, 1, tzinfo=TEN_HOURS_AHEAD)), + (TextEq, ""), +] + + +@pytest.mark.parametrize(("call", "marker", "value"), calls(ACCEPTED)) +def test_a_value_at_the_edge_passes_every_check(call, marker, value): + with pytest.raises(StashError) as raised: + call(marker, value) + + assert not isinstance(raised.value, InvalidValueError), raised.value diff --git a/mise.toml b/mise.toml index f3f0e4d91..f90245169 100644 --- a/mise.toml +++ b/mise.toml @@ -359,6 +359,18 @@ RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --target wasm32-wasip1 mise x --env test -- cargo nextest run """ +# Fuzz the Python SDK's value conversion (stackencrypt-core's `values` +# module) both ways: a value the write check accepts must frame and read +# back as itself, and as the bytes the plain Rust type frames to; arbitrary +# bytes read as a passthrough must never panic. Same flags as the stack-* +# fuzz tasks. The fuzz crate lives in `languages/python/crates/core/fuzz/` +# (detached). The task is here, not in `languages/python/mise.toml`, +# because `fuzz.yml` runs every fuzz task from the repository root. +[tasks."fuzz:python-values"] +description = "Fuzz the Python SDK's value conversion, stackencrypt-core's values::write and values::read (libFuzzer, nightly, 60s default)" +dir = "{{config_root}}/languages/python/crates/core" +run = "mise x --env test -- cargo +nightly fuzz run value_frame --sanitizer none --target $(rustc -vV | sed -n 's/^host: //p') -- -max_total_time=60" + [tasks."go:test"] description = "Format check, vet and test the Go module (languages/golang: encrypt, auth and the internal packages) against the embedded guests; needs `wasm:guest:build` and `wasm:auth-guest:build` first" # The body lives in scripts/go-binding-test.sh so the macOS and Windows CI diff --git a/scripts/__tests__/cargo-lock-freshness.test.mjs b/scripts/__tests__/cargo-lock-freshness.test.mjs index 74ebd575d..2083a1b66 100644 --- a/scripts/__tests__/cargo-lock-freshness.test.mjs +++ b/scripts/__tests__/cargo-lock-freshness.test.mjs @@ -184,15 +184,16 @@ describe('Cargo.lock records this tree’s crates at their real versions', () => ).toEqual([ 'languages/golang/encrypt/guest/Cargo.lock', 'languages/python/Cargo.lock', + 'languages/python/crates/core/fuzz/Cargo.lock', 'languages/typescript/packages/protect-ffi/Cargo.lock', 'packages/eql/Cargo.lock', ]) }) - it('finds the root lock and the six detached stack-* locks', () => { - // The root workspace and the workspaces it excludes: the three cargo-fuzz - // crates, the two Go WASI guests and the Python SDK, each with its own - // lock. + it('finds the root lock and the seven detached stack-* locks', () => { + // The root workspace and the workspaces it excludes: the three stack-* + // cargo-fuzz crates, the two Go WASI guests, the Python SDK and its core + // crate's cargo-fuzz crate, each with its own lock. expect(LOCKS).toEqual( expect.arrayContaining([ 'Cargo.lock', @@ -202,6 +203,7 @@ describe('Cargo.lock records this tree’s crates at their real versions', () => 'languages/golang/encrypt/guest/Cargo.lock', 'languages/golang/auth/guest/Cargo.lock', 'languages/python/Cargo.lock', + 'languages/python/crates/core/fuzz/Cargo.lock', ]), ) }) diff --git a/scripts/__tests__/cargo-publish-opt-out.test.mjs b/scripts/__tests__/cargo-publish-opt-out.test.mjs index 5a02b6b37..d21a05ec6 100644 --- a/scripts/__tests__/cargo-publish-opt-out.test.mjs +++ b/scripts/__tests__/cargo-publish-opt-out.test.mjs @@ -7,7 +7,7 @@ import { REPO_ROOT } from './lib/repo-root.mjs' * Every crate in every Cargo workspace must opt out of crates.io unless it is * deliberately allowlisted below: the root workspace (the stack-* crates), * EQL, protect-ffi, the Python SDK, and the single-package workspaces the root excludes (the - * three cargo-fuzz crates and the two Go WASI guests). + * four cargo-fuzz crates and the two Go WASI guests). * * A crate with no `publish` key is publishable BY DEFAULT, and release-plz * publishes every workspace member that has not opted out. So every member @@ -82,6 +82,7 @@ const WORKSPACES = [ 'packages/stack-auth/fuzz', 'packages/stack-kms/fuzz', 'packages/stack-encrypt/fuzz', + 'languages/python/crates/core/fuzz', 'languages/golang/encrypt/guest', 'languages/golang/auth/guest', ].map((root) => ({ root, publishable: new Set(), expects: '.' })), From b52357e65a28f026a6445f77d0cd5dd788e0fc25 Mon Sep 17 00:00:00 2001 From: Yuji Yokoo Date: Tue, 3 Nov 2026 00:56:26 +0900 Subject: [PATCH 2/4] fix(python): address the review of the value rules (#1392) Run fuzz:python-values and the campaign's cmin step from the root, naming the fuzz crate with --fuzz-dir. From a folder under languages/python, mise also loads that folder's mise.toml, and installs its Python and uv, which the fuzz jobs do not cache. Compare each field of the 49-type live round trip exactly: a Decimal by as_tuple(), so its scale counts, and a float by its bytes, so the sign of zero counts. Python's == treats 1.50 as 1.5 and -0.0 as 0.0. Check the normalized vectors against the corpus's reencode_value, not against a second read of the same bytes. The canonical value must write back as the reencode bytes, and a vector whose two byte strings are equal fails. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/fuzz.yml | 9 +++-- .../crates/core/tests/value_conformance.rs | 38 +++++++++++++++++-- languages/python/tests/test_encrypt_live.py | 24 +++++++++--- mise.toml | 7 +++- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 953735b60..d15e8d00a 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -177,13 +177,16 @@ jobs: run: mise run ${{ matrix.task }} -- "-max_total_time=$MAX_TOTAL_TIME" # Drops inputs that add no coverage, so the saved corpus stays small. - # Skipped when the fuzz step found a crash. + # Skipped when the fuzz step found a crash. Run from the root, naming + # the fuzz crate: from languages/python, mise would load that folder's + # mise.toml and install its Python and uv, which this job does not + # cache. - name: Minimise the corpus (${{ matrix.slug }}) - working-directory: ${{ matrix.dir }} env: TARGET: ${{ matrix.target }} + FUZZ_DIR: ${{ matrix.dir }}/fuzz run: | - cargo +nightly fuzz cmin "$TARGET" --sanitizer none --target "$(rustc -vV | sed -n 's/^host: //p')" + cargo +nightly fuzz cmin "$TARGET" --fuzz-dir "$FUZZ_DIR" --sanitizer none --target "$(rustc -vV | sed -n 's/^host: //p')" # Saved even on a crash: the grown corpus is still worth keeping, and the # crash input lives in artifacts/, not corpus/. diff --git a/languages/python/crates/core/tests/value_conformance.rs b/languages/python/crates/core/tests/value_conformance.rs index b8d203f89..74ff188b2 100644 --- a/languages/python/crates/core/tests/value_conformance.rs +++ b/languages/python/crates/core/tests/value_conformance.rs @@ -233,14 +233,46 @@ fn every_malformed_vector_is_refused() { #[test] fn every_normalized_vector_reads_as_its_canonical_value() { let corpus = corpus(); + let mut written = 0; for vector in corpus["normalized"].as_array().expect("normalized") { let name = vector["name"].as_str().expect("a name"); let transport = unhex(vector["transport"].as_str().expect("transport")); let reencode = unhex(vector["reencode"].as_str().expect("reencode")); + assert_ne!( + transport, reencode, + "{name}: a vector that normalizes nothing" + ); + // The canonical value, as the corpus states it, read as a vector. + let canonical = serde_json::json!({ + "kind": vector["kind"], + "value": vector["reencode_value"], + }); + let (mut text, mut bytes) = (String::new(), Vec::new()); + let expected = match vector["kind"].as_str() { + // A typed null is a null leaf padded to its type's length: it + // reads as a plain null. + Some("null") => None, + _ => Some(host(&canonical, &mut text, &mut bytes).expect("a kind the SDK writes")), + }; - let read = values::read(&transport); + let read = + values::read(&transport).unwrap_or_else(|error| panic!("{name}: refused: {error:?}")); - let read = read.unwrap_or_else(|error| panic!("{name}: refused: {error:?}")); - assert_eq!(Ok(read), values::read(&reencode), "{name}"); + match expected { + None => assert_eq!(read, Scalar::Null, "{name}"), + Some((kind, expected)) => { + assert_eq!(read, expected, "{name}: the read value"); + // Written back, the canonical value is the canonical bytes. + let frame = ValueFrame::of(Written(kind, value_of(&read))).expect("frames"); + assert_eq!( + frame.risky_ref(), + &reencode[..], + "{name}: the written bytes" + ); + written += 1; + } + } } + // decimal-negative-zero, at this commit. + assert_eq!(written, 1); } diff --git a/languages/python/tests/test_encrypt_live.py b/languages/python/tests/test_encrypt_live.py index 124f0d5dc..b9a6d1acc 100644 --- a/languages/python/tests/test_encrypt_live.py +++ b/languages/python/tests/test_encrypt_live.py @@ -14,6 +14,7 @@ import json import math import os +import struct from typing import Annotated, List, NamedTuple import attrs @@ -372,11 +373,24 @@ def test_every_producible_type_round_trips_through_postgres(): cur.execute("SELECT * FROM every_type") [decrypted] = cipher.decrypt(cur.fetchall()) - assert decrypted == model - # The same instant, whatever zone it comes back in. - timestamp = KIND_VALUES["timestamp"][1] - assert decrypted.timestamp == timestamp - assert decrypted.timestamp.utcoffset() is not None + for marker in PRODUCIBLE: + name = marker.name.lower() + assert exact(getattr(decrypted, name)) == exact(getattr(model, name)), name + + +def exact(value): + """A value in a form whose == also compares what Python's == does not: + a Decimal's scale (1.50 == 1.5 in Python), a float's sign (-0.0 == 0.0), + and a datetime's instant with no tzinfo dropped.""" + if isinstance(value, decimal.Decimal): + return value.as_tuple() + if isinstance(value, float): + return struct.pack(" Date: Tue, 3 Nov 2026 01:09:18 +0900 Subject: [PATCH 3/4] fix(python): derive fuzz.yml's path filter from the fuzz locks (#1392) fuzz.yml's pull_request filter missed four paths that a fuzz target compiles: stack-profile, stack-encrypt-derive, eql-bindings, and the Go guest's targets.rs, which the Python core crate compiles by #[path]. A change to any of them could break a harness with no job to notice. The stack-* fuzz crates already compiled the first two. Add the four paths. The new fuzz-workflow-paths.test.mjs reads each fuzz crate's Cargo.lock and fails when the filter misses a crate a target compiles, or a file one compiles by #[path]. Move the frame writer into values::Typed, and the scalar-to-value conversion into Scalar::value(). They replace three Written copies, Carried in decrypt.rs, Typed in query.rs, and two value_of copies. Correct the fuzz comment: values::read range-checks a timestamp but not a date or a decimal, so those are the cases read accepts and write refuses. The testdata README now names the two counts the corpus test checks, which a corpus update may change. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/fuzz.yml | 12 +- docs/fuzzing.md | 4 +- .../core/fuzz/fuzz_targets/value_frame.rs | 46 ++---- languages/python/crates/core/src/decrypt.rs | 16 +-- languages/python/crates/core/src/query.rs | 16 +-- languages/python/crates/core/src/values.rs | 47 ++++-- .../python/crates/core/testdata/README.md | 7 +- .../crates/core/tests/value_conformance.rs | 36 +---- .../__tests__/fuzz-workflow-paths.test.mjs | 135 ++++++++++++++++++ 9 files changed, 209 insertions(+), 110 deletions(-) create mode 100644 scripts/__tests__/fuzz-workflow-paths.test.mjs diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index d15e8d00a..a46232091 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -25,10 +25,18 @@ on: - packages/stack-auth/** - packages/stack-kms/** - packages/stack-encrypt/** - # The Python SDK's core crate and the manifest its dependencies come - # from: the fuzz:python-values target. + # Every other crate a target compiles, from each fuzz crate's + # Cargo.lock: scripts/__tests__/fuzz-workflow-paths.test.mjs derives + # this list and fails when a path dependency is missing from it. + - packages/stack-profile/** + - packages/stack-encrypt-derive/** + - packages/eql/crates/eql-bindings/** + # The Python SDK's core crate, the manifest its dependencies come + # from, and the Go guest's EQL resolver it compiles by #[path]: the + # fuzz:python-values target. - languages/python/crates/core/** - languages/python/Cargo.toml + - languages/golang/encrypt/guest/src/targets.rs - Cargo.toml - Cargo.lock - mise.toml diff --git a/docs/fuzzing.md b/docs/fuzzing.md index 0f4dd1441..bc07080f9 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -133,11 +133,13 @@ Two jobs with deliberately different roles: The `pull_request` trigger is path-filtered to these paths: - `packages/stack-auth/**`, `packages/stack-kms/**`, and `packages/stack-encrypt/**` -- `languages/python/crates/core/**` and `languages/python/Cargo.toml` +- the other crates a target compiles: `packages/stack-profile/**`, `packages/stack-encrypt-derive/**`, and `packages/eql/crates/eql-bindings/**` +- `languages/python/crates/core/**`, `languages/python/Cargo.toml`, and `languages/golang/encrypt/guest/src/targets.rs` - the root `Cargo.toml`, `Cargo.lock`, and `mise.toml` - the workflow file The `!**.md` and `!**.example` excludes come last, so a change to documents only is skipped. +`scripts/__tests__/fuzz-workflow-paths.test.mjs` reads each fuzz crate's `Cargo.lock`. It fails when the filter misses a crate that a target compiles. ## Adding a new target diff --git a/languages/python/crates/core/fuzz/fuzz_targets/value_frame.rs b/languages/python/crates/core/fuzz/fuzz_targets/value_frame.rs index 07ef6d21c..7d6fcacfc 100644 --- a/languages/python/crates/core/fuzz/fuzz_targets/value_frame.rs +++ b/languages/python/crates/core/fuzz/fuzz_targets/value_frame.rs @@ -17,10 +17,8 @@ use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; -use stack_encrypt::dynamic::plaintext::Sink; use stack_encrypt::dynamic::ValueFrame; -use stack_encrypt::Encrypt; -use stackencrypt_core::values::{self, Kind, Scalar, Value}; +use stackencrypt_core::values::{self, Kind, Scalar, Typed, Value}; #[derive(Arbitrary, Debug)] enum Input { @@ -93,14 +91,6 @@ impl Host { } } -struct Written<'a>(Option, Value<'a>); - -impl Encrypt for Written<'_> { - fn visit(self, sink: S) -> Result { - values::write(sink, self.0, self.1) - } -} - /// The scalar an accepted value reads back as: the value, narrowed to its /// kind. fn expected(kind: Option, value: Value<'_>) -> Scalar { @@ -147,24 +137,6 @@ fn same(a: &Scalar, b: &Scalar) -> bool { } } -/// The scalar as the value the SDK writes, or `None` for a null. -fn value_of(scalar: &Scalar) -> Option> { - Some(match scalar { - Scalar::Null => return None, - Scalar::Text(s) => Value::Text(s), - Scalar::Int(i) => Value::Int(*i), - Scalar::Float(f) => Value::Float(*f), - Scalar::Bool(b) => Value::Bool(*b), - Scalar::Bytes(b) => Value::Bytes(b), - Scalar::Date(d) => Value::Date(i64::from(*d)), - Scalar::Timestamp(t) => Value::Timestamp(*t), - Scalar::Decimal { mantissa, scale } => Value::Decimal { - mantissa: *mantissa, - scale: u32::from(*scale), - }, - }) -} - fuzz_target!(|input: Input| { match input { Input::Write(kind, host) => { @@ -172,7 +144,7 @@ fuzz_target!(|input: Input| { if values::scalar(kind, value).is_err() { return; } - let frame = ValueFrame::of(Written(kind, value)).expect("an accepted value frames"); + let frame = ValueFrame::of(Typed(kind, value)).expect("an accepted value frames"); let read = values::read(frame.risky_ref()).expect("a written frame reads"); assert!( same(&read, &expected(kind, value)), @@ -186,17 +158,19 @@ fuzz_target!(|input: Input| { let Ok(read) = values::read(&bytes) else { return; }; - let Some(value) = value_of(&read) else { + let Some(value) = read.value() else { return; }; - // A frame can hold a timestamp in seconds whose count, in - // nanoseconds, is past the range the SDK writes. The engine only - // returns a passthrough the SDK wrote, so read accepting more - // than write is not a defect. + // `values::read` range-checks a timestamp, but not a date or a + // decimal: it reads any `i32` day count, and any scale and + // mantissa. `values::scalar` refuses a date outside the engine's + // range, a scale over 28, and a mantissa over 96 bits. The engine + // only returns a passthrough the SDK wrote, so read accepting + // more than write is not a defect. if values::scalar(None, value).is_err() { return; } - let frame = ValueFrame::of(Written(None, value)).expect("a read scalar writes back"); + let frame = ValueFrame::of(Typed(None, value)).expect("a read scalar writes back"); let again = values::read(frame.risky_ref()).expect("a written frame reads"); assert!(same(&again, &read), "{again:?} after {read:?}"); } diff --git a/languages/python/crates/core/src/decrypt.rs b/languages/python/crates/core/src/decrypt.rs index 667206201..9a15187f9 100644 --- a/languages/python/crates/core/src/decrypt.rs +++ b/languages/python/crates/core/src/decrypt.rs @@ -24,7 +24,7 @@ use crate::encrypt::{engine_fault, Field, ModelPlan}; use crate::plan; use crate::refusal::Refusal; use crate::targets; -use crate::values::{self, Misfit, Never, ReadError, Scalar, Value}; +use crate::values::{self, Misfit, Never, ReadError, Scalar, Typed, Value}; /// The stored rows of one `decrypt` call, as the engine's record tree. #[derive(Default)] @@ -112,7 +112,7 @@ impl Row { pub fn passthrough(&mut self, field: &Field, value: Value<'_>) -> Result<(), Misfit> { values::scalar(None, value)?; // A value that fits always writes. - let frame = ValueFrame::of(Carried(value)).map_err(|_| Misfit::OutOfRange)?; + let frame = ValueFrame::of(Typed(None, value)).map_err(|_| Misfit::OutOfRange)?; self.add(field, Output::Passthrough.key(), frame.risky_ref().to_vec()); Ok(()) } @@ -261,16 +261,6 @@ fn read_rows( Ok(rows) } -/// A value the engine reads as it is: what [`Row`] stores. -#[derive(Clone, Copy)] -struct Carried<'a>(Value<'a>); - -impl Encrypt for Carried<'_> { - fn visit(self, sink: S) -> Result { - values::write(sink, None, self.0) - } -} - fn not_expected(what: &'static str) -> Result { Err(ReadError(what)) } @@ -555,7 +545,7 @@ pub(crate) mod tests { #[test] fn an_eql_frame_is_the_frame_of_one_bytes_scalar() { let json = br#"{"i":{"t":"users","c":"email"}}"#; - let scalar = ValueFrame::of(Carried(Value::Bytes(json))).unwrap(); + let scalar = ValueFrame::of(Typed(None, Value::Bytes(json))).unwrap(); assert_eq!(eql_frame(json), scalar.risky_ref()); } diff --git a/languages/python/crates/core/src/query.rs b/languages/python/crates/core/src/query.rs index b594b6d8c..5df850c43 100644 --- a/languages/python/crates/core/src/query.rs +++ b/languages/python/crates/core/src/query.rs @@ -6,17 +6,16 @@ //! values' `eq_term`. A term is derived from the index key in the loaded //! keyset, so no ZeroKMS request is made once the shared client is built. -use stack_encrypt::dynamic::plaintext::Sink; use stack_encrypt::dynamic::{self, OpSpec, Operand}; use stack_encrypt::kms::{DataKeySource, IndexKeySource}; -use stack_encrypt::{Encrypt, KeysetCipher}; +use stack_encrypt::KeysetCipher; use crate::client; use crate::encrypt::{engine_fault, ModelPlan}; use crate::plan; use crate::refusal::Refusal; use crate::targets; -use crate::values::{self, Kind, Value}; +use crate::values::{Kind, Typed, Value}; /// The equality query term for `value` on the sealed field `field` of /// `plan`, as JSON text. @@ -60,7 +59,7 @@ where plan.plan(), field, OpSpec::Eq, - Typed(kind, value), + Typed(Some(kind), value), &targets::resolver(), ) .map_err(|error| plan::refusal(&error))? @@ -99,15 +98,6 @@ pub fn answers_equality(plan: &ModelPlan, field: &str) -> Result<(), Refusal> { }) } -/// A value written as the field's kind, as `encrypt` writes it. -struct Typed<'a>(Kind, Value<'a>); - -impl Encrypt for Typed<'_> { - fn visit(self, sink: S) -> Result { - values::write(sink, Some(self.0), self.1) - } -} - #[cfg(test)] mod tests { use stack_encrypt::StackCipher; diff --git a/languages/python/crates/core/src/values.rs b/languages/python/crates/core/src/values.rs index d19edecd7..4efdde9c7 100644 --- a/languages/python/crates/core/src/values.rs +++ b/languages/python/crates/core/src/values.rs @@ -135,6 +135,19 @@ pub fn write(sink: S, kind: Option, value: Value<'_>) -> Result(pub Option, pub Value<'a>); + +impl Encrypt for Typed<'_> { + fn visit(self, sink: S) -> Result { + write(sink, self.0, self.1) + } +} + /// The engine's scalar for `value` under `kind`. /// /// # Errors @@ -235,6 +248,27 @@ pub enum Scalar { }, } +impl Scalar { + /// The scalar as the value the SDK writes, or `None` for a null, which + /// the SDK never writes. + pub fn value(&self) -> Option> { + Some(match self { + Self::Null => return None, + Self::Text(s) => Value::Text(s), + Self::Int(i) => Value::Int(*i), + Self::Float(f) => Value::Float(*f), + Self::Bool(b) => Value::Bool(*b), + Self::Bytes(b) => Value::Bytes(b), + Self::Date(d) => Value::Date(i64::from(*d)), + Self::Timestamp(t) => Value::Timestamp(*t), + Self::Decimal { mantissa, scale } => Value::Decimal { + mantissa: *mantissa, + scale: u32::from(*scale), + }, + }) + } +} + /// Read the one scalar a passthrough frame holds. /// /// # Errors @@ -481,7 +515,7 @@ mod tests { }, ), ] { - let frame = ValueFrame::of(Written { kind, value }).unwrap(); + let frame = ValueFrame::of(Typed(kind, value)).unwrap(); assert_eq!(read(frame.risky_ref()).unwrap(), expected, "{value:?}"); } } @@ -492,15 +526,4 @@ mod tests { let seq = ValueFrame::of(vec!["a", "b"]).unwrap(); assert!(read(seq.risky_ref()).is_err()); } - - struct Written<'a> { - kind: Option, - value: Value<'a>, - } - - impl Encrypt for Written<'_> { - fn visit(self, sink: S) -> Result { - write(sink, self.kind, self.value) - } - } } diff --git a/languages/python/crates/core/testdata/README.md b/languages/python/crates/core/testdata/README.md index b8d35c296..18adac480 100644 --- a/languages/python/crates/core/testdata/README.md +++ b/languages/python/crates/core/testdata/README.md @@ -22,4 +22,9 @@ gh api "repos/cipherstash/vitaminc/contents/testdata/value-conformance.json?ref= git hash-object value-conformance.json ``` -Then record the new commit and blob hash in the table above. `cargo test` fails on a new vector of a kind the SDK writes, until the test handles it. +Then record the new commit and blob hash in the table above. + +`cargo test` fails on a new vector in two cases: + +- The vector's kind is one the test does not know. Handle the kind in `host`, or add the vector's name to `NOT_WRITTEN`. +- The vector changes one of the two counts the test checks. `assert_eq!(written, 19)` counts the vectors the SDK writes. `assert_eq!(written, 1)` counts the normalized vectors the SDK writes back. Check that each new vector is one the test should count, then update the number. diff --git a/languages/python/crates/core/tests/value_conformance.rs b/languages/python/crates/core/tests/value_conformance.rs index 74ff188b2..68c8e09b5 100644 --- a/languages/python/crates/core/tests/value_conformance.rs +++ b/languages/python/crates/core/tests/value_conformance.rs @@ -12,10 +12,8 @@ //! vector in the corpus fails here until it is handled or listed. use serde_json::Value as Json; -use stack_encrypt::dynamic::plaintext::Sink; use stack_encrypt::dynamic::ValueFrame; -use stack_encrypt::Encrypt; -use stackencrypt_core::values::{self, Kind, Scalar, Value}; +use stackencrypt_core::values::{self, Kind, Scalar, Typed}; const CORPUS: &str = include_str!("../testdata/value-conformance.json"); @@ -53,15 +51,6 @@ fn unhex(text: &str) -> Vec { .collect() } -/// A value written as `kind`, or as a passthrough for `None`. -struct Written<'a>(Option, Value<'a>); - -impl Encrypt for Written<'_> { - fn visit(self, sink: S) -> Result { - values::write(sink, self.0, self.1) - } -} - /// The SDK's kind for a vector, its value, and the scalar it reads back as. /// `None` for a vector the SDK does not write. fn host(vector: &Json, text: &mut String, bytes: &mut Vec) -> Option<(Option, Scalar)> { @@ -114,24 +103,6 @@ fn host(vector: &Json, text: &mut String, bytes: &mut Vec) -> Option<(Option }) } -/// The scalar as the value the SDK writes. -fn value_of<'a>(scalar: &'a Scalar) -> Value<'a> { - match scalar { - Scalar::Bool(b) => Value::Bool(*b), - Scalar::Int(i) => Value::Int(*i), - Scalar::Float(f) => Value::Float(*f), - Scalar::Text(s) => Value::Text(s), - Scalar::Bytes(b) => Value::Bytes(b), - Scalar::Date(d) => Value::Date(i64::from(*d)), - Scalar::Timestamp(t) => Value::Timestamp(*t), - Scalar::Decimal { mantissa, scale } => Value::Decimal { - mantissa: *mantissa, - scale: u32::from(*scale), - }, - Scalar::Null => panic!("the SDK never writes a null"), - } -} - /// Days since 1970-01-01 of a proleptic Gregorian date, `[+-]YYYY-MM-DD`. fn days(date: &str) -> i32 { let (sign, date) = match date.strip_prefix('-') { @@ -201,7 +172,7 @@ fn every_vector_the_sdk_writes_gives_its_bytes_and_reads_back() { ); let transport = unhex(vector["transport"].as_str().expect("transport")); - let frame = ValueFrame::of(Written(kind, value_of(&expected))) + let frame = ValueFrame::of(Typed(kind, expected.value().expect("not a null"))) .unwrap_or_else(|error| panic!("{name}: refused: {error}")); assert_eq!( @@ -263,7 +234,8 @@ fn every_normalized_vector_reads_as_its_canonical_value() { Some((kind, expected)) => { assert_eq!(read, expected, "{name}: the read value"); // Written back, the canonical value is the canonical bytes. - let frame = ValueFrame::of(Written(kind, value_of(&read))).expect("frames"); + let frame = + ValueFrame::of(Typed(kind, read.value().expect("not a null"))).expect("frames"); assert_eq!( frame.risky_ref(), &reencode[..], diff --git a/scripts/__tests__/fuzz-workflow-paths.test.mjs b/scripts/__tests__/fuzz-workflow-paths.test.mjs new file mode 100644 index 000000000..be50d5cb1 --- /dev/null +++ b/scripts/__tests__/fuzz-workflow-paths.test.mjs @@ -0,0 +1,135 @@ +import { existsSync, readdirSync, readFileSync } from 'node:fs' +import { join, posix } from 'node:path' +import { describe, expect, it } from 'vitest' +import { filterCovers, negationRemovesAll } from './lib/paths-filter.mjs' +import { REPO_ROOT } from './lib/repo-root.mjs' +import { readWorkflow, triggers } from './lib/workflows.mjs' + +/** + * `fuzz.yml`'s `pull_request` filter is held to the crates its fuzz targets + * compile, read from each fuzz crate's own `Cargo.lock`. + * + * The regression job is the only thing that builds a harness on a pull + * request. A change to a crate a harness compiles, and the filter does not + * name, can break the harness with no job to notice. Each fuzz crate is a + * detached workspace, so its `Cargo.lock` lists every crate it compiles from + * this tree as a package with no `source`. A new path dependency appears + * there, and this file fails until the filter follows. + * + * The fuzz crates are the campaign matrix's `dir` values, each with `/fuzz`. + * `python-ci.test.mjs` holds `tests-python.yml` to the Python workspace the + * same way. + */ + +const WORKFLOW = '.github/workflows/fuzz.yml' + +const read = (rel) => readFileSync(join(REPO_ROOT, rel), 'utf8') + +/** The fuzz crates, from the campaign matrix. */ +function fuzzCrates() { + const job = readWorkflow(WORKFLOW).jobs['fuzz-campaign'] + return [ + ...new Set(job.strategy.matrix.include.map((row) => `${row.dir}/fuzz`)), + ].sort() +} + +/** Where a path package of this tree lives, checked against its manifest. */ +function crateDir(name) { + const candidates = [ + `packages/${name}`, + `packages/eql/crates/${name}`, + `languages/python/crates/${name.replace(/^stackencrypt-/, '')}`, + ] + const dir = candidates.find((d) => { + const manifest = join(REPO_ROOT, d, 'Cargo.toml') + return ( + existsSync(manifest) && + new RegExp(`^name = "${name}"$`, 'm').test(read(`${d}/Cargo.toml`)) + ) + }) + if (!dir) { + throw new Error( + `a fuzz Cargo.lock lists ${name} as a path dependency, and none of ${candidates.join(', ')} holds its Cargo.toml; teach this test where it lives`, + ) + } + return dir +} + +/** The directories of the path packages a fuzz crate compiles. */ +function pathDependencyTrees(fuzzCrate) { + const trees = [] + for (const block of read(`${fuzzCrate}/Cargo.lock`) + .split('[[package]]') + .slice(1)) { + if (/^source = /m.test(block)) continue + const name = /^name = "([^"]+)"/m.exec(block)?.[1] + if (!name) throw new Error(`${fuzzCrate}/Cargo.lock: a package with no name`) + if (name.endsWith('-fuzz')) continue + trees.push(crateDir(name)) + } + return trees +} + +/** The files a crate compiles through `#[path = "..."]`, from its `src/`. */ +function pathModuleFiles(crate) { + const files = [] + const walk = (dir) => { + if (!existsSync(join(REPO_ROOT, dir))) return + for (const entry of readdirSync(join(REPO_ROOT, dir), { + withFileTypes: true, + })) { + const rel = `${dir}/${entry.name}` + if (entry.isDirectory()) walk(rel) + else if (entry.name.endsWith('.rs')) { + const attribute = /#\[\s*path\s*=\s*"([^"]+)"\s*\]/g + for (const match of read(rel).matchAll(attribute)) { + files.push(posix.normalize(`${dir}/${match[1]}`)) + } + } + } + } + walk(`${crate}/src`) + return files +} + +const coversTree = (pattern, file) => + pattern.endsWith('/**') && file.startsWith(pattern.slice(0, -2)) + +const covers = (pattern, file) => pattern === file || coversTree(pattern, file) + +describe('fuzz.yml', () => { + const paths = triggers(readWorkflow(WORKFLOW)).pull_request?.paths ?? [] + // `!**.md` and `!**.example` drop documents only, not a whole tree. + const selected = (file) => + filterCovers(paths, file, covers, negationRemovesAll) + + it('finds every fuzz crate, the Python one included', () => { + expect(fuzzCrates()).toEqual([ + 'languages/python/crates/core/fuzz', + 'packages/stack-auth/fuzz', + 'packages/stack-encrypt/fuzz', + 'packages/stack-kms/fuzz', + ]) + }) + + it('runs on pull requests touching every crate a fuzz target compiles', () => { + const trees = [...new Set(fuzzCrates().flatMap(pathDependencyTrees))] + expect(trees).toContain('packages/eql/crates/eql-bindings') + const missing = trees.filter((dir) => !selected(`${dir}/src/lib.rs`)) + expect( + missing, + `pull_request.paths does not select these crates, which a fuzz target compiles:\n${missing.join('\n')}`, + ).toEqual([]) + }) + + it('runs on pull requests touching a file those crates compile by #[path]', () => { + const trees = [...new Set(fuzzCrates().flatMap(pathDependencyTrees))] + const files = trees.flatMap(pathModuleFiles) + expect(files).toContain('languages/golang/encrypt/guest/src/targets.rs') + const missing = files.filter((file) => !selected(file)) + expect( + missing, + `pull_request.paths does not select these files, which a fuzzed crate compiles by #[path]:\n${missing.join('\n')}`, + ).toEqual([]) + }) +}) From 10bdd53a75bfa6301c034e9920062657c8420b8b Mon Sep 17 00:00:00 2001 From: Yuji Yokoo Date: Tue, 3 Nov 2026 01:21:33 +0900 Subject: [PATCH 4/4] test(python): read the fuzz crates' compiled files with the shared walk (#1392) fuzz-workflow-paths.test.mjs scanned #[path] with its own regex over raw source. It read only each crate's src/, and counted a #[path] quoted in a comment. It now uses compiledCrateFiles from lib/rust-compiled-files.mjs, which strips comments and follows mod, The lockfile walk moves to lib/cargo-lock.mjs, and python-ci.test.mjs uses it too. The corpus test now reads a typed null's reencode bytes, which must be a plain null. The fuzz.yml header no longer counts six targets, and docs/fuzzing.md lists mise.test.toml in the path filter. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/fuzz.yml | 5 +- docs/fuzzing.md | 2 +- .../crates/core/tests/value_conformance.rs | 6 +- .../__tests__/fuzz-workflow-paths.test.mjs | 99 ++++++++++++------- scripts/__tests__/lib/cargo-lock.mjs | 17 ++++ scripts/__tests__/python-ci.test.mjs | 7 +- 6 files changed, 90 insertions(+), 46 deletions(-) create mode 100644 scripts/__tests__/lib/cargo-lock.mjs diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index a46232091..5664817e5 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -3,8 +3,9 @@ name: Fuzz (crates) # libFuzzer fuzzing for the stack crates' untrusted-input parsers (the # detached `packages/*/fuzz/` crates and the `fuzz:*` mise tasks), and the # Python SDK's value conversion (`languages/python/crates/core/fuzz/`). Ported from -# cipherstash-suite's `fuzz.yml`, for the six targets in the imported set; the -# suite keeps its three cts-common targets. Two jobs with different roles: +# cipherstash-suite's `fuzz.yml`, which keeps its three cts-common targets. +# The two matrices below list every target here. Two jobs with different +# roles: # # * fuzz-regression (pull_request and manual, blocking on PRs): builds every harness, which # catches harness and API drift, and replays the committed seed corpus with diff --git a/docs/fuzzing.md b/docs/fuzzing.md index bc07080f9..aae873397 100644 --- a/docs/fuzzing.md +++ b/docs/fuzzing.md @@ -135,7 +135,7 @@ The `pull_request` trigger is path-filtered to these paths: - `packages/stack-auth/**`, `packages/stack-kms/**`, and `packages/stack-encrypt/**` - the other crates a target compiles: `packages/stack-profile/**`, `packages/stack-encrypt-derive/**`, and `packages/eql/crates/eql-bindings/**` - `languages/python/crates/core/**`, `languages/python/Cargo.toml`, and `languages/golang/encrypt/guest/src/targets.rs` -- the root `Cargo.toml`, `Cargo.lock`, and `mise.toml` +- the root `Cargo.toml`, `Cargo.lock`, `mise.toml`, and `mise.test.toml` - the workflow file The `!**.md` and `!**.example` excludes come last, so a change to documents only is skipped. diff --git a/languages/python/crates/core/tests/value_conformance.rs b/languages/python/crates/core/tests/value_conformance.rs index 68c8e09b5..f06e9221c 100644 --- a/languages/python/crates/core/tests/value_conformance.rs +++ b/languages/python/crates/core/tests/value_conformance.rs @@ -230,7 +230,11 @@ fn every_normalized_vector_reads_as_its_canonical_value() { values::read(&transport).unwrap_or_else(|error| panic!("{name}: refused: {error:?}")); match expected { - None => assert_eq!(read, Scalar::Null, "{name}"), + None => { + assert_eq!(read, Scalar::Null, "{name}"); + // The reencode bytes are the plain null they write back as. + assert_eq!(values::read(&reencode), Ok(Scalar::Null), "{name}"); + } Some((kind, expected)) => { assert_eq!(read, expected, "{name}: the read value"); // Written back, the canonical value is the canonical bytes. diff --git a/scripts/__tests__/fuzz-workflow-paths.test.mjs b/scripts/__tests__/fuzz-workflow-paths.test.mjs index be50d5cb1..af45e7ac8 100644 --- a/scripts/__tests__/fuzz-workflow-paths.test.mjs +++ b/scripts/__tests__/fuzz-workflow-paths.test.mjs @@ -1,8 +1,14 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs' -import { join, posix } from 'node:path' +import { dirname, join, relative, resolve } from 'node:path' import { describe, expect, it } from 'vitest' +import { pathPackageNames } from './lib/cargo-lock.mjs' import { filterCovers, negationRemovesAll } from './lib/paths-filter.mjs' import { REPO_ROOT } from './lib/repo-root.mjs' +import { + compiledCrateFiles, + includes, + moduleDeclarations, +} from './lib/rust-compiled-files.mjs' import { readWorkflow, triggers } from './lib/workflows.mjs' /** @@ -17,8 +23,10 @@ import { readWorkflow, triggers } from './lib/workflows.mjs' * there, and this file fails until the filter follows. * * The fuzz crates are the campaign matrix's `dir` values, each with `/fuzz`. - * `python-ci.test.mjs` holds `tests-python.yml` to the Python workspace the - * same way. + * The files each crate compiles come from `compiledCrateFiles`, which + * follows `mod`, `#[path]` and `include_str!` with comments stripped, with + * every feature counted as on. `python-ci.test.mjs` holds `tests-python.yml` + * to the Python workspace the same way. */ const WORKFLOW = '.github/workflows/fuzz.yml' @@ -57,41 +65,54 @@ function crateDir(name) { /** The directories of the path packages a fuzz crate compiles. */ function pathDependencyTrees(fuzzCrate) { - const trees = [] - for (const block of read(`${fuzzCrate}/Cargo.lock`) - .split('[[package]]') - .slice(1)) { - if (/^source = /m.test(block)) continue - const name = /^name = "([^"]+)"/m.exec(block)?.[1] - if (!name) throw new Error(`${fuzzCrate}/Cargo.lock: a package with no name`) - if (name.endsWith('-fuzz')) continue - trees.push(crateDir(name)) - } - return trees + return pathPackageNames(read(`${fuzzCrate}/Cargo.lock`)) + .filter((name) => !name.endsWith('-fuzz')) + .map(crateDir) } -/** The files a crate compiles through `#[path = "..."]`, from its `src/`. */ -function pathModuleFiles(crate) { - const files = [] - const walk = (dir) => { - if (!existsSync(join(REPO_ROOT, dir))) return - for (const entry of readdirSync(join(REPO_ROOT, dir), { - withFileTypes: true, - })) { - const rel = `${dir}/${entry.name}` - if (entry.isDirectory()) walk(rel) - else if (entry.name.endsWith('.rs')) { - const attribute = /#\[\s*path\s*=\s*"([^"]+)"\s*\]/g - for (const match of read(rel).matchAll(attribute)) { - files.push(posix.normalize(`${dir}/${match[1]}`)) - } - } - } +/** Every feature counted as on: a module behind any feature is kept. */ +const EVERY_FEATURE = { has: () => true } + +/** The files a path package compiles, `#[path]` and include files among them. */ +function compiledFiles(crate) { + const { files, unresolved } = compiledCrateFiles( + REPO_ROOT, + crate, + EVERY_FEATURE, + ) + if (unresolved.length > 0) { + throw new Error( + `${crate}: compiledCrateFiles could not follow:\n${unresolved.join('\n')}`, + ) } - walk(`${crate}/src`) return files } +/** + * The files a fuzz crate's targets name by `#[path]` or an include. A target + * is a crate root that `compiledCrateFiles` does not walk, since it walks a + * library. Its plain `mod` files sit in the fuzz crate's own folder. + */ +function fuzzTargetFiles(fuzzCrate) { + const dir = `${fuzzCrate}/fuzz_targets` + const rel = (abs) => relative(REPO_ROOT, abs).replaceAll('\\', '/') + return readdirSync(join(REPO_ROOT, dir)) + .filter((name) => name.endsWith('.rs')) + .flatMap((name) => { + const file = join(REPO_ROOT, dir, name) + const text = readFileSync(file, 'utf8') + return [ + rel(file), + ...moduleDeclarations(text) + .filter((decl) => decl.path !== undefined) + .map((decl) => rel(resolve(dirname(file), decl.path))), + ...includes(text) + .filter((include) => include.literal !== undefined) + .map((include) => rel(resolve(dirname(file), include.literal))), + ] + }) +} + const coversTree = (pattern, file) => pattern.endsWith('/**') && file.startsWith(pattern.slice(0, -2)) @@ -115,21 +136,25 @@ describe('fuzz.yml', () => { it('runs on pull requests touching every crate a fuzz target compiles', () => { const trees = [...new Set(fuzzCrates().flatMap(pathDependencyTrees))] expect(trees).toContain('packages/eql/crates/eql-bindings') - const missing = trees.filter((dir) => !selected(`${dir}/src/lib.rs`)) + const missing = trees.filter((dir) => !selected(`${dir}/Cargo.toml`)) expect( missing, `pull_request.paths does not select these crates, which a fuzz target compiles:\n${missing.join('\n')}`, ).toEqual([]) }) - it('runs on pull requests touching a file those crates compile by #[path]', () => { + it('runs on pull requests touching every file those crates and the targets compile', () => { const trees = [...new Set(fuzzCrates().flatMap(pathDependencyTrees))] - const files = trees.flatMap(pathModuleFiles) + const files = [ + ...trees.flatMap(compiledFiles), + ...fuzzCrates().flatMap(fuzzTargetFiles), + ] + // The Go guest's resolver, which the Python core crate compiles by #[path]. expect(files).toContain('languages/golang/encrypt/guest/src/targets.rs') - const missing = files.filter((file) => !selected(file)) + const missing = [...new Set(files.filter((file) => !selected(file)))] expect( missing, - `pull_request.paths does not select these files, which a fuzzed crate compiles by #[path]:\n${missing.join('\n')}`, + `pull_request.paths does not select these files, which a fuzz target compiles:\n${missing.join('\n')}`, ).toEqual([]) }) }) diff --git a/scripts/__tests__/lib/cargo-lock.mjs b/scripts/__tests__/lib/cargo-lock.mjs new file mode 100644 index 000000000..e056c2ffe --- /dev/null +++ b/scripts/__tests__/lib/cargo-lock.mjs @@ -0,0 +1,17 @@ +/** + * The packages a `Cargo.lock` records with no `source` line: the crates its + * workspace compiles from this tree by path, the workspace's own members + * among them. A new path dependency appears here, so a test that derives a + * `paths:` filter or a cache key from this list fails until it follows. + */ +export function pathPackageNames(lock) { + return lock + .split('[[package]]') + .slice(1) + .filter((block) => !/^source = /m.test(block)) + .map((block) => { + const name = /^name = "([^"]+)"/m.exec(block)?.[1] + if (!name) throw new Error(`a Cargo.lock package with no name:\n${block}`) + return name + }) +} diff --git a/scripts/__tests__/python-ci.test.mjs b/scripts/__tests__/python-ci.test.mjs index cd3784079..4dd95bcbe 100644 --- a/scripts/__tests__/python-ci.test.mjs +++ b/scripts/__tests__/python-ci.test.mjs @@ -1,6 +1,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs' import { join, posix } from 'node:path' import { describe, expect, it } from 'vitest' +import { pathPackageNames } from './lib/cargo-lock.mjs' import { filterCovers } from './lib/paths-filter.mjs' import { REPO_ROOT } from './lib/repo-root.mjs' import { @@ -43,12 +44,8 @@ const read = (rel) => readFileSync(join(REPO_ROOT, rel), 'utf8') * crates at `packages/eql/crates/`. */ function pathDependencyTrees() { - const lock = read(`${PYTHON_ROOT}/Cargo.lock`) const trees = new Set() - for (const block of lock.split('[[package]]').slice(1)) { - if (/^source = /m.test(block)) continue - const name = /^name = "([^"]+)"/m.exec(block)?.[1] - if (!name) throw new Error(`Cargo.lock package with no name:\n${block}`) + for (const name of pathPackageNames(read(`${PYTHON_ROOT}/Cargo.lock`))) { if (name.startsWith('stackencrypt-')) continue const dir = [`packages/${name}`, `packages/eql/crates/${name}`].find((d) => existsSync(join(REPO_ROOT, d, 'Cargo.toml')),